Skip to main content
Every Context.dev failure surfaces as a typed exception in the SDK or a structured error envelope from the HTTP API. The envelope carries a status, a human-readable message, and a machine-readable error_code (429 responses use code instead):
The most common status codes: 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 the www. subdomain for you, so stripe.com, https://stripe.com, and www.stripe.com all resolve to the same brand.
A 400 actually comes back in several distinct flavors, told apart by 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 a by_name brand lookup must be 3–30 characters), or both/neither of an either-or pair (/web/styleguide, /web/fonts, and /web/screenshot each take domain or directUrl, 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/retrieve it comes back as a 400, not a 404.
  • WEBSITE_NOT_FOUND — the domain definitively doesn’t resolve (dead DNS). Previously folded into WEBSITE_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 set pdf[shouldParse]: false.
  • PDF_IMAGES_ONLY — the target PDF is a scan with images but no text layer. Retry with OCR enabled (ocr=true on /parse, pdf[ocr]=true on scraping endpoints) to recover the content at 1 credit per recovered page.
Surface an 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:
Common gotchas:
  • Env var name mismatch. The expected name is CONTEXT_DEV_API_KEY. Watch for typos like CONTEXTAPIKEY or CONTEXT_DEV_APIKEY.
  • .env not loaded. In Node, import "dotenv/config" (or require("dotenv").config()) must run before the SDK is initialized. In Python, load_dotenv() from python-dotenv does 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 your timeoutMS 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:
3. Retry with exponential backoff. A second attempt frequently lands on the now-warm cache from the first crawl. These snippets assume a configured client from the Quickstart:

Content Too Large (413)

Scraping and parsing endpoints cap remote content downloads at 20 MB and return a 413 with error_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 by error_code: invalid emails and cold-domain timeouts.

Invalid email

Returned by by_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). The error_code is FREE_EMAIL_DETECTED.
  • Malformed email format.
Filter known-bad providers client-side to skip the round trip:
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 a by_email lookup 500s, extract the domain and retry with type: "by_domain":
If 500s persist across retries, check status.context.dev for an open incident, then email support with the domain, timestamp, and full error body.

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 no stock; some sites have no discovered logos). Always provide defaults:
Render the fallback in the same shape so your layout does not shift. Don’t conditionally hide whole sections.

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:
Enable verbose SDK logging when chasing 4xx mysteries. Each SDK exposes a different mechanism: Python’s logging.basicConfig(level=logging.DEBUG), Node’s console.log around the call, or running through a local proxy that captures the request.

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.