Changelog
Changes to the Cormorant API, newest first. Within v1, fields are added, not removed; a renamed field or value is announced here with its old name, which requests may keep using for 30 days. The API contract is the reference for every field mentioned here.
Unreleased#
Fetch: the residential route fails over between networks#
- The
proxyroute now runs over several residential networks. When a site refuses an exit or a network fails, the request is retried at once through another one; each try is its ownproxyattempt with its status, reason andpremium_bytes. Route names and prices are unchanged. See Tiers and escalation.
Debug requests (available on request)#
X-Cormorant-Debug: 1onPOST /v1/searchandPOST /v1/fetch, for keys that support enabled for debugging: the request is recorded in detail and the answer carriesdebug_url, a link for support. Ignored for other keys. See API contract.
Standard and premium routes: renamed fields#
Every call is billed. Fetches run on standard routes (the shared cache,
a direct request, a headless browser, archives and alternative copies) and,
only when a site blocks those, on premium routes (the residential network
proxy and the rendering service proxy_render), which are priced higher.
Fields, values and labels that named the routes by their price now use
these names.
Requests. Send the new names now. Until 2026-11-19 the old request
field and filter values keep working; after that day they answer 422.
Responses use only the new names from this release on: if you read any of the response fields below, switch to the new name.
Renamed fields
| Where | Old name | New name |
|---|---|---|
Request field (POST /v1/fetch, /v1/batch/fetch items) |
allow_paid |
allow_premium |
Billing category and plan meter (billing_category, billing.category, billing.units[].meter, GET /v1/usage, ?category=, GET /v1/requests, invoices) |
fetch_paid |
fetch_premium |
Key quota, allowed category, quota header (X-Cormorant-Quota-Remaining), GET /v1/quota limit name, alert limit name |
fetch_paid, monthly_requests.fetch_paid |
fetch_premium, monthly_requests.fetch_premium |
| Plan meter, included allowance, alert limit name | paid_gb, included.paid_gb |
premium_gb, included.premium_gb |
Fetch attempts, GET /v1/usage (by_outcome, providers, keys), account usage |
paid_bytes, paid_bytes_ok |
premium_bytes, premium_bytes_ok |
| Fetch result on failure | paid_tiers_tried |
premium_routes_tried |
GET /v1/usage, /v1/usage/timeseries, /v1/usage/domains (JSON and CSV) |
paid_requests, paid_share |
premium_requests, premium_share |
GET /v1/requests rows and CSV column |
paid |
premium |
| Attempt reason | paid_skipped_domain_refuses |
premium_skipped_domain_refuses |
| Attempt reason | paywall_skip_paid |
paywall_skip_premium |
| Attempt reason (batch items) | deferred_before_paid |
deferred_before_premium |
Error code (also ?error= of GET /v1/requests) |
paid_tier_required |
premium_route_required * |
Status component (GET /v1/status, /v1/status/components/{id}) |
paid_tiers ("Paid tiers") |
premium_routes ("Premium routes") * |
Account, plan and invoice flag (GET /v1/account, /v1/account/invoices) |
free |
complimentary * |
| Per-request billing block | free_plan |
complimentary_plan * |
Account and invoice line kind |
free ("Free plan: 100 % discount") |
complimentary ("Complimentary plan: 100 % discount") * |
| MCP client tool parameter / environment variable | allow_paid / CORMORANT_ALLOW_PAID |
allow_premium / CORMORANT_ALLOW_PREMIUM (the old variable is still read) |
* Not a plain paid → premium swap: tiers became routes, and accounts
billed at a 100 % discount are complimentary.
Not renamed: the payment state of an invoice (status: paid, paid_at) and
the tier names themselves (direct, browser, archive, proxy,
proxy_render, the tiers field).
Extended company profiles: new names in status, usage and billing#
- Service status (
GET /v1/status, the status page): extended company profiles are the componentcompany_profiles, "Company profiles (extended)". See Monitoring. - Usage and billing name them
company_profile(billing category and meter;company_profile_cachedwhen answered from the cache) and request kindcompany_profile, with the label "Company profiles (extended)": thecompany_profileblock andkeys[].company_profile_requestsinGET /v1/usage,spend_budget.*.by_kind.company_profile, quota and alert limitsincluded.company_profile,GET /v1/requestsrows and itskindandcategoryfilters,GET /v1/accountand invoice lines. Ledger rows of these requests havedomain: null. - If you filter or alert on the previous names, switch to these before 2026-11-19; until then the previous filter values keep working.
Fetch: known hard domains, Chrome fingerprint through the residential proxy#
- The
proxytier now sends GET requests with Chrome's TLS/HTTP2 fingerprint, likedirect. - The browser tier no longer shows
HeadlessChromein its User-Agent or client hints. - A shipped list of known hard domains (
nestle.com,nestleusa.com,beyondmeat.com,apnews.com,nytimes.com,researchgate.net, …) sets the route per site: tiers to skip (hard_domain_skip), the exit country, the rendering difficulty, and Common Crawl on synchronous calls. - Errors for these sites carry
hintandalternative_urls. See Fetching → Known hard domains.
Fetch: busy domains answer fast; polite per-domain bursts#
- A synchronous
POST /v1/fetchnow waits at most 15 s for one of a busy domain's polite slots. When the expected wait is longer, it answers at once with HTTP 503,error: "domain_busy",retry_after_sandRetry-After. Batch items are unchanged: they wait in the queue. - New fetch field
domain_concurrency(up to 4) for a short burst on one site, e.g. a company's own pages. It is ignored while the site pushes back. See Limits & politeness.
Service status: counts, failures and a one-hour fallback window#
Each component now reports its counts (requests / attempts, failures)
and the window_s they cover; when the 10-minute window is short of the
minimum sample, the last hour is used. See
Monitoring → Windows and minimum samples.
Latency breakdown per request and in usage#
Fetch results gain timing (queue_ms, politeness_wait_ms,
upstream_ms) and each attempt wait_ms / upstream_ms. GET /v1/usage
adds latency_components (p50/p95 of each component) per kind, and latency
p50/p95 plus component percentiles per tier.
Caching: redirect targets and short pages#
- A fetched page is now also cached under the URL its redirects ended at,
for example after
http→httpsor bare host →www. A later request for that URL is then a hit. - Pages that are simply short are cached for a day instead of being
rendered again every time. These are pages where
directand the browser both gave less text thanmin_text_charsand nothing was blocked. - New guide: Caching and reuse across runs. It covers
the cache classes and TTLs, the cache keys, and how to use
cache_ttl_sso that a recurring job reuses earlier answers.
Fetch: no premium bytes for domains that refuse every tier#
A domain whose pages were refused by the premium routes as well (block page,
challenge, captcha) in 2 fetches in a row now skips proxy and
proxy_render for 6 h (then 24 h, 3 days): those fetches go to the archive
and alternative sources, or fail fast, without premium bytes (attempt reason
premium_skipped_domain_refuses; 5 % re-probe, one premium success ends it).
New fields: attempts[].premium_bytes, and on failures error_detail (every
attempt in one line) and premium_routes_tried. GET /v1/usage adds
by_outcome (succeeded / failed: requests, bytes, premium bytes, premium
requests) to each per-kind block.
Quota: alert e-mail deliverability#
GET /v1/quota has alert_email (status: deliverable, not_set,
partially_undeliverable or undeliverable; addresses, undeliverable,
reason bounced / complained, since). Alert e-mails go to every
address when several are set; an address that hard-bounces or reports a
mail as spam stops receiving them.
SEC EDGAR: "not an SEC filer" versus "unknown identifier"#
The SEC company endpoints and /v1/batch/sec items now say which case a
failed lookup is:
unknown_identifier: neither SEC nor Cormorant knows the ticker, CIK, name or ISIN;not_sec_filer: the company is known — a listing on a non-US exchange (NESN.SW,XSWX:NESN), the company register, GLEIF for an ISIN — but is not registered with the SEC under this identifier. The newhintcarries thecompany, its homelistingandsec_candidates: SEC filers with its name, such as the ADR of a foreign private issuer (Novo Nordisk →NVO);not_foundkeeps its meaning for filings and documents.
resolve takes ?isin= (batch: isin on resolve, submissions,
latest, facts).
Service status: providers component and per-engine search health#
/v1/statusand/statuslist a new component,providers("Third-party services"). It isdegradedwith reasonprovider_accesswhile an outside service Cormorant relies on refuses access; which one is not shown. It has no metrics.- The
searchcomponent reportsengines_healthy_sharefrom the search engines themselves, a per-engineengineslist (ok,degraded,down) andinstances_healthy_share.
What each request is charged, and spend against the caps#
- Every billable answer (search, fetch, batch items,
/v1/sec/…,/v1/companies/…) has abillingblock:chargeat your plan price in your invoice currency,within_allowance,billableand the units per plan meter, priced like the invoice. GET /v1/quotaandGET /v1/usagehavespend_budget: per key and per customer,cap(nullwhen none is set),spent,remainingandby_kind.- See Costs & billing → Capping the cost of a run.
Wikimedia: a policy-compliant User-Agent#
Fetches of Wikipedia, Wikidata and the other Wikimedia sites now identify as
CormorantBot/1.0 with a contact address, per Wikimedia's User-Agent
policy. An allowlisted user_agent you send is used instead. See
Fetching → Wikipedia, Wikidata.
Swiss company data: SOGC events and cantonal register excerpts#
The sogc and ch_cantonal_excerpt sources are available: the events
timeline from the Swiss Official Gazette of Commerce, and officers and
signatories (role, signing authority, since/until, origin or nationality,
residence) from the cantonal register excerpts. See
Switzerland.
Companies: ISIN as an identifier#
isin:works wherever an identifier does (GET /v1/companies/{id},search?identifier=,resolve, batchref), a bare ISIN too; the check digit is verified (422 invalid_identifier).resolveand batch items takeisin(also as a CSV column).- An identifier no source knows is remembered (ISIN 24 h, other schemes
1 h;
refresh=trueasks again).
SEC EDGAR usage#
SEC EDGAR requests (/v1/sec/... and fetches of SEC hosts) are metered as
kind sec in the billing categories sec and sec_cached
(billing_category, GET /v1/usage?category=, GET /v1/requests?kind=sec).
GET /v1/usage adds a sec block, keys[] adds sec_requests. For key
limits, SEC requests count as fetch.
Companies: resolve returns the register id#
A confident match from POST /v1/companies/resolve is now added to the
register and comes back with its cmp_ id, ref = that id and
in_register: true. "persist": false (batch: persist option, CSV:
?persist=false) keeps the old behaviour.
SEC EDGAR endpoints#
A new guide, SEC EDGAR via Cormorant, MCP tools
cormorant_sec_company, cormorant_sec_filings and cormorant_sec_document,
and new /v1/sec/... endpoints (see the
API contract): resolve, submissions, latest annual
report, filing documents, document fetch, XBRL facts and full-text search.
POST /v1/batch/sec queues up to 50,000 SEC operations as one job.
Companies: fast search and resolve#
resolve answers from the register when it has a confident match; search
does the same for a near-exact register hit unless live=true. Live sources
run concurrently and are waited for at most 2.5 s; slower ones finish in the
background. live_sources[] gains status and ms.
SEC EDGAR policy for fetches#
Fetches of www.sec.gov, data.sec.gov and efts.sec.gov follow SEC's
fair-access rules automatically: Cormorant sends its own declared identity,
only the direct tier is used, and one shared rate limit applies. New errors
rate_limited and sec_unavailable (both with retry_after_s),
sec_not_configured and redirect_not_allowed. /v1/status lists a sec
component.
Companies: stable legal name#
names.legal is the official national register's primary entry, whatever
the refresh order. New: names.alternates (other sources' forms with
source) and source on names.translations items.
Company data#
New product area: the company register (company/v1). See
Company data.
GET /v1/companies/search,GET /v1/companies/{id}, its history and sub-resources;POST /v1/companies/resolve; batch jobs of kindcompany(JSON and CSV).GET /v1/companies/changesand signedcompany.changedwebhooks (POST/GET/DELETE /v1/companies/watches).GET /v1/companies/sources,GET /v1/schemas/company(JSON Schema).- Metered as the billing categories
companyandcompany_cached. - Status: the group
company_datalists one component per active source. - MCP:
cormorant_company_search,cormorant_company_get,cormorant_company_resolve.
Account admin keys, usage per key#
- API keys have a role, fixed at creation and visible in the prefix:
account admin keys start with
cma_, standard keys withcmr_. Every authenticated/v1response carriesX-Cormorant-Key-Role. - Requests are attributed to the API key that made them;
GET /v1/usage?group_by=keyandGET /v1/accountshow usage per key.
Cache revalidation#
New fetch field revalidated: true when an expired cache entry was
confirmed unchanged by the origin (conditional GET answered 304). The
cached copy is returned with cached: true and no cost.
Usage, charges and keys#
GET /v1/usagewithgroup_by(key,project,category,day,hour), filters, totals, failures by error, latency, and thechargeat your plan price; CSV withAccept: text/csv.- A standard key sees only its own usage; an account admin key the whole customer account.
GET /v1/keys,GET /v1/keys/self,GET /v1/usage/timeseries,GET /v1/usage/domains,GET /v1/requests(request ledger) andGET /v1/requests/{request_id}.- Per-key limits (monthly spend cap, monthly request quotas, requests per
second, allowed categories),
GET /v1/quota, rate-limit headers and usage alerts (POST/GET/DELETE /v1/alerts, signed webhooks). - New guide Usage, spending and quotas and the MCP tool
cormorant_usage.
Charges, not provider costs#
- Changed for customer accounts:
cost_usdisnullfor keys of a customer account; what you are charged is inGET /v1/usageandGET /v1/requests(charge). - New
billing_categoryon every search and fetch result.
Dead domains, archived copies for missing pages#
- A host whose name does not exist answers
error: "domain_dead"at once for 6 hours. - With
allow_archive(the default), a fetch that ends indomain_deadorhttp_404/http_410returns a recent Wayback snapshot when one exists (served_from_archive_because).
2026-10-19#
Large PDFs in batch fetches#
Batch fetch items that leave max_bytes unset accept PDFs up to 200 MB.
New response fields pdf_page_count and text_truncated.
Service status#
GET /v1/status, /v1/status/summary, /v1/status/components/{id} and
/v1/status/history — no API key needed — and the status page at
/status; see the monitoring guide.
Article metadata#
Fetch results have a meta object for HTML pages: author,
published_at, modified_at, language, canonical_url, publisher,
site_name, description, section, word_count.
Developer portal#
Documentation at /docs: guides, an interactive API reference and the API
contract; /llms.txt and /llms-full.txt;
every guide as Markdown at /docs/<page>.md; an
MCP server for AI agents.
Accounts, plans and invoices#
GET /v1/account, GET /v1/account/invoices,
GET /v1/account/invoices/{number} and …/pdf. Keys of a suspended account
get 403 customer_suspended.
Unavailable domains, honest empty searches#
- A domain where every tier failed in 3 consecutive fetches answers
error: "domain_unavailable"for a while, withretry_after_s. - Search results carry
unresponsive_engines; a search that found nothing isresults: []witherror: null.
Rendering tier: retries and difficulty#
Busy proxy_render answers are retried once (two attempts), and the
difficulty that worked for a domain is remembered; such attempts carry
detail with the difficulty used (low, medium, high).
Faster synchronous calls under batch load#
POST /v1/search and POST /v1/fetch run on dedicated queues, so large
batch jobs no longer push synchronous calls into timeouts.
Per-request fetch controls#
New fetch fields: allow_premium, tiers, timeout_s, country,
user_agent (allowlisted) and accept_language. New fetch errors:
timeout, no_tier_allowed.
Cache hits honour the request#
A cached page is used only when it satisfies the request (render,
min_text_chars, max_bytes, tiers).
Provenance, content kinds and exact bytes#
Fetch results carry redirects, headers, origin_content_type,
content_kind, body_url and body_kind; GET /v1/bodies/{sha256}
streams the exact bytes to projects that retrieved them.
2026-10-18#
v1#
Search with SearXNG first and a fallback search API, fetch with tier
escalation (direct, browser, archive, residential proxy, rendering tier),
shared caches, batch jobs with idempotency keys and callbacks, per-project
keys, rate limits, budgets and GET /v1/usage.