Documentation
¶
Overview ¶
Package homedepot is a Go client for Home Depot's internal customer order history API (the same endpoints the homedepot.com purchase-history page itself calls). Authentication is cookie replay: the caller exports their browser cookies to a JSON file after logging in, and the client decodes the THD_CUSTOMER cookie for the auth token.
The API is unofficial and endpoint drift is possible. Callers should treat 4xx responses as either auth expiration (AuthError) or schema changes.
This client is inspired by the schema mapping in https://github.com/joshellissh/homedepot-history (MIT).
Index ¶
- Constants
- Variables
- func DefaultCookiePath() (string, error)
- func ExtractCustomerCookie(cookies []Cookie) (string, error)
- func LoadFromFile(path string) (Credentials, []Cookie, error)
- type APIError
- type AuthError
- type Client
- type Config
- type Cookie
- type Credentials
- type FulfillmentGroup
- type LineItem
- type OrderDetail
- type OrderSummary
Constants ¶
const ( DefaultBaseURL = "https://www.homedepot.com" DefaultTimezone = "America/New_York" )
Configurable defaults.
Variables ¶
var ErrEmptyCookieFile = errors.New("cookie file is empty")
ErrEmptyCookieFile is returned when the cookies file parses but has no entries.
var ErrMissingCustomerCookie = errors.New("THD_CUSTOMER cookie not found in jar; export cookies while logged in to homedepot.com")
ErrMissingCustomerCookie is returned when the cookie jar does not contain a THD_CUSTOMER cookie — usually because the user exported cookies while logged out.
var ErrRateLimited = errors.New("home depot API rate-limited the request")
ErrRateLimited is returned when Home Depot's API rejects a request as rate-limited. Callers can back off and retry.
Functions ¶
func DefaultCookiePath ¶
DefaultCookiePath returns the canonical on-disk location for the exported cookies file: $HOME/.homedepot-api/cookies.json.
func ExtractCustomerCookie ¶
ExtractCustomerCookie returns the value of the THD_CUSTOMER cookie from a jar, or ErrMissingCustomerCookie if it is not present.
func LoadFromFile ¶
func LoadFromFile(path string) (Credentials, []Cookie, error)
LoadFromFile is the convenience one-shot: reads the cookie jar at path, finds THD_CUSTOMER, decodes it, and returns everything the client needs.
Types ¶
type AuthError ¶
AuthError signals the request was rejected as unauthenticated (typically 401 or 403). Usually means the exported cookies have expired or an MFA step-up has invalidated the session — the user needs to log in again and re-export cookies.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the Home Depot API client.
func NewClient ¶
NewClient reads cookies from cfg.CookieFile (defaulting to DefaultCookiePath() when empty), decodes the THD_CUSTOMER cookie for auth, and returns a ready-to-use Client. This is the canonical constructor.
func NewClientWithCredentials ¶
func NewClientWithCredentials(creds Credentials, cookies []Cookie, cfg Config) *Client
NewClientWithCredentials builds a Client from already-loaded credentials + cookies. Use this when cookies come from somewhere other than a JSON file on disk (a secret manager, an env var, a unit test, etc.). cfg.CookieFile is ignored on this path.
func (*Client) GetOrder ¶
func (c *Client) GetOrder(ctx context.Context, summary OrderSummary) (OrderDetail, error)
GetOrder fetches full detail for a single order summary. Routes to the online or in-store /orderdetails variant based on summary.OrderOrigin.
func (*Client) HealthCheck ¶
HealthCheck issues a small /orderhistory call to verify auth works. It returns nil on success and an AuthError, ErrRateLimited, or APIError on the various failure modes.
func (*Client) ListOrders ¶
ListOrders returns every order summary whose salesDate falls in the [start, end] range (inclusive). Walks year-by-year, mirroring the reference impl, with polite pauses between pages.
Note: Home Depot's API only returns orders from approximately the last 24 months, so requests older than that will yield nothing regardless.
type Config ¶
type Config struct {
// CookieFile is the path to a JSON cookie jar exported from a logged-in
// browser session. Only consulted by NewClient. Empty means use the
// canonical location (DefaultCookiePath()).
CookieFile string
// Logger receives structured client-side diagnostics. Nil means a no-op
// logger — callers who want visibility should always pass a real
// *slog.Logger (typically the caller's own scoped provider logger).
Logger *slog.Logger
// HTTPClient overrides the default outbound HTTP client. Useful for
// injecting a transport with rate limiting or tracing. Nil means the
// default (30s timeout).
HTTPClient *http.Client
// BaseURL overrides the API host. Empty means DefaultBaseURL. Primarily
// used by tests pointing at an httptest.Server.
BaseURL string
// Timezone is the IANA timezone string sent on every request. Empty means
// DefaultTimezone.
Timezone string
// PageSize is the /orderhistory page size. Zero or negative means 100.
PageSize int
}
Config is the constructor parameter for NewClient / NewClientWithCredentials. Every field is optional; sensible defaults are applied for empty values.
Design intent: matches the ClientConfig / Config pattern used by the sibling clients walmart-client-go and costco-go so callers (itemize) can wire all three the same way.
type Cookie ¶
type Cookie struct {
Name string `json:"name"`
Value string `json:"value"`
Domain string `json:"domain,omitempty"`
Path string `json:"path,omitempty"`
}
Cookie is a single browser cookie the client will send on every request. Only Name and Value are strictly needed; Domain and Path are preserved for documentation but not used when serializing the outbound request.
func LoadCookies ¶
LoadCookies reads and parses a cookie file from disk. Two JSON shapes are accepted so the file works with both bare-array exports and explicitly wrapped ones:
[{"name": "...", "value": "..."}, ...]
{"cookies": [{"name": "...", "value": "..."}, ...]}
type Credentials ¶
Credentials is the identity + auth token extracted from the THD_CUSTOMER cookie. UserID is used to build the request URL; AuthToken is sent in the Authorization header on every request; CustomerAccountID is required for in-store order-details lookups.
func DecodeCustomerCookie ¶
func DecodeCustomerCookie(value string) (Credentials, error)
DecodeCustomerCookie decodes a THD_CUSTOMER cookie value into Credentials.
The cookie value is a dot-separated triple; the first segment is base64-encoded JSON of the form {"u": userId, "i": authToken, "t": customerAccountId}. Whitespace inside u and t is stripped (matches the reference implementation's behavior).
type FulfillmentGroup ¶
type FulfillmentGroup struct {
LineItems []LineItem `json:"lineItems"`
}
FulfillmentGroup groups line items by fulfillment method (ship-to-home, BOPIS, etc.). We only care about the line items for categorization.
type LineItem ¶
type LineItem struct {
THDSKU string `json:"thdSku"`
LineID string `json:"lineId"`
SKUNumber string `json:"skuNumber"`
ModelNumber string `json:"modelNumber"`
BrandName string `json:"brandName"`
Description string `json:"description"`
UnitPrice float64 `json:"unitPrice"`
TotalPrice float64 `json:"totalPrice"`
ShippingCharge float64 `json:"shippingCharge"`
CurrentQuantity float64 `json:"currentQuantity"`
OriginalOrderedQty float64 `json:"orginalOrderedQuantity"` // sic — upstream typo
CancelledQuantity float64 `json:"cancelledQuantity"`
IsGiftCard bool `json:"isGiftCard"`
ImageURL string `json:"imageUrl"`
UPCCode string `json:"upcCode"`
StatusDescription string `json:"statusDescription"`
}
LineItem is a single purchased item.
Notable API quirk: OriginalOrderedQty maps to the API field "orginalOrderedQuantity" — that spelling with the missing "i" is the upstream typo, not ours.
func (LineItem) PurchasedQuantity ¶
PurchasedQuantity is CurrentQuantity minus CancelledQuantity, clamped at zero. Use this in preference to CurrentQuantity when computing what the customer actually paid for.
type OrderDetail ¶
type OrderDetail struct {
OrderNumber string `json:"orderNumber"`
CustomerAccountID string `json:"customerAccountId"`
UserID string `json:"userId"`
OrderOrigin string `json:"orderOrigin"`
StatusDescription string `json:"statusDescription"`
SalesDate string `json:"salesDate"`
SubTotalAmount float64 `json:"subTotalAmount"`
TaxTotalAmount float64 `json:"taxTotalAmount"`
GrandTotalAmount float64 `json:"grandTotalAmount"`
ShippingCharge float64 `json:"shippingCharge"`
DeliveryCharge float64 `json:"deliveryCharge"`
StoreNumber string `json:"storeNumber"`
StoreName string `json:"storeName"`
FulfillmentGroups []FulfillmentGroup `json:"fulfillmentGroups"`
}
OrderDetail is the response from /orderdetails for a single order. The commented-out fields are present in real responses but unused by itemize (kept in the JSON so nothing breaks if we start using them later).
func (OrderDetail) AllLineItems ¶
func (o OrderDetail) AllLineItems() []LineItem
AllLineItems flattens line items across every fulfillment group.
type OrderSummary ¶
type OrderSummary struct {
OrderOrigin string `json:"orderOrigin"` // "online" or "instore"
OrderNumbers []string `json:"orderNumbers"`
SalesDate string `json:"salesDate"`
StoreNumber string `json:"storeNumber"`
StoreName string `json:"storeName"`
TotalAmount float64 `json:"totalAmount"`
OrderStatus string `json:"orderStatus"`
RegisterNumber string `json:"registerNumber"` // in-store only
TransactionID string `json:"transactionId"` // in-store only
}
OrderSummary is one entry from the /orderhistory response. The field set is deliberately small — this is only what's needed to (a) render a listing and (b) request full details later.