Get a company's brand
Get a company's name, description, logos, icons, colours, fonts, social profiles and published contact details from its domain.
We read the homepage, its web app manifest, up to 4 of its own stylesheets (on its own host or its CDN), and /favicon.ico when the page names no icon: 8 requests at most, robots.txt included. Every value says where on the site it came from, so you can judge how far to trust it.
Send the parameters as a query string with GET, or as a JSON object (up to 1 MB) with POST to the same path. For many domains, start a job.
Parameters
Headers
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
Authorization | string | Yes | – | Bearer followed by your API key. See Authentication. |
Query parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
domain | string | Yes, or url | – | The company's domain, such as acme.com. www.acme.com is read as given; a URL here means its domain. Send domain or url, not both. |
url | string | Instead of domain | – | A full http(s) URL on the site. The site's homepage is read, not this page. |
roles | boolean | No | false | true computes the colour roles on a large page too. By default they are computed on pages up to about 6,000 tags and 1.6 MB of CSS, four times the earlier limit: almost every small-business site and most large brand homepages. On a larger page colors.roles is null with a reason unless you ask. On a very large page even roles=true can come back null with over the CPU budget. A call with roles=true is stored apart from one without. |
screenshot | string | No | – | viewport or fullPage: also return a WebP image of the homepage as a browser shows it, the first screen or the whole page up to 12,000 px tall. |
viewport | string | No | desktop | desktop (1366×768) or mobile (412×915): the screen for the screenshot. |
maxAge | integer | No | 86400 | How old a stored answer may be, in seconds, up to 2,592,000 (30 days). 0 fetches fresh. |
timeout | integer | No | 20 | How long the call may take, in seconds, 1–60. |
Response
| Field | Type | Description |
|---|---|---|
url | string | The URL you sent, when you sent one. |
domain | string | The site read (lowercase; international names in xn-- form). |
finalUrl, status | string, integer | The homepage that answered, and its HTTP status. On a 404 or 500 every brand field is empty. |
blocked | object | null | The firewall that answered instead of the homepage. Then every brand field is empty: we never guess. |
name | object | {value, source}. Sources, best first: schema.org Organization, schema.org WebSite, og:site_name, application-name, apple-mobile-web-app-title, the manifest, the title. |
description | object | {value, source}. Sources, best first: schema.org Organization, the meta description, og:description, twitter:description, schema.org WebSite, the manifest. |
logos | array | The site's own logos, best first, each {url, source, confidence, type, width, height, variant, fallback}: the logo the page shows in its header or link home (an image, or an inline SVG as a data: URL with its colors), then the schema.org logo (marked when it differs from the one the page shows) and og:logo. Never a favicon or share image: those are in icons and images, with one exception. When the page has no logo image, the site's best icon (icons.best) stands in as the only entry, with fallback: true, a source of icon fallback (…) naming where the icon came from, and confidence 0.3. Every real logo has fallback: false. variant is light for a logo drawn for dark backgrounds, dark for the opposite, or null. Image-proxy URLs (/_next/image?url=…) are given as the original file. |
logosReason | string | null | Why logos is empty, or holds only the fallback icon, when it does. |
wordmark | object | null | {text, source: "home link text"}: the site's name as the page sets it in type, from its first link home when that link holds no image and its text names the site (the domain's name or the brand name, such as KYLIAN on kylian.ai). Menu items, skip links and written-out addresses are left out. null otherwise. |
icons | object | best, the one icon to show the site by: an SVG icon (it scales), else the largest declared size; null when the site declares none. Then the favicon, appleTouch and manifest lists, and maskIcon. Each icon is {url, sizes, type, source}, manifest icons with purpose. |
images | object | ogImage and twitterImage. |
colors | object | themeColor (with light and dark variants), the manifest's theme and background, cssVariables (custom properties holding colours, with how often they are used) and mostUsed (colours across the CSS read, weighted by where they paint: score, and seen, the roles they paint). neutral marks greys, black and white; stock marks a shared framework's default colours (WordPress presets, Bootstrap, Tailwind's palette, shadcn/ui's greys), ranked after the site's own. Each has a hex. |
colors.roles | object | null | The colours the page's own elements get, computed by a style engine over the page and its stylesheets: primary (the main button's background, else the link colour, else a coloured header), accent (the link colour when it differs), background and text (body), and headerBackground. Each is {hex, from, element, method: "computed"}. null when not computed, with rolesReason. |
colors.rolesDark | object | null | The same roles in the visitor's dark mode, when the site has one. |
colors.rolesReason | string | null | Why roles is null: a large page: colour roles are computed when asked (roles=true), the page or its stylesheets are over the size cap, the page has too much markup for the pass, over the CPU budget, no <body> or the style engine failed on this page. null when the roles are there. |
fonts | array | {family, source, uses}: Google Fonts, @font-face, Adobe Fonts, then fonts the CSS asks for. |
typography | object | null | The site's base typography, read from its CSS as a 1280 × 800 desktop window applies it: rootFontSize, and for body, h1–h6, button and link the family, size, weight, lineHeight and letterSpacing. Each value has value (in force, var() resolved, or null), declared (as written), px with its basis for lengths, number for weight, primary and generic for families, its source stylesheet (or style element), selector, media, inherited (body or html) and, when null, a reason naming the browser default. null on a blocked or error page. |
social | array | {network, url, source} from schema.org sameAs, then links to known social sites. Never visited. |
contact | object | {source, email, telephone, address, contactPoints}, only from the site's own schema.org Organization or LocalBusiness data. |
language | object | The homepage's language. |
fetches | array | Each request made: {url, purpose, status, bytes, error}. |
screenshot | object | With screenshot: {format: "webp", kind, viewport, bytes, data}, the image in base64. |
cached, ageSeconds | boolean, integer | Whether this answer came from our store, and how old it is. |
Colour roles are computed on the page as fetched, without JavaScript: a page whose buttons get their colours from script gets the colours of its static HTML and CSS.
Names and descriptions never carry an email address or phone number: where the site's text has one, it shows as "…". Contact details only appear in contact.
typography reads the site's base rules (body, h1, h1, .h1, button and .btn, a), not each widget's styling. Nothing is guessed: a value the CSS does not declare is null, with a reason.
How we fetch
We fetch as Rankfield-User (Mozilla/5.0 (compatible; Rankfield-User/1.0; +https://rankfield.dev/bot)), as plain HTML without running JavaScript, unless you ask for a rendered page or a screenshot.
- robots.txt: obeyed on every request and redirect, following the rules for
Rankfield-User, or forRankfieldBotwhen the file has none for it, or*when it has neither. Social sites and font services are named from their links, never fetched. - Firewalls: a challenge, CAPTCHA or block page is reported in the answer, never worked around and never retried.
- Never: cookies, logins, proxies or other bots' names. JavaScript runs only when you ask for it.
- Excluded sites: Reddit, LinkedIn, Facebook, Instagram, X/Twitter and TikTok (subdomains included), Google search result pages, and sites whose owners asked us are never fetched.
- Pace: each website is fetched gently, shared by all customers, slower when its robots.txt sets a
Crawl-delayor after a slow answer. Many URLs on one site go at that site's pace.
Repeat calls to the same website within about a minute can wait their turn for that site's slot. If the wait runs past the call's time limit (20 seconds by default), the call ends with 504 timeout and details.reason sitePace. It doesn't count as usage: retry after Retry-After, or allow a longer timeout.
More: Rankfield crawlers.
Errors
Errors are {"error": {"code", "message", "details"}}. Branch on code; message is for people.
| Status | Code | When |
|---|---|---|
| 400 | invalid_input | A parameter is missing, unknown or malformed. |
| 401 | unauthorized | No valid API key. |
| 403 | robots_disallowed | robots.txt disallows Rankfield-User for the homepage (a server error counts as no); details has the rule and line. |
| 403 | site_excluded | The site is on the exclusion list. |
| 403 | blocked | The website's bot protection refused us. Reported at once and never worked around; details says which protection. Not counted as usage. |
| 415 | unsupported_content_type | The homepage answered 2xx but isn't HTML. |
| 422 | address_not_public | The host resolves to a private or internal address. |
| 429 | busy | Our fetch capacity is busy: the call was refused at once, waited 4 seconds without a turn, or its time limit ran out while it waited (then details.reason is capacity). Not counted as usage. Retry after Retry-After seconds. |
| 500 | internal | Our bug. Retry. |
| 502 | fetch_failed | The site could not be reached: DNS failed, neither https nor http connected, or more than 5 redirects. |
| 503 | unavailable | The service is restarting or briefly unavailable. Retry after Retry-After. (A full fetch capacity answers 429 busy.) |
| 504 | timeout | The website didn't answer within the call's time limit. When details.reason is sitePace, the call waited its turn for this website instead, which we fetch gently, shared by all customers: retry after Retry-After, or allow a longer timeout. |
Only answered calls and the 502 and website-side 504 errors count as usage; refusals, our own errors (500, 503), 429 busy and a 504 with details.reason sitePace don't.
Limits
No per-key limits. When the server is busy you get 429 busy with Retry-After. Waiting calls are served in turn across keys, so a key with a few calls is never stuck behind one with thousands.
MCP
On the MCP server, the tool for this is brand_profile.