# 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 free-plan credit belong to the
account, not to a key: an account admin key finds them per month under
`account`. On a free plan you still see list-price charges, followed by the
100 % `free` 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_paid`), 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.

## 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).
