CormorantDocs
Menu

Costs & billing

Every call is billed at your plan's price, and every response tells you exactly what it is charged. What a call costs depends on the route that answered it: standard routes (the shared cache, Cormorant's own search engines, a direct request, a headless browser, archives) or premium routes (the search fallback service, the residential network and the rendering service), which are priced higher.

What a call costs#

Action Route Billing category Control it with
Search answered by SearXNG standard search —
Search that falls back to Brave premium (fallback search service, billed on its cost) search_fallback fewer, better queries; the cache
Search answered from the cache standard search_cached —
Fetch with direct, browser or archive standard fetch_own —
Fetch with proxy premium (residential network, also per GB transferred) fetch_premium allow_premium: false
Fetch with proxy_render premium (rendering service, per successful call × the site's difficulty) fetch_premium allow_premium: false, tiers
Fetch answered from the cache standard, even if the page first came from a premium route fetch_cached —

The premium routes are used only when the standard routes were blocked — never for a page that is merely short, never for a definitive answer such as a 404. In batches, an item blocked on the standard routes is retried once after a cool-down before a premium route is tried. Your plan sets the price of each category (and any included units).

Seeing the cost#

  • Every billable answer — search, fetch, batch items, /v1/sec/…, /v1/companies/… — has a billing block: what this request is charged under your plan (see below). billing_category says which category of your plan it counts toward.
  • cost_usd is the provider spend caused by the request (cache hits and standard routes report 0) — null for keys of a customer account: customers see what they are charged, never what Cormorant pays.
  • tier and attempts show which tier answered and what was tried before.
  • GET /v1/usage sums it up per project.

The billing block#

JSON
"billing": {
  "category": "fetch_premium",
  "billable": true,
  "currency": "CHF",
  "charge": "0.014000",
  "within_allowance": false,
  "units": [
    {"meter": "fetch_premium", "unit": "fetch", "quantity": "1", "included": "0", "charge": "0.004000"},
    {"meter": "premium_gb", "unit": null, "quantity": null, "included": null, "charge": "0.010000"}
  ]
}
  • charge — this request at your plan price (decimal string, 6 places, in your invoice currency): the plan's volume tiers at your account's position in the month, its included units, contract rates and discount. "0.000000" inside the allowance.
  • within_allowance — true when every unit fell inside your plan's included units; null when nothing was priced (a failed request your plan does not bill, billable: false, or a category your plan does not charge).
  • units — per plan meter: units added, how many were included, their charge. Meters billed on provider cost show only their charge.
  • Not included: the monthly base fee, the minimum top-up and VAT (account lines of the invoice). The invoice rates the whole month at once and rounds each line to the cent, so it is authoritative; summing charge over a month matches it up to rounding.
  • Projects without a customer plan (internal use) get currency and charge null, and see cost_usd.

Keeping it bounded#

JSON
{"url": "https://www.example.com/page", "allow_premium": false}

allow_premium: false removes the premium routes from the ladder, so the fetch is always billed at a standard-route price; a site that blocks every standard route then answers all_tiers_failed. tiers gives finer control, for example ["direct", "browser", "proxy"] to allow the proxy but not the rendering tier (proxy_render).

Capping the cost of a run#

A run (a research job, an agent session) can enforce its own cost cap from the answers alone:

  1. Before the run, read GET /v1/quota → spend_budget: your key's cap, spent and remaining this month (cap: null means no cap is set on the key — ask the operator for one, see below).
  2. Keep a running total of billing.charge over the run's answers (batch items carry it too). Stop, or switch to allow_premium: false, when the total reaches the run's cap.
  3. Budget for the worst case before expensive steps: a premium-route fetch is at least the plan's fetch_premium price, a search fallback is billed on provider cost. Answers with within_allowance: true are inside your plan's included units.
Python
RUN_CAP = Decimal("5.00")  # in your invoice currency
spent = Decimal("0")
for url in urls:
    page = cormorant.post("/v1/fetch", json={"url": url, "allow_premium": spent < RUN_CAP}).json()
    spent += Decimal(page["billing"]["charge"] or "0")

A client-side cap is a convenience, not a guarantee (several runs share the key, a crash loses the total). Ask the operator to set a monthly spend cap on the key (Mission Control → Customers → Keys): Cormorant enforces it on every request and batch item (429 key_budget_exhausted), shows it in X-Cormorant-Key-Budget-Remaining and spend_budget, and can alert you at 50 / 80 / 100 % (POST /v1/alerts). Use one key per workload so each gets its own cap.

Budgets#

Each project has a monthly budget in USD for third-party providers (calendar month, UTC), set by the operator. Before every provider call Cormorant reserves the estimated cost atomically, so concurrent requests can never overspend; after the call the reservation is settled to the actual cost. When the budget is exhausted, premium routes and the Brave fallback are skipped (budget_exhausted in attempts or in a search's error) while the standard routes keep working.

Usage: GET /v1/usage#

Usage of the caller's scope and what it costs. Inclusive dates (UTC), default: current month to date, at most 400 days. Read from aggregates refreshed every 5 minutes, never from the request log, so it answers in the same time at any volume.

Who sees what. A standard key (cmr_…) sees only its own requests. An account admin key (cma_…) sees every key and project of its customer — revoked keys included, and usage recorded before per-key attribution as "key": null. A project without a customer is its own account. Nothing ever crosses a customer boundary; a key or project you cannot see is 404, like one that does not exist.

Parameters

Parameter Meaning
from, to Range of UTC days (inclusive).
group_by Comma-separated, combinable: key, project, category, day, hour (hourly data: last 35 days).
key Only these keys (comma-separated prefixes, e.g. cmr_AbCdEf).
project Only these projects (comma-separated names).
category Only these billing categories: search, search_fallback, search_cached, fetch_own, fetch_premium, fetch_cached, company, company_cached, sec, sec_cached, company_profile, company_profile_cached.
format csv (same as Accept: text/csv).
JSON
{
  "project": "acme-crawler", "key": "cma_XyZaBc", "role": "account_admin",
  "scope": {"customer": "Acme", "projects": ["acme-crawler", "acme-search"], "keys": "all"},
  "from": "2026-10-01", "to": "2026-10-19",
  "as_of": "2026-10-19T11:05:02+00:00", "final": false, "currency": "USD",
  "totals": {"requests": 9120, "succeeded": 8800, "failed": 320, "success_rate": 0.9649,
             "cached": 1200, "cache_hit_rate": 0.1316,
             "errors": {"http_404": 210, "all_tiers_failed": 110},
             "latency_ms_p50": 900, "latency_ms_p95": 7400, "bytes": 812345678,
             "premium_requests": 90, "premium_share": 0.0113, "charge": "15.96",
             "billable": {"fetch_own": {"quantity": "8710", "included": "1000",
                                        "billable": "7710", "amount": "15.42"},
                          "fetch_premium": {"quantity": "90", "included": "0",
                                         "billable": "90", "amount": "0.36"},
                          "premium_gb": {"quantity": null, "included": null,
                                      "billable": null, "amount": "0.18"}}},
  "search": {"requests": 1200, "cached": 300, "cache_hit_rate": 0.25, "succeeded": 1198,
             "success_rate": 0.998, "bytes": 0, "premium_bytes": 0,
             "latency_ms_p50": 1100, "latency_ms_p95": 3900, "fallbacks": 60,
             "fallback_rate": 0.0667, "providers": {"searxng": {"requests": 840, ...}, "brave": {...}}},
  "fetch": {"requests": 7920, "cached": 900, "tiers": {"direct": {...}, "proxy": {...}},
            "errors": {"http_404": 210},
            "by_outcome": {"succeeded": {"requests": 7600, "bytes": 800000000,
                                         "premium_bytes": 8800000, "premium_requests": 80},
                           "failed": {"requests": 320, "bytes": 0,
                                      "premium_bytes": 323456, "premium_requests": 10}}, ...},
  "company_profile": {},  // extended company profiles (kind company_profile), same fields; {} when none
  "group_by": ["key"],
  "groups": [{"key": "cmr_AbCdEf", "key_label": "crawler", "key_role": "standard",
              "key_status": "active", "project": "acme-crawler", "requests": 9000, ...,
              "charge": "15.70", "billable": {...}}],
  "keys": [{"prefix": "cmr_AbCdEf", "label": "crawler", "role": "standard",
            "project": "acme-crawler", "active": true, "requests": 9000, "cached": 1150,
            "succeeded": 8690, "search_requests": 1100, "fetch_requests": 7900, "company_requests": 0,
            "company_profile_requests": 0,
            "premium_bytes": 9123456, "charge": "15.70"}],
  "account": [{"period": "2026-10", "from": "2026-10-01", "to": "2026-10-19", "currency": "USD",
               "complimentary": true, "usage_charges": "15.96", "account_level_amount": "-15.96",
               "account_lines": [{"kind": "complimentary", "description": "Complimentary plan: 100 % discount",
                                  "amount": "-15.96"}],
               "list_total": "15.96", "net_total": "0.00"}]
}
  • Metrics (in totals and every group): requests (cache hits included), succeeded, failed, cached, errors (failures by error code, top 50), latency_ms_p50/latency_ms_p95 of uncached requests (estimated from a latency histogram with bounds 100, 250, 500 ms, 1, 2, 5, 10, 20, 30, 60 and 120 s — exact to the bucket), bytes, premium_requests and premium_share (fetches that needed the premium routes, of all uncached fetches).
  • Charges (projects with a customer): charge is what the group costs at your plan's prices in your invoice currency, as a decimal string; billable lists each price-plan meter with the measured quantity, the included units it used (its pro-rata share of the plan's included units of the month), billable and amount. Meters billed on provider cost show only the amount. The period is rated exactly like the invoice and each usage line is split to the cent, first per key, then per day and category: any grouping adds up to the same total, and the keys add up to the usage lines of the estimate (GET /v1/account) or invoice. Base fee, minimum, contract discount and the complimentary credit are account-level lines: account admin keys see them per period in account. Complimentary accounts see list-price charges and the 100 % complimentary line.
  • Provider cost — cost_usd and the project budget (Cormorant's spend with its search and proxy providers) appear only for projects without an external customer (internal use). Customers see what they pay, never what Cormorant pays.
  • Freshness: as_of is the time up to which every request is included; final: true (top level, and per group with group_by=day) means the days are closed and will not change (a day becomes final two hours after it ends).
  • search and fetch keep the per-kind blocks of earlier versions (now for the caller's scope), with providers/tiers breakdowns. With key in group_by, keys keeps the earlier per-key list as well.
  • Latency components: every per-kind block has latency_components (queue_ms, politeness_wait_ms, upstream_ms, each {p50, p95} of uncached requests), and each entry of tiers/providers adds latency_ms_p50/p95 and <component>_p50/_p95, so a slow tier can be told apart from a busy domain or a queue backlog. Estimated from histograms like the other percentiles; requests from before these were recorded are not included.
  • by_outcome (in every per-kind block) splits the same requests into succeeded and failed, each with requests, bytes, premium_bytes, premium_requests (fetch) and cost_usd (provider cost, internal projects only): fetch.by_outcome.failed.premium_bytes is what blocked pages cost before the fetch gave up. Whether failed requests are billed depends on your plan (billable).

CSV. Accept: text/csv (or format=csv) returns one row per group — or one row of totals without group_by — with the dimensions, the metrics, charge, the meters as <meter>_quantity, <meter>_included, <meter>_billable, <meter>_amount and errors as code=n;code=n.

curl
curl -sS "$CORMORANT_URL/v1/usage?from=2026-10-01&to=2026-10-31&group_by=key,day" \
  -H "Authorization: Bearer $CORMORANT_API_KEY" -H "Accept: text/csv" -o usage.csv
curl
curl -sS "https://api.cormorant.ch/v1/usage?from=2026-10-01&to=2026-10-19" \
  -H "Authorization: Bearer $CORMORANT_API_KEY"
Python
usage = client.get("/v1/usage", params={"from": "2026-10-01", "to": "2026-10-19"}).json()
if usage["currency"]:  # a customer account: charges at your plan price
    print(f"charges {usage['totals']['charge']} {usage['currency']} (as of {usage['as_of']})")
else:  # a project without a customer: provider spend and budget
    print(f"spent ${usage['cost_usd']:.2f}, budget left ${usage['budget']['remaining_usd']:.2f}")
print("fetch cache hit rate:", usage["fetch"].get("cache_hit_rate"))
TypeScript
const usage = await (
  await fetch(`${BASE}/v1/usage?from=2026-10-01&to=2026-10-19`, { headers })
).json();
console.log(usage.currency ? `${usage.totals.charge} ${usage.currency}` : `$${usage.cost_usd}`);

Your account, plan and invoices#

Two different things are measured:

  • Provider spend (cost_usd on results, budgets) — what Cormorant spent on its search and proxy providers because of your requests. GET /v1/usage shows it only for projects without a customer account.
  • What you are billed — a project can belong to a customer account with a price plan. Every served request falls into a billing category (search, search_fallback, search_cached, fetch_own, fetch_premium, fetch_cached, company, company_cached, sec, sec_cached, company_profile, company_profile_cached), is metered hourly and priced monthly by the plan (included units, volume tiers, pass-through of provider cost). Complimentary accounts get the same invoice at list price with a 100 % discount line, so you can see what your usage is worth.

Keys belong to a project; a project may belong to a customer (the billed account). These endpoints show the key's own project usage plus the plan, current-period estimate and issued invoices of that customer — never another customer's data, never drafts. Money is a decimal string in the invoice currency (USD, CHF or EUR). Usage is aggregated every 5 minutes (usage_refreshed_at). See billing.md for pricing rules.

GET /v1/account#

JSON
{
  "project": {"id": 7, "name": "acme-crawler"},
  "period": {"period": "2026-10", "from": "2026-10-01", "to": "2026-10-31"},
  "usage_refreshed_at": "2026-10-19T11:00:04+00:00",
  "project_usage": {"fetch_own": {"requests": 9120, "succeeded": 8800, "bytes": 812345678,
                    "bytes_ok": 800000000, "premium_bytes": 0, "premium_bytes_ok": 0,
                    "cost_usd": "0", "cost_ok_usd": "0"}, "search": {...}},
  // cost_usd / cost_ok_usd: null for projects of an external customer
  "customer": {"name": "Acme", "company_name": "Acme Research AG", "currency": "USD",
               "status": "active", "complimentary": true},
  "customer_usage": {...},
  "plan": {"id": 1, "code": "standard-usd", "version": 1, "name": "Standard (USD)",
           "currency": "USD", "complimentary": false},
  "estimate": {"period": "2026-10", "currency": "USD",
               "lines": [{"kind": "usage", "component": "fetch_own",
                          "description": "Page fetches (1,000 included)", "quantity": "8800",
                          "unit": "fetch", "unit_price": null, "amount": "15.60",
                          "detail": {"included": "1000", "billable": "7800",
                                     "tiers": [{"from": "0", "to": "100000", "quantity": "7800",
                                                "unit_price": "0.002", "amount": "15.60"}]}},
                         {"kind": "complimentary", "description": "Complimentary plan: 100 % discount", "amount": "-15.60", ...}],
               "subtotal": "15.60", "discount_total": "15.60", "net_total": "0.00",
               "vat_rate": "8.1", "vat_amount": "0.00", "total": "0.00", "complimentary": true,
               "list_total": "15.60"}
}

keys breaks the period down per API key. An account admin key (cma_…) sees every key of the customer's projects (or of this project without a customer); an additional key (cmr_…) sees only itself. Keys appear by prefix, label and role, never ids or secrets, with its usage by category and amount, its share of the usage charges at list price. Each usage line of the estimate is split between keys in proportion to their share of that meter (to the cent), so the amounts add up to the usage lines; base fee, minimum, discounts and the complimentary credit stay on the account. A row with "prefix": null is usage recorded before per-key attribution.

JSON
"keys": [{"prefix": "cma_XyZaBc", "label": "billing", "role": "account_admin", "project": "acme-crawler",
          "active": true, "requests": 9120, "succeeded": 8800,
          "usage": {"fetch_own": {"requests": 9120, ...}}, "amount": "15.60"}]

Projects without a customer get "customer": null, "plan": null, "estimate": null (and amount: null per key). Categories: search, search_fallback, search_cached, fetch_own, fetch_premium, fetch_cached, company, company_cached, sec, sec_cached, company_profile, company_profile_cached.

GET /v1/account/invoices#

{"invoices": [{"number": "CORM-2026-0001", "status": "issued|paid|void", "period": "2026-09", "period_start", "period_end", "currency", "plan_label", "complimentary", "subtotal", "discount_total", "net_total", "vat_rate", "vat_amount", "total", "vat_note", "issued_at", "due_at", "paid_at", "voided_at", "pdf_url": "/v1/account/invoices/CORM-2026-0001/pdf"}]}

GET /v1/account/invoices/{number} and /pdf#

The invoice with its lines (and tier breakdown), or the PDF. Another customer's number (or a draft) is 404.

curl
curl -sS https://api.cormorant.ch/v1/account \
  -H "Authorization: Bearer $CORMORANT_API_KEY"
curl -sS https://api.cormorant.ch/v1/account/invoices/CORM-2026-0001/pdf \
  -H "Authorization: Bearer $CORMORANT_API_KEY" -o CORM-2026-0001.pdf
Python
account = client.get("/v1/account").json()
estimate = account["estimate"]
if estimate:
    print(f"{estimate['period']}: {estimate['total']} {estimate['currency']}")
for invoice in client.get("/v1/account/invoices").json()["invoices"]:
    print(invoice["number"], invoice["status"], invoice["total"], invoice["currency"])
TypeScript
const account = await (await fetch(`${BASE}/v1/account`, { headers })).json();
if (account.estimate) console.log(account.estimate.period, account.estimate.total);
const { invoices } = await (await fetch(`${BASE}/v1/account/invoices`, { headers })).json();
for (const invoice of invoices) console.log(invoice.number, invoice.status, invoice.total);

Amounts are decimal strings ("15.60"): parse them with a decimal type, not a float. If the account is suspended, every /v1 request answers 403 customer_suspended.

Attributing spend#

Provider spend is attributed only to the project whose request made the provider call; the caches are shared, so a page another project fetched through a premium route is a cache hit for you. Within an account, every request is also attributed to the API key that made it (batch items to the key that created the job): GET /v1/usage?group_by=key splits usage and charges per key, and keys in GET /v1/account shows each key's usage and share of the month's charges. With the account admin key (cma_…) you see every key of the account; an additional key (cmr_…) sees only itself. Give each client or environment its own key (ask the operator for a labelled key) to see who uses what.

Python
for group in client.get("/v1/usage", params={"group_by": "key"}).json()["groups"]:
    print(group["key"], group["key_role"], group["requests"], group.get("charge"))
``` Set
`project_ref` on requests to tag usage with your own identifiers (a customer,
a company id, a run).

View as Markdown