Skip to main content
If you’re building on top of Context.dev’s APIs and SDKs, this page contains some best practices to give your users a good experience.

1. Prefetch as soon as you know the domain or email

60% of Brand API requests return within 1 second. But for the other 40%, when Context.dev hasn’t seen the domain before, it can take up to a minute. Prefetching fixes this. It’s a free API that you can call a few moments before your app needs the brand data. It makes sure the results come back typically in under a second when you actually run the Brand API call. See Prefetch for Faster Response for the canonical pattern.

2. Cache API responses

If you need brand or classification API outputs frequently, it’s a good idea to cache them in your DB or in the browser’s localStorage. This saves your rate limit quota and API credits usage. Brand records (logos, descriptions, addresses, classifications, etc.) change very infrequently. Context.dev itself only updates a brand in cache every 3 months. Some recommended TTLs:
DataSuggested TTLWhy
Brand response30 daysIdentity barely moves; Context.dev itself only refreshes every 3 months.
Product extractions7 daysCatalogs change more often than identity.
Industry codes (NAICS / SIC)IndefiniteClassification systems move on regulator timescales.

3. Set timeoutMS for cold hits

Warm domains come back in around 250ms. A domain Context.dev has never seen has to be crawled live, which can take up to 60 seconds. If your client timeout is set to something tight like 5 seconds, you’ll cut off the exact requests that needed the most patience. So on any path that can tolerate the wait, set a generous timeout. The timeoutMS parameter lets you say it explicitly:
const { brand } = await client.brand.retrieve({
  domain: "example.com",
  timeoutMS: 60000, // 60s of headroom for cold hits
});
timeoutMS tops out at 300,000ms (5 minutes); past that the server gives up and returns a 408. See Troubleshooting for how to handle a 408 when it does happen.

4. Run bulk jobs in the background

Some calls are inherently slow. A full-site crawl (/web/crawl) or a storefront product sweep (/brand/ai/products) walks many pages, and on a large site that can run long. Don’t block a user’s request on it. Kick the work off, return immediately, and write the results back when they land.
const response = await client.web.webCrawlMd({
  url: "https://context.dev",
  maxPages: 50,
  maxDepth: 2,
});
console.log(`${response.results.length} pages crawled`);
Wrap that call in whatever worker queue you already run (like BullMQ, Sidekiq, Cloud Tasks, or a goroutine, etc.), show the user a status field while it runs, and notify them when the result is ready.

5. Retry transient failures with backoff

Three error classes are worth retrying: 429 (rate limit), 408 (cold-hit timeout), and 500 (transient server error). Everything else (a 401, a 422) is a bug in the request, and retrying it just wastes calls. The retry contract (honor the Retry-After header on 429s, otherwise doubling delay, capped attempts) and copy-paste implementations in every SDK live on the Rate limits page. Use that pattern rather than rolling your own.

6. Skip free and disposable emails before you look them up

A brand-by-email lookup against gmail.com, yahoo.com, outlook.com, and the 10,000+ disposable services out there will never resolve to a company: there’s no brand behind a personal inbox. /brand/retrieve-by-email returns a 422 for these. You can save the round trip by filtering the obvious ones up front:
const FREE_PROVIDERS = new Set([
  "gmail.com", "yahoo.com", "hotmail.com", "outlook.com",
  "aol.com", "icloud.com", "proton.me",
]);

function shouldEnrich(email: string): boolean {
  const domain = email.split("@")[1]?.toLowerCase();
  return Boolean(domain) && !FREE_PROVIDERS.has(domain);
}
You don’t have to maintain the full disposable-domain list yourself: /brand/prefetch-by-email already detects 10,000+ free and disposable providers and short-circuits them for you, so calling it skips the manual guard entirely. Read how to prefetch.

7. Set up fallbacks for missing datapoints

A 200 doesn’t promise a complete record. A private company has no stock ticker, a brand-new domain may have no logo on file yet, and any optional field can come back null or empty. Fall back to a default value instead of branching your whole layout around what’s present:
const primaryColor = brand.colors[0]?.hex ?? "#000000";
const logoUrl = brand.logos[0]?.url ?? "/placeholder-logo.svg";
const description = brand.description ?? `${brand.title} (description unavailable)`;
Defensive defaults keep the page from collapsing when the API hands back a sparse record.

8. Show a real loading state on cold paths

Cold brand calls and full-site crawls take seconds, not milliseconds, long enough that a frozen screen can seem “broken.” Some recommended UI fixes:
  • Use skeleton boxes shaped like the content, not a generic spinner.
  • Echo the user’s input back optimistically while the fetch runs.
  • Have a graceful “we couldn’t find this one” state ready for 400 responses (NOT_FOUND or WEBSITE_ACCESS_ERROR) and sparse records.
Build it once and the wait stops feeling like a bug.

9. Keep your API key on the server

Your API key is a bearer credential: whoever holds it can spend your credits and burn your rate limit. Treat it exactly like a database password. That means it never ships in front-end code, where anyone with devtools can read it. The one exception is Logo Link, which uses a separate publicClientId credential built to be safe in the client-side browser. See Get logos from a domain.

Prefetch

Warm the cache before the user-facing call.

Rate limits

Stay under the per-minute request cap.

Fair use

Responsible use of brand data and scraped content.

Troubleshooting

Error codes and recovery patterns.