# API reference

Base URL: `https://veqen-app.com`. Authenticate with `Authorization: Bearer vq_live_...`. The OpenAPI document is at [/openapi.json](https://veqen-app.com/openapi.json).

## Send calls

`POST /v1/events` (ingest key). Up to 1,000 events and 5 MB per request (a larger batch is refused whole); `Content-Encoding: gzip` is welcome.

```bash
curl https://veqen-app.com/v1/events \
  -H "Authorization: Bearer $VEQEN_API_KEY" -H "Content-Type: application/json" \
  -d '{"events": [{"id": "9f1c2e7a5b3d4c6e", "ts": "2026-10-22T14:03:11Z", "provider": "openai",
        "model": "gpt-5-mini", "input_tokens": 1200, "output_tokens": 340, "cache_read_tokens": 0,
        "customer": "acme", "feature": "support-bot"}]}'
```

| Field | Type | Notes |
|---|---|---|
| `id` | string, 8–64 characters | Unique per call. Retries with the same `id` and `ts` are stored once |
| `ts` | ISO 8601 | Within the last 35 days, and no more than 24 hours ahead |
| `provider`, `model` | string | As the provider names them; Veqen normalizes and prices them |
| `input_tokens` | integer | Uncached input |
| `output_tokens` | integer | Including reasoning |
| `cache_read_tokens`, `cache_write_tokens`, `cache_write_1h_tokens` | integer | Optional |
| `audio_input_tokens`, `audio_output_tokens` | integer | Optional, for realtime and audio models |
| `units` | number | Images, seconds, characters, queries or pages, for models billed per unit |
| `variant` | string | Image size and quality (`hd/1024x1792`) or video resolution (`1080p`) |
| `batch` | boolean | Batch API call |
| `service_tier`, `residency` | string | `priority`, `flex`; `us` |
| `cost_usd` | number | Kept when the model has no price, or when the call reports no usage |
| `customer`, `feature`, `user`, `project`, `environment` | string | See [Tags](/docs/tags) |
| `tags` | object | Up to 32 free-form tags |
| `latency_ms`, `status` | integer, `ok` or `error` | Optional |

Response:

```json
{"accepted": 1, "duplicates": 0, "dropped": 0, "rejected": []}
```

`dropped` counts calls over your plan's cap or your own bill budget (not stored, not billed). `rejected` lists `{"index", "error"}` for invalid events; the rest of the batch is still stored.

`POST /v1/otlp/v1/traces` accepts OpenTelemetry instead: see [OpenTelemetry](/docs/opentelemetry).

## Budget status

`GET /v1/budgets/status` (ingest key) → `{"blocked": [{"scope": "customer", "value": "acme", "limit_usd": 5.0}]}`. The SDKs poll this every 30 seconds.

## Read spend

`GET /v1/usage` (full key)

| Parameter | |
|---|---|
| `start`, `end` | `YYYY-MM-DD`, inclusive; defaults to the last 30 days; up to two years |
| `group_by` | `provider`, `model`, `customer`, `feature`, `user`, `project` or `environment` |
| `provider`, `model`, `customer`, `feature`, `user`, `project`, `environment` | Filters (exact match; an empty value means untagged) |
| `daily` | `true` adds a per-day series |
| `limit` | Groups to return, up to 1,000 |

```json
{"start": "2026-09-23", "end": "2026-10-22",
 "totals": {"calls": 48211, "cost": 1284.37, "input_tokens": 51200443, "output_tokens": 9102114,
            "cache_read_tokens": 30110022, "cache_write_tokens": 1022011, "units": 12400, "unpriced_calls": 0},
 "groups": [{"key": "acme", "calls": 12001, "cost": 412.2, "share": 0.321, "...": "..."}]}
```

`GET /v1/usage/models?days=30` (ingest key; Pro and Team) → calls and tokens per model, used by `veqen diff`.

`GET /v1/export.csv?start=&end=` (full key) → daily rollups as CSV, one row per day and tag combination.

`GET /v1/account` (ingest key) → organization, plan, this month's tracked calls and limits.

## Prices

`GET /v1/prices?model=gpt-5-mini` (no key) → the model's `kind`, its `unit` if it's billed per unit, rates (per million tokens, or per unit), context window and retirement date. Add what one call uses (`input_tokens`, `output_tokens`, `cached_tokens`, `audio_input_tokens`, `audio_output_tokens`, `units`, `variant`, `batch=true`) to get `cost_per_call`: `/v1/prices?model=sora-2&units=8` prices an 8-second clip. The whole price book is at [/prices.json](https://veqen-app.com/prices.json).

## Simulate a workload

`POST /v1/simulate` (no key) prices a month of calls across any mix of models at list prices: the same engine as [the simulator](/simulate).

```bash
curl https://veqen-app.com/v1/simulate -H "Content-Type: application/json" -d '{"growth_pct": 5, "lines": [
  {"label": "support", "model": "anthropic/claude-sonnet-4-5", "calls": 600000, "input_tokens": 2400, "cached_tokens": 12000, "output_tokens": 380},
  {"label": "captions", "model": "deepgram/nova-3", "calls": 20000, "units": 1800}]}'
```

Each line takes the model, `calls` a month and what one call uses: the token fields, `units` (images, seconds, characters, queries or pages), `variant` and `batch`. Up to 25 lines. The response has each line's `per_call`, `monthly` and `share`; a `ladder` placing the call among every current model of its kind (`cheaper_than` is the share of those models that cost more); `same_model`, the same model on other hosts, cheapest first; and `cheaper`, cheaper models from the same provider. Totals: `monthly`, `first_year`, and a `projection` per month with `growth_pct` applied. Limited to a few requests a second per IP address.

## Limits

Ingest: 200 requests a second per organization (batch calls instead of raising this). Read endpoints: 10 requests a second per key. Exceeding either returns `429` with `Retry-After`.

Request bodies: 5 MB, or 25 MB once gzip is inflated. `/v1/events` takes up to 1,000 events a request; an OTLP export can hold any number of spans within the size limit.
