# Usage, spending and quotas

Everything your API keys did, what it costs you and how much of each limit is
left — per key, project, category, day or hour, in JSON or CSV. The figures
come from the same metering and rating as your invoice, so they add up to it
to the cent.

## Who sees what

Your key's prefix tells you what it can see:

| Key | Sees |
|---|---|
| `cma_…` account admin key | every key (revoked ones included) and project of your account, usage recorded before per-key attribution (`key: null`), account-level lines, plan allowances |
| `cmr_…` standard key | only its own usage, requests, limits and alerts |

Asking about a key or project you cannot see answers `404`, exactly like one
that does not exist. Every response carries `X-Cormorant-Key-Role`, and
usage responses say what they cover in `scope`.

## The endpoints

| Endpoint | Use it for |
|---|---|
| `GET /v1/usage` | Totals and groups (`group_by=key,project,category,day,hour`) with charges |
| `GET /v1/usage/timeseries` | The same metrics per hour or day, zero-filled, for charts |
| `GET /v1/usage/domains` | Your busiest fetched domains (90 days) |
| `GET /v1/requests` | Every request, newest first, with cursor paging (90 days) |
| `GET /v1/quota` | Every limit and allowance: used, left, projected, status |
| `GET /v1/keys`, `GET /v1/keys/self` | Your keys, their roles and limits |
| `POST /v1/alerts` | A signed webhook at 50 / 80 / 100 % of a limit |

Details of every field: [API contract](/docs/api.md).

## Charges, not costs

`charge` is what you pay: your plan's prices (with your contract rates) in
your invoice currency, as a decimal string. `billable` shows, per price-plan
meter, the measured `quantity`, the `included` units it used and the
`amount`. Cormorant's own provider costs are not part of customer figures.

Base fee, minimum, contract discount and the complimentary credit belong to the
account, not to a key: an account admin key finds them per month under
`account`. On a complimentary plan you still see list-price charges, followed
by the 100 % `complimentary` line, so you know what your usage is worth.

## Fresh and final

Usage is aggregated every 5 minutes. `as_of` is the moment up to which every
request is included; a day is `final` two hours after it ended, and its
figures will not change afterwards. The request ledger is real time — a
request's `charge` appears once it has been aggregated.

## Examples

Charges per key this month, then per key and day as CSV:

```sh
curl -sS "https://api.cormorant.ch/v1/usage?group_by=key" \
  -H "Authorization: Bearer $CORMORANT_API_KEY"
curl -sS "https://api.cormorant.ch/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-october.csv
```

```python
import csv
import io
import os

import httpx

client = httpx.Client(
    base_url="https://api.cormorant.ch",
    headers={"Authorization": f"Bearer {os.environ['CORMORANT_API_KEY']}"},
)
usage = client.get("/v1/usage", params={"group_by": "key"}).json()
print(f"{usage['totals'].get('charge')} {usage['currency']} as of {usage['as_of']}")
for group in usage["groups"]:
    print(group["key"], group["key_role"], group["requests"], group.get("charge"))

# The same as CSV, e.g. for a spreadsheet or pandas.read_csv.
text = client.get(
    "/v1/usage",
    params={"group_by": "project,category", "from": "2026-10-01", "to": "2026-10-31"},
    headers={"Accept": "text/csv"},
).text
rows = list(csv.DictReader(io.StringIO(text)))
```

Every failed request of a key yesterday, page by page:

```python
params = {"key": "cmr_AbCdEf", "outcome": "failed", "from": "2026-10-18", "to": "2026-10-18"}
cursor = None
while True:
    page = client.get("/v1/requests", params={**params, "cursor": cursor} if cursor else params)
    body = page.json()
    for row in body["requests"]:
        print(row["time"], row["domain"], row["error"], row["request_id"])
    cursor = body["next_cursor"]
    if not cursor:
        break
```

Where do I stand? Each limit has `limit`, `used`, `remaining`,
`projected_end_of_period` and `status` (`ok`, `warning` from 80 %,
`exhausted`):

```python
quota = client.get("/v1/quota").json()
for key in quota["keys"]:
    for limit in key["limits"]:
        print(key["key"], limit["name"], limit["used"], "/", limit["limit"], limit["status"])
for allowance in (quota.get("customer") or {}).get("allowances", []):
    print(allowance["name"], allowance["used"], "of", allowance["limit"], "included")
```

## Limits and what happens at them

The operator can give a key a monthly spend cap, monthly request quotas
(`search`, `fetch`, `fetch_premium`, `company`), a rate limit and allowed categories — see
[Limits & politeness](/docs/limits.md#per-key-limits). Every response tells you
how much is left:

```
RateLimit-Policy: "project";q=200;w=1, "key";q=10;w=1
RateLimit: "project";r=187;t=1, "key";r=9;t=1
X-Cormorant-Key-Budget-Remaining: 12.40 USD
X-Cormorant-Quota-Remaining: fetch=18766, search=900
```

At a limit, requests answer `429 key_quota_exhausted` or
`429 key_budget_exhausted` with `Retry-After` until the month resets — do
not retry those in a loop.

### Spend against the caps: `spend_budget`

`GET /v1/quota` and `GET /v1/usage` both carry `spend_budget`: this month's
spend per key (the calling key, or every active key for an account admin
key) and, for account admin keys, for the whole customer:

```json
"spend_budget": {
  "period": "2026-10", "currency": "CHF", "basis": "charges",
  "resets_at": "2026-11-01T00:00:00+00:00",
  "keys": [{
    "key": "cmr_AbCdEf", "key_label": "research", "project": "chipiron",
    "is_self": true, "currency": "CHF",
    "cap": null, "spent": "12.34", "pending": "0.04", "remaining": null,
    "status": "no_cap",
    "by_kind": {"search": "1.20", "fetch": "11.10", "sec": "0.00", "company_profile": "0.00", "company": "0.00"}
  }],
  "customer": {"customer": "Chipiron", "currency": "CHF", "cap": null, "spent": "12.30", "pending": "0.00",
               "remaining": null, "status": "no_cap", "by_kind": {"…": "…"},
               "account_charges": "0.00", "net_estimate": "12.30"}
}
```

`cap` is the key's monthly spend cap; **`null` means no cap is set** (status
`no_cap`), it is never left out. Customers have no account-wide cap: caps are
per key. `spent` is your charges at plan price as of the last refresh plus
the per-request estimate since (`pending`); `by_kind` splits the refreshed
part by request kind. `basis` is `provider_cost` (USD) only for projects
without a customer. How to cap a run on the client side:
[Costs & billing](/docs/billing.md#capping-the-cost-of-a-run).

## Alerts

Register a webhook and get an event when a key, project or your account
crosses 50, 80 or 100 % of a limit or allowance (once per threshold and
month):

```sh
curl -sS https://api.cormorant.ch/v1/alerts \
  -H "Authorization: Bearer $CORMORANT_API_KEY" -H 'Content-Type: application/json' \
  -d '{"url": "https://hooks.example.com/cormorant", "thresholds": [80, 100]}'
```

Store the `secret` from the answer: it is shown once. Verify each delivery's
`X-Cormorant-Signature` (`t=<unix>,v1=<hex>`, the HMAC-SHA256 of
`"<t>.<raw body>"`) before trusting it — the [API contract](/docs/api.md) has a
ready-made function. The same events are listed in `GET /v1/quota`
(`events`).

## From an agent

The MCP server's `cormorant_usage` tool answers "what did I use and what is
left?" in a compact form — see [MCP server](/docs/mcp.md).
