status, a human-readable message, and a machine-readable error_code (429 responses use code instead):
For the current API status, see status.context.dev.
Bad Request (400)
Good news first: you don’t have to sanitize the domain yourself. The API strips the protocol and thewww. subdomain for you, so stripe.com, https://stripe.com, and www.stripe.com all resolve to the same brand.
error_code:
INPUT_VALIDATION_ERROR— the request itself is malformed. A domain with no TLD (stripe), a missing required parameter, an out-of-range value (company name on aby_namebrand lookup must be 3–30 characters), or both/neither of an either-or pair (/web/styleguide,/web/fonts, and/web/screenshoteach takedomainordirectUrl, never both and never neither).WEBSITE_ACCESS_ERROR— the site exists but couldn’t be scraped (blocked, hostile WAF, unreachable mid-crawl). This is effectively “no brand here,” and on/brand/retrieveit comes back as a 400, not a 404.WEBSITE_NOT_FOUND— the domain definitively doesn’t resolve (dead DNS). Previously folded intoWEBSITE_ACCESS_ERROR; now its own code so you can tell a dead domain apart from a live site that blocked the crawl.NOT_FOUND— no brand matched the identifier you passed. The request is not billed.PDF_SKIPPED— the scraping target is a PDF and the request setpdf[shouldParse]: false.PDF_IMAGES_ONLY— the target PDF is a scan with images but no text layer. Retry with OCR enabled (ocr=trueon/parse,pdf[ocr]=trueon scraping endpoints) to recover the content at 1 credit per recovered page.
INPUT_VALIDATION_ERROR as a user-facing validation message, not a retryable failure. WEBSITE_NOT_FOUND and WEBSITE_ACCESS_ERROR both mean the brand can’t be crawled, so treat them as a clean “not found” — WEBSITE_NOT_FOUND is the definitive signal that the domain is dead.
Unauthorized (401)
The API key is missing, invalid, expired, or deleted. Start by confirming the key is actually being loaded, without ever logging the key itself:- Env var name mismatch. The expected name is
CONTEXT_DEV_API_KEY. Watch for typos likeCONTEXTAPIKEYorCONTEXT_DEV_APIKEY. .envnot loaded. In Node,import "dotenv/config"(orrequire("dotenv").config()) must run before the SDK is initialized. In Python,load_dotenv()frompython-dotenvdoes the same.- Key was rotated. Generate a new key in the dashboard and update the environment variable everywhere the application runs.
Request Timeout (408)
A 408 comes back when either yourtimeoutMS budget elapsed before the API finished, or the cold-hit crawl ran past the five-minute platform maximum. Warm-cache responses avoid crawl work, but exact latency depends on the target site, cache state, region, and requested work.
Two related behaviors on brand lookups for domains that aren’t cached yet: a timeoutMS below 10,000 ms fails immediately with a 422 COLD_DOMAIN_TIMEOUT_TOO_LOW instead of burning the budget on a guaranteed timeout (a cold crawl can never finish that fast), and when a cold lookup does 408, the error message includes a hint pointing at the free prefetch endpoint so your retry lands on a warm cache.
Three recovery strategies, in order of preference:
1. Prefetch the domain or email. A warmed cache avoids repeating cold crawl work on the eventual /brand/retrieve. See Prefetch for Faster Response.
2. Raise the timeoutMS budget. Minimum 1,000 ms, maximum 300,000 ms (5 minutes). 60 seconds is a sane default for non-time-sensitive paths:
client from the Quickstart:
Content Too Large (413)
Scraping and parsing endpoints cap remote content downloads at 20 MB and return a 413 witherror_code: "CONTENT_TOO_LARGE" when the requested content exceeds it. Batch page records carry the same code, and /web/search results with Markdown enrichment report it per result in markdown.code. An oversized auxiliary resource — like an embedded PDF on an otherwise scrapable page — degrades to the page result instead of failing the request; only the requested content itself being over the cap is terminal. No retry fixes a 413, so treat it as a permanent failure for that URL.
Unprocessable Request (422)
A 422 comes in two families, told apart byerror_code: invalid emails and cold-domain timeouts.
Invalid email
Returned byby_email brand lookups (POST /brand/retrieve with type: "by_email"), POST /people/enrich, and email-identifier prefetches — always before any credits are charged. The three causes:
- Disposable email service (e.g.
tempmail.com,guerrillamail.com). - Free email provider (
gmail.com,yahoo.com,hotmail.com,outlook.com,aol.com,icloud.com,proton.me). Theerror_codeisFREE_EMAIL_DETECTED. - Malformed email format.
POST /utility/prefetch applies a broader 10,000+ disposable-provider filter automatically when you pass identifier.email.
Cold-domain timeout too low
COLD_DOMAIN_TIMEOUT_TOO_LOW is returned by brand lookups when the domain isn’t cached yet and timeoutMS is below 10,000 ms — a cold crawl can never finish that fast, so the API fails immediately instead of letting the request run into a guaranteed 408. Raise timeoutMS to at least 60,000 ms for paths that may hit cold domains, or prefetch the domain first and retry once the cache is warm.
Server Error (500)
A 500 is transient. Two recovery patterns: 1. Retry with exponential backoff (same shape as the 408 retry above). 2. Fall back to a different lookup method. If aby_email lookup 500s, extract the domain and retry with type: "by_domain":
Missing or partial brand data (200)
A successful 200 does not guarantee every field is populated. Not every brand has every asset (a private company has nostock; some sites have no discovered logos). Always provide defaults:
Debugging recipes
Isolate the issue with a raw cURL request. If a cURL call works and the SDK call does not, the SDK or env-var setup is the suspect:logging.basicConfig(level=logging.DEBUG), Node’s console.log around the call, or running through a local proxy that captures the request.
Related resources
Rate limits
The full 429 recovery contract.
Prefetch
The fastest fix for 408 cold-hit timeouts.
Best practices
Caching, fallbacks, and error handling at the integration level.
Status page
Live API status.