JupiterFetch

API reference

One endpoint, one object, eight errors.

Everything below is version 1. Fields are added, never repurposed. A new key is not a breaking change, so parse permissively.

Need a key? Start free with 100 credits and no card, then create one on the API keys page.

Quick start

Requestcurl
# Blocking. Returns when the profile is ready, or at the wait budget.
curl -H "Authorization: Bearer $JUPITERFETCH_API_KEY" \
  "https://jupiterfetch.com/v1/brands/example.com"
Response200 · application/json
{
  "domain": "example.com",
  "name": "Example",
  "colorScheme": "both",
  "logos": [{
    "kind": "wordmark", "theme": "light",
    "sourceType": "inline-svg",
    "source": "https://cdn.jupiterfetch.com/example.com/….png",
    "format": "png", "width": 2048, "height": 512,
    "vectorUrl": "https://cdn.jupiterfetch.com/example.com/….svg",
    "originUrl": null
  }],
  "colors": {
    "roles": { "primary": "#635bff" },
    "palette": [{ "hex": "#635bff",
                 "type": "brand" }]
  },
  "quality": { "score": 86 },
  "fetch": { "path": "fast" }
}
// Trimmed. Full object shape in the API reference.

Code samples

The same short program in five languages, each using only its standard library, so there is nothing to install. It reads your key from JUPITERFETCH_API_KEY, retries a fetch_timeout once, and prints the best logo and the primary color.

brand.js
// Node 18+, no dependencies. Run with JUPITERFETCH_API_KEY set: node brand.js
// Server-side only: the API sends no CORS headers, and your key must never reach a browser.
const JUPITERFETCH_URL = 'https://jupiterfetch.com/v1'

async function getBrand(domain, attempt = 0) {
  const res = await fetch(`${JUPITERFETCH_URL}/brands/${encodeURIComponent(domain)}`, {
    headers: { Authorization: `Bearer ${process.env.JUPITERFETCH_API_KEY}` },
    signal: AbortSignal.timeout(70_000), // a little over the longest wait the API allows
  })
  if (res.ok) return res.json() // a Brand object

  // Errors are RFC 9457 problem documents. Branch on `code`, not on the status.
  const type = res.headers.get('content-type') ?? ''
  const problem = type.includes('application/problem+json')
    ? await res.json()
    : { code: 'unknown', detail: await res.text() }

  if (attempt === 0 && problem.code === 'fetch_timeout') {
    // The fetch usually keeps running, so a retry often comes back from the cache.
    return getBrand(domain, 1)
  }
  // Wait out a short rate limit and retry once. A longer one is reported, not waited on.
  const seconds = Number(res.headers.get('retry-after') ?? 1)
  if (attempt === 0 && problem.code === 'rate_limited' && seconds <= 10) {
    await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
    return getBrand(domain, 1)
  }

  const fetchId = problem.fetchId ? ` (fetchId ${problem.fetchId})` : ''
  throw Object.assign(new Error(`${problem.code}: ${problem.detail}${fetchId}`), {
    status: res.status,
    code: problem.code,
    fetchId: problem.fetchId,
  })
}

// Wrapped in a function because top-level await is not allowed in a CommonJS file.
async function main() {
  try {
    const brand = await getBrand('stripe.com')
    // logos is ranked best first and can be empty. Each source URL works for at least 30 days,
    // so download the file if you want to keep it.
    console.log('logo:', brand.logos[0]?.source ?? 'none')
    // Lowercase hex such as "#1a2b3c", or null.
    console.log('primary color:', brand.colors.roles.primary ?? 'none')
  } catch (err) {
    console.error(err.message)
    process.exitCode = 1
  }
}

main()

Base URL and authentication

Bearer keys that start with jf_. Every response is JSON, and timestamps are ISO-8601 UTC with lowercase hex colors.

Authenticated request
curl -H "Authorization: Bearer $JUPITERFETCH_API_KEY" \
  "https://jupiterfetch.com/v1/brands/example.com?wait=20000"

# Missing, malformed or wrong key → 401 unauthorized.

GET /v1/brands/{domain}

The only implemented endpoint. It blocks: a fresh cache entry returns immediately, a miss runs the fetch inline within the wait budget.

On success: 200 with the Brand object. On a budget overrun: 504 fetch_timeout, and the fetch keeps running, so the cache is populated by the time you retry.

Query parameterTypeMeaning
waitinteger, msHow long to block before giving up. Default 20000, maximum 60000; larger values are clamped. Exceeding it returns 504, and the fetch continues in the background.
refreshtrue | falseIgnore any cached profile and fetch again.

Response headers

Present on every /v1 response, success or failure, except where noted.

HeaderMeaning
Cache-StatusOne of hit, miss, stale, refresh.
X-Fetch-IdThe ft_… id for this attempt. Quote it in a bug report.
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetWhen the window resets.
X-Credits-RemainingCredits left on your account after this call. One credit is one successful call; there is no limit header and nothing resets. Absent for internal keys.
Cache-ControlAlways no-store. Do not put a shared cache in front of the API.
Retry-AfterSeconds to wait. Sent only with rate_limited.

The Brand object

One document per domain. Every enum below is closed. An unrecognized value means you are talking to a newer version than you think.

FieldTypeNotes
domainstringThe normalized domain the profile is keyed on.
urlstringThe URL actually fetched, after redirects.
namestringBrand name.
descriptionstringOne-line description, where the site offers one.
colorSchemelight | dark | bothWhich background the brand is designed against.
logos[]arrayEach entry has kind (logo, wordmark, symbol, icon, favicon, app-icon, og), theme (light, dark, any: the background the logo is drawn for, read from its file name or its colors, so a white wordmark is dark; any when it reads on both or the file does not say), sourceType, a confidence, and source: the logo hosted by JupiterFetch, a raster image URL valid for at least 30 days after you receive it. format (png, jpeg, webp, gif) is the hosted file’s format and is never null: an SVG logo is rasterized before hosting, and its sanitized SVG is at vectorUrl (null when the logo was not SVG). originUrl is where the image came from, or null when the page embedded it; never fetch it and use what comes back. width and height are the hosted file’s pixel size and are never null. See images.
colors.rolesobjectEight keys, each nullable: primary, secondary, accent, background, surface, textPrimary, textSecondary, link.
colors.palette[]arrayEvery color found, each typed brand, accent, dark or light, with brightness, usage share and the sources it was seen in. Hex is always lowercase.
typography.fonts[]arrayFamily, role (heading, body, display, code, ui), origin (google, adobe, custom, system), weights, sources and fallback.
imagesobjectog: the page’s social image, hosted the same way as a logo (source, originUrl, format, width, height; no vectorUrl, since a social image is never SVG), or null. twitter: the image the page names for X cards, hosted the same way, or null when it names none, or when that image fails our check or could not be hosted. When it is the same image as og, both carry the same URL. banners is empty today.
linksobjectWebsite and social profiles: twitter, linkedin, github, youtube, instagram, facebook, tiktok, crunchbase. Absent ones are null.
metaobjectTitle, language, theme color, the web app manifest, and any schema.org Organization block.
qualityobjectscore out of 100, coverage flags per section, and warnings[]. A thin result is a 200 with a low score, never a 404. Apply your own bar.
fetchobjectid, fetchedAt, expiresAt, path (fast or browser), durationMs, cached and sourceUrl.

Images

Every image in a Brand is hosted by JupiterFetch, so it works in your product with no conversion of your own.

Every image in a brand profile is hosted by JupiterFetch at https://cdn.jupiterfetch.com/… and stays valid for at least 30 days after you receive it. Download what you want to keep; hotlinking works during those 30 days and then breaks. format is always png, jpeg, webp or gif. SVG logos are rasterized (longest edge 2,048 px) and the sanitized SVG is at vectorUrl, safe to use in <img>. Serve stored copies with Content-Type from format and X-Content-Type-Options: nosniff. originUrl tells you where the image came from; never fetch it and use what comes back.

Render images with <img>. images.og follows the same shape as a logo, minus vectorUrl, because a social image is never SVG.

Errors

RFC 9457 problem documents, served as application/problem+json. Branch on code, not on the status: statuses are reused, codes are not.

CodeStatusWhen
invalid_url400The domain could not be parsed or normalized.
unauthorized401Missing, malformed or unrecognized bearer key.
quota_exceeded402Your account has no credits left. Successful calls spend a credit, cached or not; errors never do. Buy credits on /billing.
forbidden_domain403The domain resolves to a private range, link-local address or localhost, or is on the blocklist.
domain_unresolvable404DNS returned nothing for the domain.
site_blocked424The site refused us, usually with a WAF. A partial Brand may be attached to the problem document. It is answered even when an expired profile exists, and a repeat within ten minutes of a definite block is answered from a negative cache unless refresh=true.
rate_limited429Too many requests. Honor the retry-after header.
fetch_timeout504The wait budget expired or the fetch failed. After a timeout the fetch continues in the background and fills the cache; retry once.
Problem document
HTTP/1.1 504 Gateway Timeout
content-type: application/problem+json; charset=utf-8

{
  "type": "https://jupiterfetch.com/problems/fetch_timeout",
  "title": "Fetch timed out",
  "status": 504,
  "code": "fetch_timeout",
  "detail": "the fetch did not complete within 20000 ms; …",
  "fetchId": "ft_01J9…"
}

Also available as an MCP connector

The same brand lookup, for Claude, ChatGPT and any other MCP client, at https://mcp.jupiterfetch.com.

Once connected, asking for a company’s brand runs the same lookup as GET /v1/brands/{domain}, and each successful call spends one credit from your account.

Add JupiterFetch to Claude

Or add it by hand: put https://mcp.jupiterfetch.com in as a custom connector in Claude, ChatGPT or any other MCP client, sign in with your JupiterFetch account, and approve the connection.

In Claude Code, add it with one command, then run /mcp to authenticate:

Claude Codeone time
claude mcp add --transport http jupiterfetch \
  https://mcp.jupiterfetch.com

The Claude API’s MCP connector takes a bearer token directly, with no sign-in flow. Use a per-user key from /api-keys as authorization_token, in the header only, never in a URL:

Claude APImcp_servers
{
  "mcp_servers": [{
    "type": "url",
    "url": "https://mcp.jupiterfetch.com",
    "name": "jupiterfetch",
    "authorization_token": "jf_..."
  }],
  "tools": [{ "type": "mcp_toolset", 
              "mcp_server_name": "jupiterfetch" }]
}

Two tools, both read-only:

ToolWhat it doesCost
get_brandGet a brand profile for a domain.1 credit per successful lookup, cached results included. A partial result from a blocked page spends none.
get_creditsCheck your credit balance.Free.