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:
StatusMeaningWhere to look
400Invalid input, or a well-formed domain that can’t be resolvedBad Request
401Missing or invalid API keyUnauthorized
408Cold-hit timeout or custom timeoutMS exceededRequest Timeout
422Invalid, disposable, or free email (only on by_email brand lookups)Invalid Email
429Rate limit exceeded — wait the seconds given in the Retry-After headerHandle Rate Limits
500Transient server errorServer Error
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 three 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 domain is well-formed but the resolver couldn’t reach it (DNS failure, dead site, hostile WAF). This is effectively “no brand here,” and on /brand/retrieve it comes back as a 400, not a 404.
  • NOT_FOUND — no brand matched the identifier you passed. The request is not billed.
Surface an INPUT_VALIDATION_ERROR as a user-facing validation message, not a retryable failure. A WEBSITE_ACCESS_ERROR means the brand doesn’t exist or can’t be crawled, so treat it as a clean “not found.”

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 5-minute platform maximum. For reference, cold-hit latency is around 7 seconds at p50 and up to a minute at p99; cached hits return in around 250ms. Three recovery strategies, in order of preference: 1. Prefetch the domain or email. A warmed cache lands the eventual /brand/retrieve in under a second. 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:

Invalid Email (422)

Returned only by by_email brand lookups (POST /brand/retrieve with type: "by_email"). 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.

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.