# Zeldoc Platform API

> Read your organization's usage, plan cost and invoices from the Zeldoc platform. This is the same data the dashboard shows. Guide: https://docs.zeldoc.ai/reporting-api

- Base URL: https://platform.zeldoc.ai
- OpenAPI spec (the same content as JSON): https://platform.zeldoc.ai/api/openapi.json

## Authentication

Send a **reporting token** as a bearer token:

```
Authorization: Bearer zdt_...
```

An organization admin creates reporting tokens in the dashboard
(Organization → API tokens), where the organization id (`org_id`) every
path takes is shown too. A token is read-only and belongs to one
organization: it opens the endpoints listed here and nothing else, so it
cannot create keys, invite users or mint further tokens.

```bash
curl -s "https://platform.zeldoc.ai/api/orgs/$ORG_ID/key-usage?month=2026-08" \
  -H "Authorization: Bearer $ZELDOC_TOKEN" \
  -H "User-Agent: my-company-usage-sync/1.0"
```

## User-Agent

Send an explicit, descriptive `User-Agent` header, for example
`my-company-usage-sync/1.0`. The platform sits behind Cloudflare, which
rejects the default user agent of some HTTP libraries (Python's `urllib`, for
one) with error 1010.

## Money and tokens

- Amounts are decimal **strings** (`"12.3456"`) so no precision is lost; parse
  them as decimals, not floats.
- `prompt_tokens` is the **whole** input, cache reads and writes included.
  `cache_read_input_tokens` and `cache_creation_input_tokens` are subsets of
  it, not additions. The four non-overlapping buckets that provider invoices
  use are:

  ```
  uncached input = prompt_tokens - cache_read_input_tokens - cache_creation_input_tokens
  cache write    = cache_creation_input_tokens
  cache read     = cache_read_input_tokens
  output         = completion_tokens
  ```

- Models Zeldoc hosts itself (`zeldoc_hosted: true`) are covered by the
  subscription: zero spend, but their tokens and requests count.

## Visibility

What an organization sees of its money is agreed per customer. When usage,
the subscription or invoices are not shared with your organization, the
matching endpoints answer 403; contact Zeldoc.

## Partners

A partner runs an app on Zeldoc for other organizations, for example a hosted
chat workspace with one key per customer. A partner's people create a
**partner token** in the dashboard (Partner → API tokens); it opens
`GET /api/partner/status` and nothing else. That one call lists every
organization the partner serves and each key it runs there, compared with
the partner's recommended models, so a scheduled job can tell when a key
needs a model added.

## Endpoints

### GET /api/orgs/{org_id}/invoices: List invoices.

The invoices issued to the organization, newest first.

Auth: `Authorization: Bearer <reporting token>`

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `org_id` | path | string | yes | Your organization id. |

Responses:

- **200**: Invoices → `InvoiceSummaryDTO`[]
- **401**: Missing, expired or revoked token
- **403**: Another organization's id, or invoices are not shared with your organization
- **409**: The organization's setup needs attention on Zeldoc's side; contact Zeldoc

### GET /api/orgs/{org_id}/invoices/{invoice_id}: Get one invoice.

Auth: `Authorization: Bearer <reporting token>`

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `org_id` | path | string | yes | Your organization id. |
| `invoice_id` | path | string (uuid) | yes | Invoice id |

Responses:

- **200**: Invoice → `InvoiceDTO`
- **401**: Missing, expired or revoked token
- **403**: Another organization's id, or invoices are not shared with your organization
- **404**: Not found, or the invoice belongs to another organization
- **409**: The organization's setup needs attention on Zeldoc's side; contact Zeldoc

### GET /api/orgs/{org_id}/invoices/{invoice_id}/pdf: Download an invoice as PDF.

Auth: `Authorization: Bearer <reporting token>`

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `org_id` | path | string | yes | Your organization id. |
| `invoice_id` | path | string (uuid) | yes | Invoice id |

Responses:

- **200**: Invoice PDF → `application/pdf`
- **401**: Missing, expired or revoked token
- **403**: Another organization's id, or invoices are not shared with your organization
- **404**: Not found, or the invoice belongs to another organization
- **409**: The organization's setup needs attention on Zeldoc's side; contact Zeldoc

### GET /api/orgs/{org_id}/key-usage: Usage per API key.

Spend, tokens and requests for every API key of the organization over a
period, highest spend first, each split by the models it called; plus
totals by model, by provider and by each of the organization's key fields.
A key is usually held by one person, so per key is per person.

Pick at most one period style: `month`, `year`, `months`, or `start_date`
with `end_date`. Without any: the current month to date. Dates are UTC
calendar days, the end is clamped to today, and the response echoes the
range it covered. A range spans at most 366 days; backfill longer periods
as successive ranges. For a daily sync, ask for yesterday as
`start_date=end_date=YYYY-MM-DD`.

Spend is USD only: a range can span months with different exchange rates.
Use the monthly usage endpoint for DKK.

Auth: `Authorization: Bearer <reporting token>`

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `org_id` | path | string | yes | Your organization id. |
| `month` | query | string | no | A single calendar month, `YYYY-MM`. |
| `year` | query | integer (int32) | no | A calendar year (Jan..Dec), clamped so it never runs past today. |
| `months` | query | integer (int32) | no | Trailing calendar months ending with the current one. |
| `start_date` | query | string (date) | no | First day to include, `YYYY-MM-DD`. Requires `end_date`. |
| `end_date` | query | string (date) | no | Last day to include, `YYYY-MM-DD`. Requires `start_date`. |

Responses:

- **200**: Per-key and total usage for the period → `OrgKeyUsageDTO`
- **400**: Conflicting or malformed period parameters, or a range over 366 days; the body says which
- **401**: Missing, expired or revoked token
- **403**: Another organization's id, or usage is not shared with your organization
- **409**: The organization's setup needs attention on Zeldoc's side; contact Zeldoc

### GET /api/orgs/{org_id}/monthly-usage: Usage per calendar month.

Spend in USD and DKK plus token counts, one entry per month, oldest first.
DKK uses the month's stored exchange rate; for the current month before
its rate is stored, the most recent rate is used and `rate_estimated` is
true. Without parameters: the trailing 12 months.

Auth: `Authorization: Bearer <reporting token>`

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `org_id` | path | string | yes | Your organization id. |
| `months` | query | integer (int32) | no | Trailing calendar months to include, the current one included (default 12, max 60). Mutually exclusive with `year`. |
| `year` | query | integer (int32) | no | One calendar year (January to December). Mutually exclusive with `months`. |

Responses:

- **200**: Monthly usage for the organization → `OrgMonthlyUsageDTO`
- **400**: Both `months` and `year` were supplied
- **401**: Missing, expired or revoked token
- **403**: Another organization's id, or usage is not shared with your organization

### GET /api/orgs/{org_id}/plan-cost: Monthly plan cost.

What the organization's subscription costs per month, line by line, in
the requested currency. Token usage is not included; see the usage
endpoints.

Auth: `Authorization: Bearer <reporting token>`

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `org_id` | path | string | yes | Your organization id. |
| `currency` | query | `Currency` | no | Currency to quote the plan in (DKK, EUR or USD). Defaults to DKK. |

Responses:

- **200**: The monthly plan cost → `OrgPlanCostDTO`
- **401**: Missing, expired or revoked token
- **403**: Another organization's id, or the subscription is not shared with your organization

### GET /api/partner/status: Every organization you serve and the keys you run there, each compared with your recommended models.

A key is up to date when `has_recommended` is true. `missing_recommended`
lists what to add (with `POST .../recommended` in the dashboard, or by
editing the key's models), `not_allowed` what the organization may not use
(contact Zeldoc), and `extra` what the key has beyond the list.

Auth: `Authorization: Bearer <reporting token>`

Responses:

- **200**: Every customer and the keys the partner runs there, each checked against the recommended models → `PartnerStatusDTO`
- **401**: Unauthorized
- **403**: Not a partner

## Types

The response bodies above, field by field. Money amounts are decimal strings.

### ApiKeyIdDTO

Type: string

### Currency

ISO 4217 currency code a price can be quoted in. Every price holds
a hand-set amount in each of these; nothing here is derived from an
exchange rate.

Type: `"DKK"` | `"EUR"` | `"USD"`

### DailyUsageEntryDTO

Usage on one calendar day (UTC), within a month of the monthly usage.
Spend is in USD only; convert it with the month's rate
(`dkk_spend / usd_spend` of the month) if you need DKK.

| Field | Type | Description |
|---|---|---|
| `cache_creation_input_tokens` | integer (int64) | Prompt tokens written into the provider's prompt cache; a subset of `prompt_tokens`. |
| `cache_read_input_tokens` | integer (int64) | Prompt tokens served from the provider's prompt cache; a subset of `prompt_tokens`. |
| `completion_tokens` | integer (int64) |  |
| `date` | string (date) |  |
| `prompt_tokens` | integer (int64) |  |
| `request_count` | integer (int64) |  |
| `total_tokens` | integer (int64) |  |
| `usd_spend` | string |  |

### EmailAddressDTO

An e-mail address, trimmed and lower-cased.

Type: string (email)

### InvoiceBuyerDTO

Frozen buyer ("Bill to") details as they appeared at issue time.

| Field | Type | Description |
|---|---|---|
| `address_line1` | string |  |
| `address_line2` | string |  |
| `attention` | string |  |
| `city` | string |  |
| `country` | string |  |
| `cvr` | string |  |
| `ean` | string |  |
| `email` | string (email) \| null |  |
| `legal_name` | string |  |
| `payment_reference` | string |  |
| `postal_code` | string |  |
| `vat` | string |  |

### InvoiceDTO

A full, immutable invoice. All money fields are integer øre.

| Field | Type | Description |
|---|---|---|
| `buyer` | `InvoiceBuyerDTO` |  |
| `currency` | `Currency` |  |
| `due_at` | string (date) |  |
| `id` | string (uuid) |  |
| `invoice_number` | string |  |
| `issued_at` | string (date-time) |  |
| `language` | `InvoiceLanguage` |  |
| `lines` | `InvoiceLineDTO`[] |  |
| `org_id` | string |  |
| `period_end` | string (date) |  |
| `period_start` | string (date) |  |
| `seller` | `InvoiceSellerDTO` |  |
| `status` | `InvoiceStatus` |  |
| `subscription_subtotal_cents` | integer (int64) |  |
| `subtotal_cents` | integer (int64) |  |
| `total_cents` | integer (int64) |  |
| `usage_subtotal_cents` | integer (int64) |  |
| `usd_dkk_rate` | string \| null |  |
| `vat_cents` | integer (int64) |  |
| `vat_rate_bps` | integer (int32) |  |

### InvoiceLanguage

The language an invoice is written in, frozen when it is issued.

Type: `"da"` | `"en"`

### InvoiceLineDTO

A single line on an invoice. Money is integer øre.

| Field | Type | Description |
|---|---|---|
| `description` | string |  |
| `kind` | `InvoiceLineKind` | What the line bills. `usage_provider` lines are informational: an "of which <provider>" split of the single `usage` line directly above them, summing exactly to it. They are not charged — totals are computed from plan + usage, not from lines. |
| `line_total_cents` | integer (int64) |  |
| `quantity` | integer (int32) |  |
| `sort_order` | integer (int32) |  |
| `unit_price_cents` | integer (int64) |  |

### InvoiceLineKind

What an invoice line bills: the plan, the month's usage (one line, or
one per upstream provider), or a developer's ZDev overage.

Type: `"subscription"` | `"usage"` | `"usage_provider"` | `"zdev_overage"`

### InvoiceSellerDTO

Frozen seller identity as it appeared on the invoice at issue time.

| Field | Type | Description |
|---|---|---|
| `address` | string |  |
| `bank` | string |  |
| `cvr` | string |  |
| `email` | string (email) |  |
| `legal_name` | string |  |
| `vat` | string |  |

### InvoiceStatus

Where an invoice stands: issued, or voided (kept, never deleted, and
stamped VOID on its PDF).

Type: `"issued"` | `"void"`

### InvoiceSummaryDTO

Compact invoice for list views.

| Field | Type | Description |
|---|---|---|
| `currency` | `Currency` |  |
| `due_at` | string (date) |  |
| `id` | string (uuid) |  |
| `invoice_number` | string |  |
| `issued_at` | string (date-time) |  |
| `period_start` | string (date) |  |
| `status` | `InvoiceStatus` |  |
| `total_cents` | integer (int64) |  |

### KeyFieldDTO

One field of an organization's key schema: every API key of the
organization can carry a value for it. Lists of these are in display
order.

| Field | Type | Description |
|---|---|---|
| `default_value` | null \| `KeyFieldValueDTO` | The value filled in for this field when a key is created in the dashboard, which the person creating it may change: a boolean, a text or an option's key, like any value of the field. `null` when the field has none. A key created through the API without a value for the field does not get it. |
| `display_name` | string | What the dashboard shows, e.g. "Team name". |
| `key` | `KeyFieldKeyDTO` | Stable identifier the values are stored under, e.g. `team`. |
| `options` | `KeyFieldOptionDTO`[] | The choices of a `select` field, in the order to show them; empty otherwise. |
| `required` | boolean | A key cannot be created, nor its fields saved, without a value here. |
| `sort_options_alphabetically` | boolean | A `select` field's choices are sorted by label (ignoring case) when true, the default; when false they keep the order they were given in. |
| `type` | `KeyFieldType` |  |

### KeyFieldKeyDTO

The stable identifier of an organization's key field, such as `team` or
`is_private`. It names the value on every key, so it never changes; the
display name next to it may.

Type: string

### KeyFieldOptionDTO

One choice of a select key field, e.g. `{"key": "backend", "label":
"Backend"}`.

| Field | Type | Description |
|---|---|---|
| `key` | `KeyFieldOptionKeyDTO` | Stable identifier stored on the keys that pick this option. |
| `label` | string | What the dashboard shows. |

### KeyFieldOptionKeyDTO

The stable identifier of one option of a select key field, such as
`backend`. A key stores this, not the label, so renaming the option's
label renames it on every key at once.

Type: string

### KeyFieldType

What a key field holds, and so which input the key editor shows.

Type: `"text"` | `"boolean"` | `"select"`

### KeyFieldValueDTO

One key field value on a key: a JSON boolean for a boolean field, a string
for a text field, and the chosen option's key for a select field.

Type: boolean | string

### KeyProduct

What an API key is for. Set when Zeldoc creates the key; a service key
can also be marked or unmarked later. Ordinary keys have none.

Type: `"zconnect"` | `"zdev"` | `"service"`

### ModelProduct

The product a gateway model is sold under, as named on zeldoc.ai. A model
with no product is offered to nobody. An organization's allow-list is the
models of the products on its settings page, plus whatever its team was
granted directly in the gateway.

Type: `"zcore"` | `"zdev"` | `"zrouter"`

### MonthlyUsageEntryDTO

Usage for a single calendar month for one organization.

`month` is the first day of the month. `dkk_spend` is null when no USD to
DKK rate is available for that month; the USD figure is always there.

For the current (in-progress) month with no stored rate yet, `dkk_spend` is
estimated from the most recent stored rate and `rate_estimated` is true.
Earlier months with no stored rate keep `dkk_spend` null.

| Field | Type | Description |
|---|---|---|
| `cache_creation_input_tokens` | integer (int64) | Prompt tokens written into the provider's prompt cache. Also a subset of `prompt_tokens`; billed at a premium over the input rate. |
| `cache_read_input_tokens` | integer (int64) | Prompt tokens served from the provider's prompt cache. A subset of `prompt_tokens`, not an addition to it; billed at a fraction of the input rate, which is why a cache-heavy key can carry many tokens for little spend. |
| `completion_tokens` | integer (int64) |  |
| `days` | `DailyUsageEntryDTO`[] | The month day by day: one entry per calendar day (UTC), oldest first, up to today for the current month; days without usage are zero. The days add up to the month's figures. |
| `dkk_spend` | string \| null |  |
| `month` | string (date) | First day of the month this entry covers. |
| `prompt_tokens` | integer (int64) | Input and output split of `total_tokens`, the two halves that are priced differently. |
| `rate_estimated` | boolean | True when `dkk_spend` was derived from a fallback rate (current month before its real rate is stored), false when from a stored rate or when there is no DKK value. |
| `request_count` | integer (int64) |  |
| `total_tokens` | integer (int64) |  |
| `usd_spend` | string |  |

### OrgKeyFieldGroupUsageDTO

The usage of every key holding one value of a key field, e.g. all keys
whose team is Backend. The groups of a field add up to the report's
totals.

| Field | Type | Description |
|---|---|---|
| `cache_creation_input_tokens` | integer (int64) | A subset of `prompt_tokens`, as on the key entries. |
| `cache_read_input_tokens` | integer (int64) | A subset of `prompt_tokens`, as on the key entries. |
| `completion_tokens` | integer (int64) |  |
| `external` | `OrgUsageSliceDTO` | Traffic of these keys to every other model. |
| `key_count` | integer (int64) | How many of the report's keys hold this value; zero for an option or a yes/no nobody picked, listed so every choice shows. |
| `label` | string \| null | The value for display: the option's label, the text itself, or "Yes" / "No". Null with `value`. |
| `prompt_tokens` | integer (int64) |  |
| `request_count` | integer (int64) |  |
| `spend_usd` | string |  |
| `total_tokens` | integer (int64) |  |
| `value` | null \| `KeyFieldValueDTO` | The value the keys hold, as on `fields`: the text, true or false, or the option's key. Null for the keys with no value, which includes keys deleted since they spent. |
| `zeldoc` | `OrgUsageSliceDTO` | Traffic of these keys to Zeldoc-hosted models. |

### OrgKeyFieldUsageDTO

The report's usage grouped by one key field: spend per team, private
versus company keys, and so on.

A select field lists every option in the schema's order and a boolean
true then false, each even when no key holds it; a text field lists the
texts its keys hold, highest spend first. The group of keys with no value
comes last, and only when there are such keys. The groups add up to the
report's totals. Keys are grouped by the values they hold now, not when
the usage happened.

| Field | Type | Description |
|---|---|---|
| `display_name` | string |  |
| `groups` | `OrgKeyFieldGroupUsageDTO`[] |  |
| `key` | `KeyFieldKeyDTO` | The field's stable key, as on each entry's `fields`. |
| `type` | `KeyFieldType` |  |

### OrgKeyModelUsageDTO

Usage one API key drove on a single model over the reported period.
Spend is in USD.

| Field | Type | Description |
|---|---|---|
| `cache_creation_input_tokens` | integer (int64) | Prompt tokens written into the provider's prompt cache. Also a subset of `prompt_tokens`; billed at a premium over the input rate. |
| `cache_read_input_tokens` | integer (int64) | Prompt tokens served from the provider's prompt cache. A subset of `prompt_tokens`, not an addition to it; billed at a fraction of the input rate, which is why a cache-heavy key can carry many tokens for little spend. |
| `completion_tokens` | integer (int64) |  |
| `model` | string | The model as named in the request, e.g. "zeldoc/zdev" or "claude-opus-4-8". |
| `prompt_tokens` | integer (int64) |  |
| `request_count` | integer (int64) |  |
| `spend_usd` | string |  |
| `total_tokens` | integer (int64) |  |
| `zeldoc_hosted` | boolean | True for a model Zeldoc hosts itself. Covered by the subscription, so its spend is zero; it still counts tokens and requests, and rolls into the entry's `zeldoc` slice. |

### OrgKeyUsageDTO

Per-key spend and model usage for one organization over a date range.

`start_date` and `end_date` echo back the resolved, inclusive range that was
actually queried, so a caller polling this on a schedule can record exactly
which days a response covers rather than re-deriving it from its own request.

All spend is USD. Unlike the monthly usage endpoint there is no DKK figure:
the stored USD->DKK rates are per calendar month, and an arbitrary date range
can span several months, so a single converted total would silently blend
rates. Convert per month via `/api/orgs/{org_id}/monthly-usage` when DKK is
needed.

| Field | Type | Description |
|---|---|---|
| `end_date` | string (date) | Last day covered, inclusive. |
| `key_fields` | `KeyFieldDTO`[] | The organization's key fields in display order: the labels for each entry's `fields`, so the report reads on its own. |
| `keys` | `OrgKeyUsageEntryDTO`[] |  |
| `org_id` | string | Organization id. |
| `start_date` | string (date) | First day covered, inclusive. |
| `totals` | `OrgKeyUsageTotalsDTO` |  |

### OrgKeyUsageEntryDTO

One API key's usage over the reported period, split by the models it called.

`models` is empty for a key with no recorded activity in the period. The
per-model figures can sum to slightly less than the key total when the
gateway recorded usage without attributing it to a model.

`zeldoc` and `external` split the key's tokens and requests by where they
went: Zeldoc's own self-hosted models versus third-party providers. They
always add up to the key's figures; usage attributed to no model at all is
counted as external.

A key deleted since it spent is still reported, named "(deleted key)" or
by its last alias, without `fields`, `product` or `recipient_email`.

| Field | Type | Description |
|---|---|---|
| `cache_creation_input_tokens` | integer (int64) | Prompt tokens written into the provider's prompt cache. Also a subset of `prompt_tokens`; billed at a premium over the input rate. |
| `cache_read_input_tokens` | integer (int64) | Prompt tokens served from the provider's prompt cache. A subset of `prompt_tokens`, not an addition to it; billed at a fraction of the input rate, which is why a cache-heavy key can carry many tokens for little spend. |
| `completion_tokens` | integer (int64) |  |
| `external` | `OrgUsageSliceDTO` | Traffic to every other model: what left for third-party providers. |
| `fields` | map of string → `KeyFieldValueDTO` | The key's key field values by field key, for the fields the organization has now (`key_fields` on the report). Empty for a key deleted since it spent. These are the key's values today, not when the usage happened. |
| `id` | `ApiKeyIdDTO` |  |
| `key_prefix` | string |  |
| `models` | `OrgKeyModelUsageDTO`[] |  |
| `name` | string |  |
| `product` | null \| `KeyProduct` | `zconnect` for a ZConnect key, `zdev` for a ZDev key, null for an ordinary key or one deleted since it spent. |
| `prompt_tokens` | integer (int64) |  |
| `recipient_email` | null \| `EmailAddressDTO` | Who the key was sent to, for a key handed out from the ZConnect page; otherwise null. |
| `request_count` | integer (int64) |  |
| `spend_usd` | string |  |
| `total_tokens` | integer (int64) |  |
| `zeldoc` | `OrgUsageSliceDTO` | Traffic to Zeldoc-hosted models, the subscription-covered part. |

### OrgKeyUsageTotalsDTO

Period totals across every key in the organization, including the split
across the models those keys called.

| Field | Type | Description |
|---|---|---|
| `by_field` | `OrgKeyFieldUsageDTO`[] | The same totals grouped by each of the organization's key fields, in the order of `key_fields`: spend per team, private versus company keys. Empty when the organization has no key fields. |
| `cache_creation_input_tokens` | integer (int64) | Prompt tokens written into the provider's prompt cache. Also a subset of `prompt_tokens`; billed at a premium over the input rate. |
| `cache_read_input_tokens` | integer (int64) | Prompt tokens served from the provider's prompt cache. A subset of `prompt_tokens`, not an addition to it; billed at a fraction of the input rate, which is why a cache-heavy key can carry many tokens for little spend. |
| `completion_tokens` | integer (int64) |  |
| `external` | `OrgUsageSliceDTO` | Traffic to third-party models across every key. |
| `models` | `OrgModelUsageDTO`[] | Every model the organization used in the period, highest spend first, then most tokens first so zero-priced Zeldoc-hosted models still order by size. Empty when no key recorded activity. Like the per-key breakdown these can sum to slightly less than the totals above, because usage is occasionally recorded without a model. |
| `prompt_tokens` | integer (int64) |  |
| `providers` | `OrgProviderUsageDTO`[] | Every upstream provider the organization's usage was routed to in the period, highest spend first: the cut above `models`. Zeldoc-hosted traffic may appear as `hosted_vllm` at zero spend, but not reliably; use `zeldoc` / `external` for that split. Empty when nothing was recorded. |
| `request_count` | integer (int64) |  |
| `spend_usd` | string |  |
| `total_tokens` | integer (int64) |  |
| `zeldoc` | `OrgUsageSliceDTO` | Traffic to Zeldoc-hosted models across every key. Together with `external` this adds up to the totals above. |

### OrgModelUsageDTO

One model's usage across the whole organization over the reported period:
the per-key `models` breakdown rolled up across every key. Spend is USD.

| Field | Type | Description |
|---|---|---|
| `cache_creation_input_tokens` | integer (int64) | Prompt tokens written into the provider's prompt cache. Also a subset of `prompt_tokens`; billed at a premium over the input rate. |
| `cache_read_input_tokens` | integer (int64) | Prompt tokens served from the provider's prompt cache. A subset of `prompt_tokens`, not an addition to it; billed at a fraction of the input rate, which is why a cache-heavy key can carry many tokens for little spend. |
| `completion_tokens` | integer (int64) |  |
| `model` | string | The model as named in the request, e.g. "zeldoc/zdev" or "claude-opus-4-8". |
| `prompt_tokens` | integer (int64) |  |
| `request_count` | integer (int64) |  |
| `spend_usd` | string |  |
| `total_tokens` | integer (int64) |  |
| `zeldoc_hosted` | boolean | True for a model Zeldoc hosts itself. Covered by the subscription, so its spend is zero; it still counts tokens and requests, and rolls into the totals' `zeldoc` slice. |

### OrgMonthlyUsageDTO

Monthly usage for a single organization.

`months` holds one entry per calendar month, oldest first. `summary` holds
period totals and the change against the previous month.
`available_years` lists the calendar years the organization can have data
for, from the year it was created to the current one.

| Field | Type | Description |
|---|---|---|
| `available_years` | integer (int32)[] |  |
| `months` | `MonthlyUsageEntryDTO`[] |  |
| `org_id` | string | Organization id. |
| `summary` | `OrgUsageSummaryDTO` |  |

### OrgPlanCostDTO

The organization's monthly subscription cost, from its products, its
ZCore plan, the ZDev seats it has ordered and any price agreed with it. All money values are minor units of
`currency` (e.g. 149000 = 1.490,00 DKK), taken from the price set in that
currency, never converted.

| Field | Type | Description |
|---|---|---|
| `currency` | `Currency` | Currency of all amounts (ISO 4217): the one requested (DKK by default). |
| `lines` | `OrgPlanCostLineDTO`[] | One line per product the organization has that has a price. |
| `missing` | `OrgPlanCostMissingDTO`[] | Products the organization has that have no price yet. |
| `seats` | integer (int32) | ZDev seats ordered, across the plans. Every ordered seat is billed, used or not. |
| `total_monthly_cents` | integer (int64) | Sum of all `line_total_cents`, in minor units. |
| `zcore_plan` | null \| `ZCorePlan` | The organization's ZCore plan; null when it has none. |

### OrgPlanCostLineDTO

A single billable line of an organization's monthly plan cost.

| Field | Type | Description |
|---|---|---|
| `custom_price` | boolean | True when the price is one agreed with the organization rather than the list price. |
| `is_per_user` | boolean | When true, the line is billed per seat (`quantity` = seats); otherwise flat. |
| `line_total_cents` | integer (int64) | `unit_price_cents * quantity`, in minor units. |
| `period` | null \| `PlanCostPeriodDTO` | Set when the line covers only part of a month (seats added mid-month, on an invoice); null for a whole month. |
| `plan` | null \| `ZDevPlan` | The ZDev plan of a ZDev line (one line per plan with seats); null on other products. |
| `quantity` | integer (int32) | Seats for per-user lines, otherwise 1. |
| `service` | `PricingService` |  |
| `type` | `PricingType` |  |
| `unit_price_cents` | integer (int32) | Price of one unit, in minor units (e.g. 149000 = 1.490,00 DKK). For a line covering part of a month, the price for those days. |
| `zcore_plan` | null \| `ZCorePlan` | The ZCore plan of a ZCore line at its list price; null on other products and on an agreed price. |

### OrgPlanCostMissingDTO

A product the organization has that has no price yet (ZCore without a
plan or an agreed price, or a ZDev plan it has seats of), so it is not
in the plan cost.

| Field | Type | Description |
|---|---|---|
| `plan` | null \| `ZDevPlan` | The ZDev plan without a price, for ZDev; null on other products. |
| `service` | `PricingService` |  |
| `type` | `PricingType` |  |

### OrgProviderUsageDTO

One upstream provider's usage across the whole organization over the
reported period: the coarser cut above the models, for "how much went to
OpenAI versus Anthropic". Ordered by spend, highest first. Spend is USD.

| Field | Type | Description |
|---|---|---|
| `cache_creation_input_tokens` | integer (int64) | Prompt tokens written into the provider's prompt cache. Also a subset of `prompt_tokens`; billed at a premium over the input rate. |
| `cache_read_input_tokens` | integer (int64) | Prompt tokens served from the provider's prompt cache. A subset of `prompt_tokens`, not an addition to it; billed at a fraction of the input rate, which is why a cache-heavy key can carry many tokens for little spend. |
| `completion_tokens` | integer (int64) |  |
| `prompt_tokens` | integer (int64) |  |
| `provider` | string | The provider's slug, e.g. `openai`, `anthropic`, `gemini`, or `hosted_vllm` for Zeldoc-hosted models. |
| `request_count` | integer (int64) |  |
| `spend_usd` | string |  |
| `total_tokens` | integer (int64) |  |

### OrgUsageSliceDTO

Tokens and requests for one side of the Zeldoc-hosted / external split.

Used in pairs on the per-key entry and on the period totals: `zeldoc` is the
traffic that went to Zeldoc's own self-hosted models (covered by the
subscription and priced at zero), `external` is everything
else — the traffic that left for OpenAI, Anthropic, Google and the rest.
The two always add up to the row they sit on, so "what share went out into
the world" is one division.

No spend figure: the Zeldoc side is zero by construction, and the external
side is the row's own `spend_usd`.

| Field | Type | Description |
|---|---|---|
| `cache_creation_input_tokens` | integer (int64) | Prompt tokens written into the provider's prompt cache. Also a subset of `prompt_tokens`; billed at a premium over the input rate. |
| `cache_read_input_tokens` | integer (int64) | Prompt tokens served from the provider's prompt cache. A subset of `prompt_tokens`, not an addition to it; billed at a fraction of the input rate, which is why a cache-heavy key can carry many tokens for little spend. |
| `completion_tokens` | integer (int64) |  |
| `prompt_tokens` | integer (int64) |  |
| `request_count` | integer (int64) |  |
| `total_tokens` | integer (int64) |  |

### OrgUsageSummaryDTO

Period totals (over all returned months) plus the month-over-month percentage
change.

Each `*_change_pct` compares the **current calendar month** against the
**immediately previous month**. Because the current month is in progress, its
totals are **projected to a full-month run-rate** (so-far / days-elapsed *
days-in-month) before the comparison, so the delta reflects the trend rather
than a partial-vs-full understatement. The projection affects only this delta,
not the actual values in `months[]`.

`None` when either month is absent from the returned window (e.g. a past year
was selected), or when the previous month is zero (no baseline).

| Field | Type | Description |
|---|---|---|
| `requests_change_pct` | string \| null |  |
| `tokens_change_pct` | string \| null |  |
| `total_dkk_spend` | string \| null |  |
| `total_requests` | integer (int64) |  |
| `total_tokens` | integer (int64) |  |
| `total_usd_spend` | string |  |
| `usd_spend_change_pct` | string \| null | Percent change of the current month vs the previous month (e.g. -25.7). |

### PartnerCustomerDTO

An organization a partner serves.

| Field | Type | Description |
|---|---|---|
| `key_count` | integer |  |
| `name` | string |  |
| `org_id` | string |  |

### PartnerCustomerKeysDTO

One organization you serve and the keys you run there.

| Field | Type | Description |
|---|---|---|
| `allowed_models` | `PartnerModelDTO`[] | The models a key there may have; the recommended ones are marked. |
| `customer` | `PartnerCustomerDTO` |  |
| `keys` | `PartnerKeyDTO`[] |  |

### PartnerKeyDTO

A key you run, compared with your recommended models.

| Field | Type | Description |
|---|---|---|
| `created_at` | string (date-time) |  |
| `extra` | string[] |  |
| `has_recommended` | boolean |  |
| `id` | string |  |
| `key_prefix` | string |  |
| `locked` | boolean | The profile is a fixed set Zeldoc keeps: the models cannot be changed. |
| `missing_recommended` | string[] | Recommended and allowed here, but not on the key. |
| `models` | string[] |  |
| `name` | string |  |
| `no_longer_offered` | string[] | On the key but no longer offered to the organization, e.g. an older version. It stays on the key until you remove it. |
| `not_allowed` | string[] | Recommended, but not on this organization's allow-list. |
| `profile_id` | `PartnerProfileIdDTO` |  |
| `profile_name` | string |  |
| `recommended_models` | string[] | What this key is checked against: its profile's recommendation. |

### PartnerModelDTO

| Field | Type | Description |
|---|---|---|
| `id` | string |  |
| `name` | string |  |
| `product` | null \| `ModelProduct` |  |
| `zconnect` | boolean | On the allow-list as one of the organization's ZConnect models. |

### PartnerProfileDTO

What one kind of key is checked against, e.g. "Chat" or "Vault".

| Field | Type | Description |
|---|---|---|
| `follows_zrouter` | boolean | The recommendation follows the ZRouter models, so a new one is recommended at once. Otherwise it is a fixed set Zeldoc keeps, and the keys' models cannot be changed by the partner. |
| `id` | `PartnerProfileIdDTO` |  |
| `key_count` | integer |  |
| `name` | string |  |
| `recommended` | string[] |  |

### PartnerProfileIdDTO

Type: string (uuid)

### PartnerStatusDTO

Every customer of the partner and the keys it runs there, each checked
against the recommended models.

| Field | Type | Description |
|---|---|---|
| `customers` | `PartnerCustomerKeysDTO`[] |  |
| `key_count` | integer |  |
| `keys_missing_recommended` | integer | Keys missing a recommended model the customer may use. |
| `name` | string |  |
| `org_id` | string | The partner's own organization id. |
| `profiles` | `PartnerProfileDTO`[] | What each kind of key is checked against. |

### PlanCostPeriodDTO

The days of a month a plan cost line covers, when it is less than the
whole month: seats added mid-month are billed from the day they were
added, for those days only.

| Field | Type | Description |
|---|---|---|
| `first_day` | string (date) |  |
| `last_day` | string (date) |  |

### PricingService

Service or product line that can be priced.

Type: `"private_llm"` | `"llm_router"` | `"zcontrol"` | `"zconnect"`

### PricingType

Type or sub-offering within a priced service (e.g. Coding vs Assistant).

Type: `"coding"` | `"assistant"` | `"public_models"` | `"management"` | `"integrations"`

### ZCorePlan

A ZCore plan, as sold on zeldoc.ai. It sets the organization's monthly
ZCore price.

Type: `"starter"` | `"small"` | `"medium"` | `"large"`

### ZDevPlan

A ZDev plan. Seats are ordered per plan, and each person with a seat
holds a seat of one plan.

Type: `"go"` | `"pro"` | `"max"`

