# Architecture (https://docs.corevalue.dev/architecture)



CoreValue sits between your applications and the AI providers they call. Every
request passes through the gateway, which checks it against your keys, budgets
and policies, forwards it, and records what it cost. The console reads those
records to show spend, run budgets and produce reports.

```text
  Your applications and agents
  OpenAI · Anthropic · Google SDKs · Claude Code · MCP clients · agents
                         │
                         ▼
  ┌──────────────────────────────────────────────────────┐
  │  CoreValue gateway        https://gateway.corevalue.dev │
  │  identify key → check budgets and policy → forward     │
  │  → price the response → settle budgets → record        │
  └──────────────────────────────────────────────────────┘
          │                                   │
          ▼                                   ▼
  AI providers, MCP servers,          Requests, spend and budgets
  agents                              in the console (app.corevalue.dev)
```

## The ways in [#the-ways-in]

| Surface           | What it is                                                                                                                                                                          | Docs                                                                                                          |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **LLM gateway**   | The provider APIs your SDKs already speak: OpenAI-compatible (`/v1`), any provider by model prefix (`/gateway/v1`), and the native Anthropic, Google, Bedrock and Vertex endpoints. | [OpenAI](/getting-started/openai), [Anthropic](/getting-started/anthropic), [Gemini](/getting-started/gemini) |
| **MCP gateway**   | One endpoint in front of your MCP servers. Tool calls are checked against access rules and logged with their cost.                                                                  | [MCP gateway](/features/mcp/overview)                                                                         |
| **Agent gateway** | Calls between agents (A2A), with rules on who may call each agent.                                                                                                                  | [Agent gateway](/features/agents/overview)                                                                    |
| **CoreValue SDK** | Optional helpers for TypeScript and Python. They set `Cova-*` headers and read cost from responses. They do not proxy or change your calls.                                         | [CoreValue SDK](/sdks/cova-sdk)                                                                               |

## A request, step by step [#a-request-step-by-step]

<Steps>
  <Step>
    **Identify the caller.** The CoreValue key is looked up by its SHA-256 hash.
    The key's scopes decide which customers, models and providers it may use.
  </Step>

  <Step>
    **Choose the provider key.** Your own provider key for that provider is used
    first. Your application never sees it.
  </Step>

  <Step>
    **Apply limits and policy.** Rate limits, blocked models, and, where you have
    turned them on, guardrails that redact or refuse personal data.
  </Step>

  <Step>
    **Check budgets.** Every budget that covers the request is checked: the
    organisation, its team, the member, the customer and the key. A budget set to
    block holds the request's estimated cost against its remaining amount, so
    concurrent requests cannot overspend it together. An organisation or team
    budget set to downgrade rewrites the model to a cheaper one instead.
  </Step>

  <Step>
    **Forward.** The request goes to the provider unchanged. Failures with status
    429, 500, 502, 503, 504 or 529 are retried, and a `Cova-Fallbacks` chain tries
    other models in order. Streams pass through as they arrive.
  </Step>

  <Step>
    **Price the response.** The gateway reads the provider's usage (input, output,
    cached and reasoning tokens) and prices it from CoreValue's price table. The
    cost is returned in `Cova-Request-Cost`.
  </Step>

  <Step>
    **Settle and record.** Each held amount is corrected to the actual cost, and
    the request is recorded with its cost, tokens, latency and tags. Recording
    happens after your response is sent, so it adds no time to the request.
  </Step>
</Steps>

## Budgets [#budgets]

Budget counters are kept in micro-dollars, so sub-cent requests add up
exactly. A held amount that is never settled, for example because a stream was
abandoned, expires after five minutes rather than holding the budget forever.
Budget settings are cached for up to 60 seconds, so a change takes effect
within a minute. See [Budgets](/features/budgets/budgets-and-limits).

## Cost [#cost]

CoreValue prices requests itself, from its own table of models and their
rates. It prices input and output tokens, cached input,
cache writes, reasoning tokens, per-request fees, images, audio and web search
where a model charges for them. A model with no price is recorded at $0 and
marked unpriced, so it shows up rather than disappearing. See
[How cost is calculated](/additional/how-cost-is-calculated) and
[Supported models](/additional/supported-models).

## Where data lives [#where-data-lives]

All of it runs on Google Cloud in `us-central1`.

| Store          | What it holds                                                                                                             |
| -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **PostgreSQL** | Organisations, members, teams, budgets and policies; CoreValue keys as SHA-256 hashes; your provider keys; the audit log. |
| **ClickHouse** | One row per request: model, provider, tokens, cost, latency and your tags.                                                |
| **Redis**      | Rate-limit and budget counters, short-lived caches, and the response cache, which is encrypted.                           |

## When something fails [#when-something-fails]

| If…                               | Then                                                                                    |
| --------------------------------- | --------------------------------------------------------------------------------------- |
| The provider fails or rate-limits | The request is retried, then sent to the next model in `Cova-Fallbacks` if you set one. |
| Budget data cannot be read        | The request is served. Budgets never take your traffic down.                            |
| Recording a request fails         | Your response is unaffected; it was already sent.                                       |
| A request is refused by a budget  | HTTP `429`, before anything reaches the provider.                                       |

## Security [#security]

* **Your keys stay out of your code.** Applications hold only a CoreValue key;
  provider keys are stored in CoreValue and added to the request on the way out.
* **CoreValue keys are stored as hashes**, so a key cannot be read back, only
  replaced.
* **Access in the console** is decided by roles, and significant changes to
  budgets, roles, keys and integrations are recorded in the
  [audit log](/features/organization/audit-log).
* **Personal data** in requests and tool calls can be redacted or refused by
  [guardrails](/features/access/guardrails).

See [Data and privacy](/additional/data-and-privacy) for compliance.


# What is CoreValue? (https://docs.corevalue.dev/)



CoreValue is an AI gateway with spend control built in. Your applications send
their AI requests through it; it checks each one against your budgets before it
reaches the provider, records what it cost, and attributes that cost to the
team, customer and feature it was for.

## The problem [#the-problem]

AI providers give you usage dashboards and spend limits, but they are organised
around your account (projects, workspaces and keys), not around your customers,
teams and features. Each provider has its own prices, reports and limits, and a
limit stops all the traffic that shares it.

You can tag requests and add up costs in your own code. But holding each
customer and team to a budget, across every provider, means building and
maintaining that yourself.

**CoreValue puts attribution and budgets in the request path.** Every request
is priced and attributed to its customer, team, member, key and feature. Before
it reaches the model, it is checked against the budgets that apply. A key cap,
member budget or customer budget refuses it; a team or organisation budget can
move it to a cheaper model instead. Each check first reserves a worst-case
estimate, so concurrent requests cannot all slip past the same budget.

## How it works [#how-it-works]

```text
  Your app or agent  ──►  CoreValue gateway  ──►  OpenAI · Anthropic · Google · more
                          │  checks budgets and policy before the provider
                          │  prices the response, settles budgets
                          ▼
                       Console: spend · budgets · alerts · reports
```

You change two lines: the base URL and the key.

```python
# Before
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

# After
client = OpenAI(
    base_url="https://gateway.corevalue.dev/v1",
    api_key=os.environ["COVA_API_KEY"],  # sk-cova-…
)
```

Your provider key moves into CoreValue, and the provider keeps billing you as
it does today. See [Architecture](/architecture) for the full path
of a request.

## What you get [#what-you-get]

### Measure the cost [#measure-the-cost]

* Every request with its model, tokens, latency and cost. → [Cost tracking](/features/cost-tracking/requests)
* Spend by team, member, customer, feature and model. → [Cost attribution](/features/cost-attribution)
* Multi-step agent runs followed from first request to last. → [Runs](/features/cost-tracking/runs)

### Control the spend [#control-the-spend]

* Monthly budgets for the organisation, a team, a member, a customer or a key,
  checked before each request. → [Budgets](/features/budgets/budgets-and-limits)
* Refuse, downgrade to a cheaper model, or alert when a budget runs out.
* Rate limits, blocked models, key scopes and PII guardrails. → [Access control](/features/access/block-models)
* Email alerts before a budget runs out. → [Budget alerts](/features/budgets/budget-alerts)

### Prove the value [#prove-the-value]

* Chargeback to teams for finance. → [Chargeback](/features/reports/chargeback)
* Cost and margin per customer. → [Unit economics](/features/reports/unit-economics)
* An executive summary and scheduled reports. → [Reports](/features/reports/executive)

## Trust [#trust]

* **Your provider keys stay out of your code** and are never sent to your
  applications.
* **CoreValue keys are stored as hashes** and cannot be read back.
* **Roles decide who sees what** in the console, and changes are recorded in
  the [audit log](/features/organization/audit-log).
* **What is stored, and for how long,** is set out in
  [Data and privacy](/additional/data-and-privacy).

## Get started [#get-started]

<Cards>
  <Card title="Quickstart" href="/getting-started/quickstart">
    From sign-in to your first request and its cost, in about five minutes.
  </Card>

  <Card title="OpenAI SDK" href="/getting-started/openai">
    Keep your code; change the base URL and the key.
  </Card>

  <Card title="Anthropic SDK" href="/getting-started/anthropic">
    The native Messages API, unchanged.
  </Card>

  <Card title="Google Gemini SDK" href="/getting-started/gemini">
    The native Gen AI SDK, with only your CoreValue key.
  </Card>

  <Card title="Claude Code" href="/getting-started/claude-code">
    See and cap what each developer spends.
  </Card>

  <Card title="MCP gateway" href="/features/mcp/overview">
    Your MCP servers behind one endpoint, every tool call logged.
  </Card>

  <Card title="Migrate from Helicone" href="/guides/migrate-from-helicone">
    Most headers keep their names; change the base URL and key.
  </Card>

  <Card title="AI coding assistants" href="/guides/ai-coding-assistants">
    Instructions for Claude Code, Cursor and Copilot, and `/llms.txt`.
  </Card>
</Cards>

## API reference and SDKs [#api-reference-and-sdks]

* [Authentication](/api-reference/authentication): how requests carry a CoreValue key.
* [Headers](/api-reference/headers): every `Cova-*` header you can send or receive.
* [Errors](/api-reference/errors): what each refusal looks like.
* [CoreValue SDK](/sdks/cova-sdk): TypeScript and Python helpers for headers and cost.
* [Integrations](/sdks/integrations/overview): LangChain, Vercel AI SDK, LlamaIndex and more.

Tools and agents can read these docs at [`/llms.txt`](https://docs.corevalue.dev/llms.txt).


# Data and privacy (https://docs.corevalue.dev/additional/data-and-privacy)





## What is true today [#what-is-true-today]

Everything below is a control the code enforces, named so you can check it.

**Provider keys are stored as written; column encryption is in progress.**
In the hosted cloud, provider keys — and the upstream credentials you
register for MCP servers and agents — are stored in Cloud SQL, which Google
Cloud encrypts at rest by default with Google-managed keys, and are readable
only by the platform's own service accounts. They are not yet encrypted
inside the database column: an earlier version of this page said they were,
and that was wrong. Encryption in the column, with the key held outside the
database, is being built and this paragraph will change when it ships. Proxy
keys are stored as hashes and cannot be recovered.

**Members see what they are allowed to see.** Role-based access control is
enforced on the API — every route names the permission it requires, and
[Roles and permissions](/references/roles) lists them, generated from the
code — and an organisation owner can narrow request visibility so members
see their own traffic rather than everyone's.

**Cached responses are encrypted.** The response cache is encrypted with
AES-GCM and supports key rotation.

**Where the hosted cloud runs.** One region: Google Cloud `us-central1`, in
North America. Additional regions will be added as customers need them.

Security questionnaires, compliance documentation and data-processing terms
are available on request on the Enterprise plan. Contact
[support@corevalue.dev](mailto:support@corevalue.dev).

<QuestionsSection />


# How cost is calculated (https://docs.corevalue.dev/additional/how-cost-is-calculated)





## Real Cost — `Cova-Request-Cost` (M0) [#real-cost--cova-request-cost-m0]

As of M0 (0.0a), the gateway computes **real cost** for every request — not an
estimate. Where it is reported depends on the response: the `Cova-Request-Cost`
header for non-streaming requests, and the final SSE `cova` frame or the
`/meta` endpoint for streaming ones (see below). The value is derived from
extracted upstream usage and the model's pricing table in
`packages/cost/models/`.

### Non-Streaming Requests [#non-streaming-requests]

For non-streaming requests, the upstream provider returns a `usage` object
(prompt tokens, completion tokens, cache tokens, etc.). The gateway extracts
this usage, looks up the model's per-token pricing, and sets
`Cova-Request-Cost` on the response headers.

```json
"usage": {
  "prompt_tokens": 11,
  "completion_tokens": 9,
  "total_tokens": 20
}
```

The corresponding header:

```
Cova-Request-Cost: 0.0015000000
```

<Callout type="info">
  `Cova-Request-Cost` is **always set** on non-streaming responses, even when
  the computed cost is `0`. This is a guarantee — if the header is absent,
  something went wrong with usage extraction.
</Callout>

### Streaming Requests [#streaming-requests]

A streamed response &#x2A;*never carries `Cova-Request-Cost`**. Response headers are
sent before the first chunk, and the cost is only known once the stream has
ended and usage has been read from it.

Where to read the cost instead depends on the stream's wire format:

| Stream format                                           | Examples                                           | Where the cost is                  |
| ------------------------------------------------------- | -------------------------------------------------- | ---------------------------------- |
| Server-Sent Events (`text/event-stream`)                | OpenAI-format chat completions, Anthropic Messages | Final SSE `cova` frame, or `/meta` |
| AWS event stream (`application/vnd.amazon.eventstream`) | Bedrock `invoke-with-response-stream`              | `/meta` only                       |
| Streaming JSON                                          | Google `streamGenerateContent` without `alt=sse`   | `/meta` only                       |

The frame is only appended to SSE streams. Appending text to a binary event
stream or a JSON document would corrupt it, so those responses pass through
byte for byte.

For SSE streams, the gateway appends a final `data:` frame after the
upstream stream closes (including after `data: [DONE]` for OpenAI-format
providers). The cost is included in the `cova` object:

```
data: {"cova":{"requestId":"...","provider":"groq","model":"groq/llama-3.3-70b-versatile","status":"success","requestCost":0.0015}}
```

<Callout type="warning">
  In the streaming final SSE frame, `requestCost` is set only when
  `usage.costUsd > 0`. If no usage was extracted (e.g. upstream errored), the
  field is absent and you should fall back to the
  `GET /gateway/v1/meta/:requestId` endpoint.
</Callout>

For any stream, read `Cova-Id` from the response headers and call
`GET /gateway/v1/meta/:requestId` after the stream ends. It returns
`{ "status": "pending" }` with `202` until settlement has finished, and keeps
the metadata for 5 minutes.

For the full streaming FinOps flow — including the `@cova/gateway` SDK's
`streamChat()` helper and the `/meta` fallback — see
[Streaming FinOps](/features/routing/streaming).

## Upfront Estimate — `Cova-Request-Cost-Estimate` (M4) [#upfront-estimate--cova-request-cost-estimate-m4]

When escrow (pass-through billing) is enabled and a PTB key may serve the
request, the gateway sets `Cova-Request-Cost-Estimate` on the response. This is
an **upfront worst-case cost estimate** — the escrow reservation amount —
provided as a UX hint only.

```
Cova-Request-Cost-Estimate: 0.0230000000
```

<Callout type="note">
  `Cova-Request-Cost-Estimate` is **not** the source of truth for cost. The
  actual cost is always `Cova-Request-Cost` (or the `requestCost` field in the
  streaming `cova` frame). The estimate may be higher than the real cost because
  it reserves the worst-case token budget.
</Callout>

## Anthropic Requests [#anthropic-requests]

For Anthropic requests, the gateway extracts usage from the Anthropic response
format (which differs from OpenAI's). Token counting and cost calculation use
the same pricing-table lookup as other providers. Cross-provider translation
(M4) maps the response back to OpenAI format before returning it to the client.

## Developer Reference [#developer-reference]

For the full cost calculation implementation, see the
[`packages/cost`](https://github.com/CoreValue/corevalue/tree/main/packages/cost)
package in the open-source repository.

<Callout type="note">
  If you want to calculate costs across models and providers, you can use our
  free, open-source tool: [LLM API Pricing
  Calculator](https://www.corevalue.dev/llm-cost)
</Callout>

<Callout type="info">
  These methods are based on our current understanding and may be subject to
  changes in the future as APIs and token counting methodologies evolve.
</Callout>

<QuestionsSection />


# Request and response storage (https://docs.corevalue.dev/additional/request-storage)



# Body Retention Modes [#body-retention-modes]

Control whether request and response bodies are stored — for privacy, compliance, or cost.

<Callout type="info">
  **Tier:** Body retention is a non-free feature (Growth tier or higher).
</Callout>

## Modes [#modes]

| Mode            | What's Stored                        | When to Use                  |
| --------------- | ------------------------------------ | ---------------------------- |
| `full`          | Full request + response bodies in S3 | Debugging, replay, audit     |
| `metadata_only` | Metadata only (no bodies)            | Privacy-sensitive workloads  |
| `none`          | No body storage                      | Maximum privacy / compliance |

## Default [#default]

Body retention defaults to **OFF** (fail-closed for privacy). When disabled, only metadata is logged — no request or response bodies are stored.

## Configuration [#configuration]

Body retention is configured at the org level via the CoreValue dashboard or API. The gateway checks the org's `body_retention_enabled` setting on each request.

## What Gets Stored [#what-gets-stored]

* **When enabled:** Request and response bodies uploaded to S3/GCS object storage. Metadata logged to analytics.
* **When disabled:** Bodies not stored. Only metadata (model, tokens, cost, user\_id, properties) is logged.

## Privacy Implications [#privacy-implications]

For GDPR/CCPA compliance, disable body retention. The gateway still tracks cost and usage metadata without storing the actual content of requests.

<Callout>
  **Related:** PII Scanning masks sensitive data even when body retention is enabled.
</Callout>


# Supported providers and models (https://docs.corevalue.dev/additional/supported-models)



CoreValue calls a provider with the [provider key](/features/organization/provider-keys) your organisation adds on **Gateway** → **Provider keys**. Which models you can use depends on which providers you hold a key for, so the list is per organisation rather than one global catalogue.

## How to reach each provider [#how-to-reach-each-provider]

There are three ways in, depending on the SDK you use:

| Base URL                                   | Use it for                                    | Model name                                                                               |
| ------------------------------------------ | --------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `https://gateway.corevalue.dev/v1`         | OpenAI, with the OpenAI SDK                   | As OpenAI names it, for example `gpt-4o-mini`                                            |
| `https://gateway.corevalue.dev/gateway/v1` | Every other provider, with the OpenAI SDK     | The provider's prefix, then its own model id, for example `groq/llama-3.3-70b-versatile` |
| `https://gateway.corevalue.dev`            | The Anthropic, Gemini and AWS SDKs, unchanged | As the provider names it                                                                 |

| Provider           | Name on **Provider keys** | Prefix on `/gateway/v1`                                                  | Native endpoint                                                                                               |
| ------------------ | ------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| OpenAI             | OpenAI                    | — (use `/v1`)                                                            | `/v1/chat/completions`, `/v1/responses`, `/v1/embeddings` and the rest of the OpenAI API                      |
| Anthropic          | Anthropic                 | `anthropic/`                                                             | `/v1/messages` — see [Anthropic](/getting-started/anthropic)                                                  |
| Google AI (Gemini) | Google AI (Gemini)        | `google/`                                                                | `/v1beta/models/{model}:generateContent` and `:streamGenerateContent` — see [Gemini](/getting-started/gemini) |
| Vertex AI          | Vertex AI                 | —                                                                        | `/v1/projects/{project}/locations/{location}/publishers/google/models/{model}:generateContent`                |
| AWS Bedrock        | AWS Bedrock               | `bedrock/`                                                               | `/model/{model-id}/invoke` and `/model/{model-id}/invoke-with-response-stream`                                |
| Groq               | Groq                      | `groq/`                                                                  | —                                                                                                             |
| Mistral AI         | Mistral AI                | `mistral/`                                                               | —                                                                                                             |
| DeepSeek           | DeepSeek                  | `deepseek/`                                                              | —                                                                                                             |
| xAI                | X.AI (Grok)               | `xai/`                                                                   | —                                                                                                             |
| Perplexity         | Perplexity                | `perplexity/`                                                            | —                                                                                                             |
| OpenRouter         | OpenRouter                | `openrouter/`                                                            | —                                                                                                             |
| DeepInfra          | DeepInfra                 | `deepinfra/`                                                             | —                                                                                                             |
| Novita             | Novita                    | `novita/`                                                                | —                                                                                                             |
| Baseten            | Baseten                   | `baseten/`                                                               | —                                                                                                             |
| Fireworks          | Fireworks                 | `fireworks/`                                                             | —                                                                                                             |
| Cerebras           | Cerebras                  | `cerebras/`                                                              | —                                                                                                             |
| Nebius             | Nebius Token Factory      | `nebius/`                                                                | —                                                                                                             |
| Canopy Wave        | Canopy Wave               | `canopywave/`                                                            | —                                                                                                             |
| Chutes             | Chutes                    | `chutes/`                                                                | —                                                                                                             |
| Vercel AI Gateway  | Vercel AI Gateway         | `vercel/`, then Vercel's own id, for example `vercel/openai/gpt-4o-mini` | —                                                                                                             |
| Together AI        | Not offered               | `together/`                                                              | —                                                                                                             |

Through `anthropic/`, `google/` and `bedrock/`, CoreValue translates the OpenAI-format request into the provider's own format and maps the reply back, so your code keeps reading OpenAI responses. Those responses carry `Cova-Gateway-Mode: translated`; see [Response headers](/api-reference/response-headers).

<Callout type="warn">
  A prefix is honoured only on `/gateway/v1`. On `/v1`, `groq/llama-3.3-70b-versatile` is not routed by its prefix. To send a bare model name to `/v1/chat/completions` and let CoreValue find the provider, see [Route by model name](/features/routing/model-routing).
</Callout>

Azure OpenAI appears on **Provider keys** and in the price registry, but the gateway has no route to it yet. How prefixes work with retries and fallbacks is on [Call other providers](/features/routing/other-providers).

## List the models you can call [#list-the-models-you-can-call]

`GET https://gateway.corevalue.dev/v1/models` returns the models your organisation can reach, in the OpenAI list format, so `client.models.list()` works unchanged.

```bash
curl https://gateway.corevalue.dev/v1/models \
  -H "Authorization: Bearer $COVA_API_KEY"
```

* A model is listed when at least one provider your organisation holds a key for serves it. After you add a provider key, that provider's models appear within about a minute.
* Each `id` is the model's name in CoreValue's price registry, such as `claude-sonnet-4-5-20250929` or `llama-3.3-70b-versatile`, and `owned_by` is the model's author. The id carries no provider prefix: on `/gateway/v1`, add the prefix yourself.

See [List models](/api-reference/list-models) for the endpoint reference.

## The price registry [#the-price-registry]

CoreValue prices each request from its registry of provider list prices. The registry today prices 119 models across 21 providers. A price belongs to a model *on a provider*: the same model can cost different amounts on Anthropic, Bedrock and OpenRouter, and each request is priced at the rate of the provider that served it. Requests through `vercel/` are priced as the underlying model on its author's own API.

How a price becomes the cost of a request is on [How cost is calculated](/additional/how-cost-is-calculated).

## Models CoreValue cannot price [#models-corevalue-cannot-price]

The gateway forwards a model even when the registry does not know it. The request is recorded at **$0** and marked **Unpriced** on **Requests**; its tokens are still recorded.

<Callout type="warn">
  An unpriced request costs $0 to every spend control. Budgets, key caps and cents-based rate limits do not count it, so traffic to an unpriced model is not capped. Look for the **Unpriced** mark on **Requests** after you start using a new model.
</Callout>

CoreValue can refuse an unpriced model before it is sent, with HTTP `400` and the message &#x2A;Model "…" on … cannot be priced by this gateway and was not sent.* That refusal is not switched on yet: today the request is served and recorded at $0. In a [fallback chain](/features/routing/fallbacks), a model nothing prices is already skipped rather than attempted.

## Related [#related]

* [Provider keys](/features/organization/provider-keys) — add the key for each provider
* [Call other providers](/features/routing/other-providers) — prefixes, retries and fallbacks
* [Route by model name](/features/routing/model-routing) — send a bare model name to `/v1`
* [Response headers](/api-reference/response-headers) — which provider and model served a request


# Changelog (https://docs.corevalue.dev/changelog/CHANGELOG)



# Changelog [#changelog]

## October 2026 — Breaking changes / Security [#october-2026--breaking-changes--security]

**Status:** Merged; ships with the next release.

Five read endpoints that return spend, budget and billing data now require a signed-in user whose
role holds the **Read reports** permission (`reports.read`). Before this change any member of the
organisation, and any API key, could read them.

| Endpoint                          | What it returns                                                     |
| --------------------------------- | ------------------------------------------------------------------- |
| `GET /v1/budget/breaches`         | The budget breach log: each scope's spend and ceiling when it acted |
| `GET /v1/credits/invoices`        | The organisation's invoices                                         |
| `GET /v1/credits/spend/breakdown` | The organisation's spend by model                                   |
| `GET /v1/budget/customers`        | Each customer's spend, budget, price, revenue and margin            |
| `GET /v1/budget/customer-prices`  | The price each customer pays                                        |

* **Who keeps access:** owners and admins, and custom roles that include Read reports.
* **Who loses access:** members whose role does not include Read reports, and **API keys**. A
  script that reads these endpoints with an API key now receives `403`; read them from a signed-in
  user session whose role holds Read reports instead.
* **Custom roles:** a role meant to audit or manage customer budgets and prices should include
  Read reports, since listing them now needs it.
* **In the console:** pages that show this data to a viewer without the permission say so (a
  locked or hidden state) rather than showing an empty list.

## M4 — SDK + Competitor Gaps [#m4--sdk--competitor-gaps]

**Status:** Shipped

* **`@cova/gateway` SDK** (TypeScript + Python): thin header helper that sets
  `Cova-*` request headers, parses response headers into `CovaGatewayMeta`, and
  provides `streamChat()` for streaming FinOps.
* **Cross-provider translation**: OpenAI-format request bodies are translated to
  native provider formats for `anthropic/`, `google/`, and `bedrock/` prefixes
  when `CROSS_PROVIDER_TRANSLATION_ENABLED` is set. Responses are mapped back to
  OpenAI format by the response mapper.
* **Fallback chains**: `Cova-Fallbacks` header — JSON array of model names tried
  in order when the primary model returns a retryable status code.
* **Streaming FinOps**: final SSE `data: {"cova":{...}}` frame appended after
  the upstream stream closes. `streamChat()` yields `{cova: CovaGatewayMeta}`.
  `/meta` endpoint fallback for missing `requestCost`.
* **`Cova-Request-Cost-Estimate`**: upfront worst-case cost estimate (escrow
  reservation amount) set when PTB keys may serve the request. UX hint only.
* **`GET /gateway/v1/meta/:requestId`**: metadata retrieval endpoint with
  orgId-scoped Redis keys, UUID v4 validation, 300s TTL.
* **Semantic cache**: deferred to a future milestone.

## M3 — Cutover [#m3--cutover]

**Status:** Shipped

* **Worker decommissioned**: the legacy Cloudflare Worker (async logging path)
  is no longer deployed. The Hono gateway on Cloud Run is the sole request path.
* **Unified gateway**: all traffic routes through `gateway.corevalue.dev` with
  the full feature set (caching, rate limiting, fallbacks, real cost).
* **Single routing codebase**: `MODEL_PREFIX_MAP` and the 12-step pipeline are
  the single source of truth — no more duplicated Worker routing logic.

## M2 — Parity [#m2--parity]

**Status:** Shipped

* **Response mapping**: upstream provider responses mapped to OpenAI-compatible
  format for non-OpenAI providers.
* **Responses API**: `POST /v1/responses` endpoint support.
* **`GET /v1/models`**: OpenAI-compatible model list endpoint (M2 0.7). Returns
  registry models filtered by `requireExplicitRouting`. Cached in-memory for 60s.
* **18 provider prefixes**: expanded `MODEL_PREFIX_MAP` with `cerebras/`,
  `deepinfra/`, `novita/`, `nebius/`, `baseten/`, `chutes/`, `xai/`,
  `canopywave/` (M2 0.8).
* **Prompt expansion**: `Cova-Prompt-Id` header and `body.prompt_id` support for
  prompt-template expansion before forwarding to the provider.

## M1 — Unify [#m1--unify]

**Status:** Shipped

* **Merged W2 gates**: budget gate integrated into the 12-step pipeline
  (step 9). Disallow list check for blocked models.
* **Escrow**: pass-through billing (PTB) credit reservation before forwarding.
  `Cova-Credits-Remaining` set on successful PTB responses.
* **Validation**: request body validation and provider key resolution (BYOK
  first, then PTB) unified in the pipeline.

## M0 — Correctness (0.0a) [#m0--correctness-00a]

**Status:** Shipped

* **Inline cost**: `Cova-Request-Cost` response header — real cost computed from
  extracted usage, always set (even when 0) on non-streaming responses.
* **Dimensions**: `Cova-Provider`, `Cova-Model`, `Cova-Status`, `Cova-Id`
  response headers for request attribution.
* **Response headers**: full `Cova-*` response header assembly in
  `buildResponseHeaders()` (streaming.ts) — `Cova-Gateway-Mode`,
  `Cova-Translation-Warning`, `Cova-Cache-*` headers.

***

## Roadmap (not shipped) [#roadmap-not-shipped]

* **M5+**: semantic cache, Azure OpenAI provider prefix, automated OpenAPI spec
  regeneration for the Hono gateway. These are on the roadmap and have not been
  shipped.


# Availability and Reliability (https://docs.corevalue.dev/references/availability)





CoreValue's AI Gateway is a Hono application deployed on Cloud Run, providing a
scalable and fault-tolerant infrastructure for your LLM requests. The legacy
Cloudflare Worker was decommissioned in the M3 cutover — the Hono gateway is now
the sole request path.

## How CoreValue Ensures High Availability [#how-corevalue-ensures-high-availability]

Our gateway is designed with minimal business logic to maximize performance and
reliability:

* **Selective Business Logic**: Unless headers enabling specific features are included, our gateway does not apply any additional business logic. By default, we simply forward your LLM requests directly to the provider.

* **Robust Error Handling**: We wrap all of our business logic code in comprehensive error handling. No matter what happens, we gracefully fallback to just forwarding the LLM request, ensuring uninterrupted service.

* **Post-Response Logging**: After returning the entire response to you, we send logs to the Core API server to be consumed by a completely separate service. This ensures that logging does not impact the response time of your requests.

**Your requests are handled efficiently and reliably
with CoreValue.**

## Deployment Practices [#deployment-practices]

To maintain the stability and reliability of our gateway, we follow rigorous deployment steps:

1. **Comprehensive Testing**: Before any deployment, we run a suite of integration and unit tests to ensure all functionalities work as intended.

2. **Manual Quality Assurance**: Our team performs manual QA to catch any issues that automated tests might miss.

3. **Code Approval**: All code changes require approval from one of our technical co-founders before deployment.

4. **Gradual Rollout**: We roll out updates gradually using Cloud Run traffic splitting, deploying to a small percentage of traffic at a time.

## Logging Process Overview [#logging-process-overview]

The following sequence diagram illustrates how we log only after the response is returned:

```mermaid
sequenceDiagram
    participant Client
    participant CoreValue Gateway
    participant LLM Provider
    participant Core API Server

    Client ->>+ CoreValue Gateway: Send LLM Request
    CoreValue Gateway ->>+ LLM Provider: Forward Request
    LLM Provider -->>- CoreValue Gateway: Return Response
    CoreValue Gateway -->>- Client: Return Response
    CoreValue Gateway ->>+ Core API Server: Send Logs (After Response)
```

By sending logs to the Core API server only after the response is returned to the client, we ensure that our logging process does not affect the latency or reliability of your applications.

**With the Hono gateway, CoreValue stays out of
your critical path for logging.**

<FaqHeader />

<QuestionsSection />


# Roles and permissions (https://docs.corevalue.dev/references/roles)



{/* Generated by `yarn workspace cova-api gen:roles-doc`. Do not edit by hand. */}

Every route on the CoreValue API names one permission it requires, and every
role is a set of permissions. Both are declared in code, so this page is
rendered from that declaration rather than written beside it.

## Built-in roles [#built-in-roles]

An organisation has three built-in roles. They mean the same thing in every
organisation and cannot be edited.

* **Owner** holds every permission, including the four ownership actions:
  transfer the organisation, delete it, cancel the subscription and remove a
  payment method. There is one owner per organisation, and ownership moves
  only by transfer.
* **Admin** holds everything except `ownership`.
* **Member** reads traffic and annotates it: `traffic.read` and
  `traffic.annotate`.

Every role, and every API key, holds `org.read`: the organisation you belong
to, its entitlements and settings reads. It is what lets a person open the
console at all, and no role can drop it.

**API keys carry no role.** A key holds exactly what a member holds and nothing
above it, whichever role the person who created it has. What a key may do is
bounded separately, by the permissions set on the key itself.

**Custom roles** are an Enterprise feature. The catalogue below is what a
custom role is built from: any permission except `ownership`, which no role
can be given.

## The permission catalogue [#the-permission-catalogue]

| Permission             | What it allows                                                                                                                    | Owner | Admin | Member | API key |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- | :---: | :---: | :----: | :-----: |
| `org.read`             | Open the organisation: its settings, entitlements and terms. Every role holds this.                                               |   ✓   |   ✓   |    ✓   |    ✓    |
| `traffic.read`         | Dashboards, requests, sessions, metrics, properties, MCP and A2A views.                                                           |   ✓   |   ✓   |    ✓   |    ✓    |
| `traffic.annotate`     | Feedback, scores, tags and a request's custom property.                                                                           |   ✓   |   ✓   |    ✓   |    ✓    |
| `org.settings.write`   | Organisation details, onboarding, PII and retention config, saved views.                                                          |   ✓   |   ✓   |    —   |    —    |
| `org.members.write`    | Invite, change roles, remove; teams and their membership; visibility mode.                                                        |   ✓   |   ✓   |    —   |    —    |
| `spend_controls.write` | Budgets at every scope, key caps, alerts, downgrade mappings, rate limits, prices and declared value.                             |   ✓   |   ✓   |    —   |    —    |
| `gateway_config.write` | Fallback chains, cache and routing.                                                                                               |   ✓   |   ✓   |    —   |    —    |
| `keys.write`           | API keys, proxy keys and their scopes.                                                                                            |   ✓   |   ✓   |    —   |    —    |
| `provider_keys.write`  | Provider keys and the vault.                                                                                                      |   ✓   |   ✓   |    —   |    —    |
| `egress.write`         | Webhooks and integrations: data leaving the organisation.                                                                         |   ✓   |   ✓   |    —   |    —    |
| `governance.write`     | Access rules, MCP and A2A registries.                                                                                             |   ✓   |   ✓   |    —   |    —    |
| `reports.read`         | Chargeback, unit economics, value, the budget summary, the breach log, invoices, spend by model, and customer budgets and prices. |   ✓   |   ✓   |    —   |    —    |
| `audit.read`           | Read the organisation's audit log and export it.                                                                                  |   ✓   |   ✓   |    —   |    —    |
| `billing.write`        | Checkout, upgrades, add-ons and payment setup.                                                                                    |   ✓   |   ✓   |    —   |    —    |
| `playground.use`       | Chat with models and MCP tools in the console playground, spending as yourself.                                                   |   ✓   |   ✓   |    —   |    —    |
| `ownership`            | Transfer, delete the organisation, cancel the subscription, remove a payment method. The owner only; no role can hold it.         |   ✓   |   —   |    —   |    —    |

## Routes by permission [#routes-by-permission]

Which API routes require each permission. A path segment in braces is a
parameter. A route that does not appear here is not gated on a role.

### `org.read` [#orgread]

* `GET /v1/a2a/agents`
* `GET /v1/api-keys/scopes/entitlement`
* `GET /v1/governance/access-default`
* `GET /v1/governance/access-rules`
* `POST /v1/governance/access-rules/audit`
* `GET /v1/governance/entitlement`
* `GET /v1/mcp-connections`
* `POST /v1/mcp-connections/authorize`
* `POST /v1/mcp-connections/consent`
* `GET /v1/mcp-connections/settings`
* `DELETE /v1/mcp-connections/{grantId}`
* `GET /v1/mcp/servers`
* `POST /v1/mcp/upstream/connect`
* `GET /v1/mcp/upstream/connect/{handle}`
* `POST /v1/mcp/upstream/connect/{handle}/continue`
* `GET /v1/mcp/upstream/connections`
* `DELETE /v1/mcp/upstream/connections/{connectionId}`
* `POST /v1/organization/user/accept_terms`
* `GET /v1/organization/{id}/permissions/me`
* `GET /v1/organization/{id}/roles`
* `GET /v1/organization/{id}/roles/entitlement`
* `GET /v1/organization/{id}/sso/entitlement`
* `GET /v1/team`
* `GET /v1/team/settings/visibility`
* `GET /v1/team/{teamId}/members`
* `GET /v1/tool-rates`

### `traffic.read` [#trafficread]

* `POST /v1/a2a/calls`
* `POST /v1/a2a/summary`
* `POST /v1/budget/actions/breakdown`
* `POST /v1/budget/actions/over-time`
* `POST /v1/dashboard/scores/query`
* `POST /v1/dashboard/verdicts`
* `POST /v1/mcp/filter-options`
* `POST /v1/mcp/summary`
* `POST /v1/mcp/tool-calls`
* `POST /v1/metrics/activeUsers`
* `POST /v1/metrics/averageLatency`
* `POST /v1/metrics/averageTimeToFirstToken`
* `POST /v1/metrics/averageTokensPerRequest`
* `POST /v1/metrics/cacheSavings`
* `POST /v1/metrics/costEfficiency`
* `POST /v1/metrics/costOverTime`
* `POST /v1/metrics/costPerTokenOverTime`
* `POST /v1/metrics/country`
* `POST /v1/metrics/errorBreakdown`
* `POST /v1/metrics/errorOverTime`
* `POST /v1/metrics/latencyOverTime`
* `POST /v1/metrics/models`
* `POST /v1/metrics/modelsOverTime`
* `POST /v1/metrics/propertyAggregation`
* `POST /v1/metrics/quantiles`
* `POST /v1/metrics/requestCount`
* `POST /v1/metrics/requestOverTime`
* `POST /v1/metrics/requestStatusOverTime`
* `POST /v1/metrics/spendTrend`
* `POST /v1/metrics/threatsOverTime`
* `POST /v1/metrics/timeToFirstToken`
* `POST /v1/metrics/tokensOverTime`
* `POST /v1/metrics/totalCost`
* `POST /v1/metrics/totalRequests`
* `POST /v1/metrics/totalThreats`
* `POST /v1/metrics/usersOverTime`
* `POST /v1/property/hidden/query`
* `POST /v1/property/properties/over-time`
* `POST /v1/property/query`
* `POST /v1/property/{propertyKey}/search`
* `POST /v1/property/{propertyKey}/top-costs/query`
* `POST /v1/property/{propertyKey}/top-requests/query`
* `POST /v1/providers`
* `POST /v1/request/count/query`
* `POST /v1/request/query`
* `POST /v1/request/query-clickhouse`
* `POST /v1/request/query-ids`
* `POST /v1/request/{requestId}/assets/{assetId}`
* `POST /v1/security/pii/over-time`
* `POST /v1/security/pii/recent`
* `POST /v1/security/pii/summary`
* `POST /v1/session/query`
* `POST /v1/spend/by`
* `POST /v1/test/gateway-request`
* `POST /v1/user/metrics-overview/query`
* `POST /v1/user/metrics/query`
* `POST /v1/user/query`

### `traffic.annotate` [#trafficannotate]

* `POST /v1/request/{requestId}/feedback`
* `PUT /v1/request/{requestId}/property`
* `POST /v1/request/{requestId}/score`
* `POST /v1/session/{sessionId}/feedback`
* `POST /v1/session/{sessionId}/tag`

### `org.settings.write` [#orgsettingswrite]

* `POST /v1/filter`
* `DELETE /v1/filter/{id}`
* `PATCH /v1/filter/{id}`
* `PUT /v1/mcp-connections/settings`
* `POST /v1/organization/body-retention-config`
* `POST /v1/organization/onboard`
* `POST /v1/organization/pii-config`
* `POST /v1/organization/update_onboarding`
* `POST /v1/organization/{id}/create_filter`
* `DELETE /v1/organization/{id}/sso`
* `GET /v1/organization/{id}/sso`
* `PUT /v1/organization/{id}/sso`
* `POST /v1/organization/{id}/sso/test`
* `POST /v1/organization/{id}/sso/verify-domain`
* `POST /v1/organization/{id}/update`
* `POST /v1/organization/{id}/update_filter`
* `POST /v1/property/hide`
* `POST /v1/property/restore`

### `org.members.write` [#orgmemberswrite]

* `POST /v1/organization/{id}/add_member`
* `DELETE /v1/organization/{id}/remove_member`
* `POST /v1/organization/{id}/roles`
* `PATCH /v1/organization/{id}/roles/{roleId}`
* `POST /v1/organization/{id}/roles/{roleId}/archive`
* `POST /v1/organization/{id}/update_member`
* `POST /v1/team`
* `PUT /v1/team/settings/visibility`
* `DELETE /v1/team/{teamId}`
* `PUT /v1/team/{teamId}`
* `DELETE /v1/team/{teamId}/member`
* `POST /v1/team/{teamId}/member`

### `spend_controls.write` [#spend_controlswrite]

* `DELETE /v1/budget/alert-config`
* `PUT /v1/budget/alert-config`
* `POST /v1/budget/breach`
* `DELETE /v1/budget/customer`
* `PUT /v1/budget/customer`
* `DELETE /v1/budget/customer-price`
* `PUT /v1/budget/customer-price`
* `POST /v1/budget/downgrade-mappings`
* `DELETE /v1/budget/downgrade-mappings/{mappingId}`
* `PUT /v1/budget/member/{memberUserId}`
* `POST /v1/budget/notify`
* `PUT /v1/budget/velocity-limit`
* `DELETE /v1/budget/velocity-limit/{id}`
* `PUT /v1/budget/window-limit`
* `DELETE /v1/budget/window-limit/{id}`
* `PUT /v1/budget/window-settings`
* `POST /v1/chargeback/{month}/close`
* `DELETE /v1/model-prices`
* `PUT /v1/model-prices`
* `PUT /v1/model-prices/import`
* `POST /v1/rate-limits`
* `DELETE /v1/rate-limits/{ruleId}`
* `PUT /v1/rate-limits/{ruleId}`
* `POST /v1/recommendations/{recommendationId}/apply`
* `POST /v1/recommendations/{recommendationId}/dismiss`
* `PUT /v1/tool-rates`
* `DELETE /v1/tool-rates/{id}`
* `DELETE /v1/value/rate`
* `PUT /v1/value/rate`

### `gateway_config.write` [#gateway_configwrite]

* `POST /v1/gateway/fallback-chains`
* `DELETE /v1/gateway/fallback-chains/{chainId}`
* `PUT /v1/gateway/fallback-chains/{chainId}`

### `keys.write` [#keyswrite]

* `POST /v1/api-keys`
* `POST /v1/api-keys/proxy-key`
* `PUT /v1/api-keys/proxy-key/{proxyKeyId}/scopes`
* `DELETE /v1/api-keys/{apiKeyId}`
* `PATCH /v1/api-keys/{apiKeyId}`
* `PUT /v1/api-keys/{apiKeyId}/scopes`
* `GET /v1/mcp-connections/org`
* `GET /v1/mcp/upstream/connections/org`

### `provider_keys.write` [#provider_keyswrite]

* `POST /v1/api-keys/provider-key`
* `DELETE /v1/api-keys/provider-key/{providerKeyId}`
* `GET /v1/api-keys/provider-key/{providerKeyId}`
* `PATCH /v1/api-keys/provider-key/{providerKeyId}`
* `GET /v1/api-keys/provider-keys`
* `POST /v1/vault/add`
* `GET /v1/vault/key/{providerKeyId}`
* `GET /v1/vault/keys`
* `PATCH /v1/vault/update/{id}`

### `egress.write` [#egresswrite]

* `GET /v1/integration`
* `POST /v1/integration`
* `GET /v1/integration/slack/channels`
* `GET /v1/integration/slack/settings`
* `GET /v1/integration/type/{type}`
* `GET /v1/integration/{integrationId}`
* `POST /v1/integration/{integrationId}`
* `POST /v1/integration/{integrationId}/stripe/test-meter-event`
* `GET /v1/webhooks`
* `POST /v1/webhooks`
* `DELETE /v1/webhooks/{webhookId}`
* `POST /v1/webhooks/{webhookId}/test`

### `governance.write` [#governancewrite]

* `POST /v1/a2a/agents`
* `DELETE /v1/a2a/agents/{id}`
* `PATCH /v1/a2a/agents/{id}`
* `PUT /v1/a2a/agents/{id}/keys`
* `POST /v1/a2a/agents/{id}/probe`
* `PUT /v1/governance/access-default`
* `POST /v1/governance/access-rules`
* `DELETE /v1/governance/access-rules/{id}`
* `PATCH /v1/governance/access-rules/{id}`
* `PUT /v1/mcp/platform-servers/{serverId}`
* `POST /v1/mcp/servers`
* `POST /v1/mcp/servers/discover`
* `DELETE /v1/mcp/servers/{id}`
* `PATCH /v1/mcp/servers/{id}`
* `POST /v1/mcp/servers/{id}/probe`

### `reports.read` [#reportsread]

* `GET /v1/budget/breaches`
* `GET /v1/budget/customer-prices`
* `GET /v1/budget/customers`
* `GET /v1/budget/summary`
* `GET /v1/chargeback/{month}`
* `GET /v1/credits/invoices`
* `GET /v1/credits/spend/breakdown`
* `GET /v1/unit-economics`
* `GET /v1/value`
* `GET /v1/value/rates`

### `audit.read` [#auditread]

* `GET /v1/organization/{id}/audit`
* `GET /v1/organization/{id}/audit.csv`

### `billing.write` [#billingwrite]

* `POST /v1/stripe/cloud/checkout-session`
* `POST /v1/stripe/payment-methods/setup-session`
* `DELETE /v1/stripe/subscription/add-ons/{productType}`
* `POST /v1/stripe/subscription/add-ons/{productType}`
* `POST /v1/stripe/subscription/existing-customer/upgrade-to-growth`
* `POST /v1/stripe/subscription/new-customer/upgrade-to-growth`
* `POST /v1/stripe/subscription/undo-cancel-subscription`

### `playground.use` [#playgrounduse]

* `GET /v1/playground/options`
* `POST /v1/playground/servers/{serverId}/tools-check`
* `POST /v1/playground/step`

### `ownership` [#ownership]

* `DELETE /v1/organization/delete`
* `PUT /v1/organization/{id}/sso/enforce`
* `POST /v1/organization/{id}/update_owner`
* `DELETE /v1/stripe/payment-methods/{paymentMethodId}`
* `POST /v1/stripe/subscription/cancel-subscription`
* `POST /v1/stripe/subscription/manage-subscription`
* `POST /v1/stripe/subscription/migrate-to-pro`

***

Generated from `server/core/src/lib/authz/permissions.ts` and `middleware/rbac.ts`; do not edit by hand.


# Key concepts (https://docs.corevalue.dev/getting-started/key-concepts)



## CoreValue [#corevalue]

| Term              | What it means                                                                                                                                                                           |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **AI Gateway**    | The address your applications send AI requests to, `https://gateway.corevalue.dev`. It passes each request to the provider, sends the answer back unchanged, and records what happened. |
| **AI Governance** | Seeing, controlling and reporting on the requests the AI Gateway carries — usage, budgets, access rules, reports and your organisation's settings.                                      |
| **Console**       | The website where you do all of that: [app.corevalue.dev](https://app.corevalue.dev).                                                                                                   |
| **Provider**      | A company whose AI models you call — OpenAI, Anthropic, Google, Groq and so on.                                                                                                         |
| **Request**       | One call from your application to a model, such as one chat completion. Every request that goes through the gateway appears on the [Requests](/features/cost-tracking/requests) page.   |

## Who is who [#who-is-who]

| Term             | What it means                                                                                                                                                                                                                                                                                                                                                            |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Organisation** | Your company's account in CoreValue. Keys, budgets, members and requests all belong to one organisation. You can belong to more than one and switch between them from the top bar.                                                                                                                                                                                       |
| **Member**       | A person who can sign in to your organisation's console.                                                                                                                                                                                                                                                                                                                 |
| **Role**         | What a member is allowed to do. **Owner** can do everything (there is one per organisation). **Admin** can do everything except the owner-only actions, such as transferring ownership. **Member** can view requests and spend but not change settings. Enterprise organisations can also create **custom roles**. → [Members and roles](/features/organization/members) |
| **Team**         | A group of members and keys inside your organisation — usually a product team or a service. A team can have its own monthly budget, and its members can be limited to seeing their team's requests. → [Teams](/features/organization/teams)                                                                                                                              |
| **Customer**     | One of *your* customers — a company or account your product serves. You tell CoreValue which customer a request was for with the `Cova-Customer-Id` header, and can then see and cap spend per customer. Customers never sign in to CoreValue.                                                                                                                           |
| **End user**     | A person using *your* product. You tell CoreValue who with the `Cova-User-Id` header, to see requests per user and to set per-user rate limits. End users never sign in to CoreValue.                                                                                                                                                                                    |

<Callout type="info">
  **Member vs. customer vs. end user.** A *member* is someone at your company using the CoreValue console. A *customer* and an *end user* are people on the other side of your product — CoreValue only knows about them because your application names them in a header. → [Tag requests with users and customers](/features/cost-attribution)
</Callout>

## Keys [#keys]

There are two kinds of key, and they go in opposite directions.

| Term              | What it means                                                                                                                                                                                                                                                                                                                          |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Provider key**  | Your key from OpenAI, Anthropic or another provider. You add it to CoreValue once, in the console; CoreValue uses it to call the provider for you. Your applications never see it, and the provider bills you directly — this is often called **BYOK** ("bring your own key"). → [Provider keys](/features/organization/provider-keys) |
| **CoreValue key** | The key your application sends to the gateway, in place of the provider key. It starts `sk-cova-`. It comes in two kinds, below.                                                                                                                                                                                                       |
| **API key**       | A CoreValue key for an application or a person. Its spend counts towards its team's budget and the organisation budget.                                                                                                                                                                                                                |
| **Virtual key**   | A CoreValue key that can also carry **its own limits**: a monthly spend cap and request-count limits. Give each service its own virtual key when you want to cap that service on its own. It starts `sk-cova-proxy-`.                                                                                                                  |

Both kinds are used the same way: put the key where your SDK expects the provider key, and point the SDK at the gateway. Both are shown **once**, when you create them — copy them straight away. → [API keys and virtual keys](/features/organization/api-keys)

## Money and limits [#money-and-limits]

| Term           | What it means                                                                                                                                                                                                                    |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Cost**       | What a request cost at the provider's list price, in US dollars. It is shown on every request and returned in the `Cova-Request-Cost` header. → [How cost is calculated](/additional/how-cost-is-calculated)                     |
| **Budget**     | A monthly spending limit for the organisation, a team, a member or a customer. It resets on the 1st of each month.                                                                                                               |
| **Cap**        | A monthly spending limit on a single virtual key.                                                                                                                                                                                |
| **Action**     | What happens when a budget is reached: **alert** (let the request through and tell you), **downgrade** (switch to a cheaper model) or **block** (refuse the request). → [Budgets & Limits](/features/budgets/budgets-and-limits) |
| **Rate limit** | A limit on how many requests are accepted in a time window, such as 1,000 an hour. It protects against runaway loops rather than overspend. → [Rate limits](/features/rate-limits)                                               |
| **Breach**     | A time a budget acted. Every breach is listed in the console.                                                                                                                                                                    |

## Tags you can add to a request [#tags-you-can-add-to-a-request]

| Term         | What it means                                                                                                                                                                                                                                         |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Header**   | An extra line of information sent with a request. CoreValue's headers all start `Cova-`. → [Headers](/api-reference/headers)                                                                                                                          |
| **Property** | Any label you choose, sent as `Cova-Property-<Name>: <value>` — for example `Cova-Property-Env: production`. Requests can be filtered by it. For the feature a request was for, use `Cova-Feature-Id` instead: the Spend page breaks cost down by it. |


# Set up your organisation (https://docs.corevalue.dev/getting-started/set-up)



This is the setup you do in the console. One step needs a developer: changing
two lines of code so your applications send their AI requests through
CoreValue. Everything else happens here.

<Steps>
  <Step>
    ### Sign in and name your organisation [#sign-in-and-name-your-organisation]

    Sign in at [app.corevalue.dev](https://app.corevalue.dev/signin). The first
    time, you are asked to name your organisation. Keys, budgets, requests and
    reports all belong to it.
  </Step>

  <Step>
    ### Add your provider keys [#add-your-provider-keys]

    On **Gateway → Provider keys**, add a key for each AI provider you use, such as
    OpenAI, Anthropic or Google. CoreValue uses it to call the provider on your
    behalf, and the provider keeps billing you as it does today. Your applications
    never see it. See [Provider keys](/features/organization/provider-keys).
  </Step>

  <Step>
    ### Create CoreValue keys [#create-corevalue-keys]

    On **Governance → Keys**, create the keys your applications will use instead of
    provider keys. How you split them decides how you can split spend:

    * **One key per service or environment**, so each can be capped on its own.
    * **A team on each key**, so spend lands on that team for budgets and chargeback.
    * **A key scoped to one customer**, if you resell AI, so every request on it is
      counted for that customer.

    Each key is shown once, when it is created. See
    [API keys](/features/organization/api-keys).
  </Step>

  <Step>
    ### Add your team [#add-your-team]

    On **Governance → Access**, click **Invite member** and choose a role. Someone
    who already has a CoreValue account is added straight away. Create teams below
    the members list, and add people to them. See
    [Members](/features/organization/members) and [Roles](/features/organization/roles).
  </Step>

  <Step>
    ### Set a first budget [#set-a-first-budget]

    On **Budgets & Limits → Organization**, set a **Monthly Budget** a little above
    what you expect to spend, with the action **Alert only**, and add yourself on
    **Notifications → Budget alerts**. Nothing is refused while you learn what
    normal looks like. See [Budget configuration](/guides/budget-configuration).
  </Step>

  <Step>
    ### Hand the keys to your developer [#hand-the-keys-to-your-developer]

    Send your developer the key you created and the developer
    [Quickstart](/getting-started/quickstart). It is two lines of code: the
    gateway's base URL and the CoreValue key.
  </Step>

  <Step>
    ### See your first requests [#see-your-first-requests]

    Once requests arrive, **Requests** lists each one with its model, tokens and
    cost, and **Overview** shows spend so far this month. See
    [Overview](/features/cost-tracking/dashboard).
  </Step>
</Steps>

## Next [#next]

* [For SaaS companies](/start-here/saas): cost and margin per customer.
* [For AI-native teams](/start-here/ai-native-teams): spend and budgets per team.


# Budget configuration (https://docs.corevalue.dev/guides/budget-configuration)



This guide takes you from no budgets to budgets you can rely on. It uses the
controls on **Budgets & Limits**; the page-by-page reference is
[Budgets](/features/budgets/budgets-and-limits).

<Callout type="info">
  **Hand this to your developer:** budgets act on CoreValue keys, so each
  service needs its own key and the gateway's base URL. See the
  [OpenAI](/getting-started/openai) or [Anthropic](/getting-started/anthropic)
  quickstart, and [Errors](/api-reference/errors) for the `429` a refused
  request returns.
</Callout>

## How the layers fit together [#how-the-layers-fit-together]

A request is checked against every budget that covers it, and the first one
that is full decides what happens.

| Layer            | Caps                                      | Use it for                                                                    |
| ---------------- | ----------------------------------------- | ----------------------------------------------------------------------------- |
| **Organisation** | All spend                                 | A backstop for the month.                                                     |
| **Team**         | Every key assigned to the team            | Each team's share, and chargeback.                                            |
| **Member**       | Every key a member owns                   | People with their own keys, such as developers using coding assistants.       |
| **Customer**     | Requests carrying that `Cova-Customer-Id` | Your own customers. See [Per-customer budgets](/guides/per-customer-budgets). |
| **Key**          | One CoreValue key                         | A single service or environment.                                              |

All of them are monthly and reset on the 1st (UTC). Spend already made this
month counts as soon as you set one.

## Choose what happens at the budget [#choose-what-happens-at-the-budget]

| Action              | What it does                                                                                                              | Pick it when                                        |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| **Alert only**      | Serves the request and records the breach. Alert recipients are emailed when an organisation or team budget acts.         | You are learning what normal looks like.            |
| **Downgrade model** | Rewrites the request to a cheaper model from your downgrade mappings, then serves it. Organisation and team budgets only. | Running on a cheaper model is better than stopping. |
| **Block requests**  | Refuses with HTTP `429` before the provider is called.                                                                    | The cap is a hard limit.                            |

Member and customer budgets stop spend only with **Refuse the request**.
**Block** and **Downgrade** need the Growth plan or higher; **Alert only** works
on every plan.

## A setup that works [#a-setup-that-works]

<Steps>
  <Step>
    ### Give each service and environment its own key [#give-each-service-and-environment-its-own-key]

    Create a key per service, and separate keys for production, staging and
    development, on **Governance → Keys**. Spend can then be capped and read per key,
    and a test loop in staging cannot use production's budget.
  </Step>

  <Step>
    ### Set an organisation budget on Alert only [#set-an-organisation-budget-on-alert-only]

    On **Budgets & Limits → Organization**, set a **Monthly Budget** above what you
    expect to spend, with **Action When Budget Exceeded** on **Alert only**. The
    **Month-End Forecast** on the same tab shows where the month is heading.
  </Step>

  <Step>
    ### Get warned early [#get-warned-early]

    On **Notifications → Budget alerts**, add the people who should hear about it
    and set **Warn before the budget is reached**, for example `80%, 95%`.
  </Step>

  <Step>
    ### Cap the keys that can run away [#cap-the-keys-that-can-run-away]

    On **Budgets & Limits → Keys**, click **Edit caps** on each service key and add
    a **Cost** limit for the month. Add a **Count** limit too, for example
    *1,000 requests per 1 hour*, on keys used by agents or batch jobs. A key over
    either limit gets `429` while every other key carries on.

    To cap every key at once, set **Default cap per virtual key** on the
    Organization tab. A key's own cap always wins.
  </Step>

  <Step>
    ### Add team budgets [#add-team-budgets]

    On **Budgets & Limits → Teams**, give each team a **Monthly budget**, and assign
    keys to teams on the Keys tab. See [Team chargeback](/guides/team-chargeback).
  </Step>

  <Step>
    ### Watch a week, then tighten [#watch-a-week-then-tighten]

    After a week, open **Budgets & Limits → Actions** to see what each budget
    would have done. When the numbers match what you expect, lower the budgets to
    what you can afford and switch the ones that matter to **Block requests**, or
    to **Downgrade model** with a mapping for every model you use.
  </Step>
</Steps>

## Stop a runaway loop [#stop-a-runaway-loop]

A monthly budget catches overspend at the end of the month; a loop can spend a
lot in an hour. Three controls catch it sooner:

* **A Count limit on the key**, as above, refuses once the key makes too many
  requests in a window.
* **[Rate limits](/features/rate-limits)** cap requests or cents per window for
  the whole organisation, per user or per tag.
* **[Velocity limits](/features/velocity-limits)*&#x2A; and
  &#x2A;*[daily ceilings](/features/budgets/budget-windows)** cap spend per minute,
  hour or day. Both are in preview: they record what they would have refused
  but do not refuse yet.

## Things to know [#things-to-know]

* Requests already in flight when a budget fills still complete, so spend can
  land slightly above it.
* A budget change takes effect within about a minute.
* If CoreValue cannot read a budget, the request is served: budgets never take
  your traffic down.
* Changing budgets needs the Owner or Admin role.

## Related [#related]

* [Budgets](/features/budgets/budgets-and-limits): every field on every tab.
* [Budget alerts](/features/budgets/budget-alerts)
* [Per-customer budgets](/guides/per-customer-budgets)
* [Errors](/api-reference/errors): what a refused request looks like.


# Per-customer budgets (https://docs.corevalue.dev/guides/per-customer-budgets)



If you sell an AI product, one heavy customer can cost more than they pay. This
guide sets up three things: every request is **bound** to the customer it
serves, each customer is **gated** by a budget checked before the provider is
called, and each customer's **margin** is visible.

<Callout type="info">
  **Hand this to your developer:** step 1 needs the `Cova-Customer-Id` header on
  every request. See [Tag your traffic](/features/cost-attribution) and the
  [OpenAI](/getting-started/openai) or [Anthropic](/getting-started/anthropic)
  quickstart. Steps 2 and 3 are done in the console.
</Callout>

## 1. Bind requests to customers [#1-bind-requests-to-customers]

Send the customer's ID with every request in the `Cova-Customer-Id` header. Use
the ID your own system already has.

```python
client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Hello!"}],
    extra_headers={"Cova-Customer-Id": customer.id},
)
```

IDs are matched without regard to case, and blank values are ignored.

### Make the binding hard to get wrong [#make-the-binding-hard-to-get-wrong]

The header is whatever your code sends. To stop a key from charging the wrong
customer, scope it: on **Governance → Keys**, click **Scopes** on the key and
add the customers it may act for.

| Key scoped to          | Request without the header              | Request naming another customer     |
| ---------------------- | --------------------------------------- | ----------------------------------- |
| No customers (default) | Not bound to any customer               | Allowed                             |
| Exactly one customer   | Bound to that customer                  | Refused, `403 customer_not_allowed` |
| Several customers      | Refused, `400 customer_header_required` | Refused, `403 customer_not_allowed` |

A key per customer, each scoped to that one customer, binds every request with
no header at all. See [Key scopes](/features/access/key-scopes).

## 2. Gate each customer with a budget [#2-gate-each-customer-with-a-budget]

<Steps>
  <Step>
    Go to **Budgets & Limits → Customers**. Every customer ID seen this month is
    listed with its spend.
  </Step>

  <Step>
    Click **Add limit**, choose the **Customer**, and click **Continue**.
  </Step>

  <Step>
    Enter the &#x2A;*Monthly budget (USD)**, set **At the budget** to **Refuse the
    request**, and click **Save**.
  </Step>
</Steps>

Once the customer's spend reaches the budget, their requests get HTTP `429`
before the provider is called, until the month rolls over (UTC):

```json
{
  "error": {
    "message": "Customer \"acme-corp\" budget of $50.00 exceeded. Current spend: $50.02. …",
    "type": "rate_limit_error"
  }
}
```

Other customers are unaffected. Catch the `429` and show the customer a message,
offer an upgrade, or queue the work.

<Callout>
  To keep a customer working past their budget instead, choose **Downgrade the
  model**: their requests move to a cheaper model from your downgrade mappings.
  **Alert, and allow** is not enforced on customer budgets yet and lets
  requests through.
</Callout>

A customer can be picked in **Add limit** after its first request this month,
so you can budget a new customer as soon as they start using your product.

## 3. See each customer's margin [#3-see-each-customers-margin]

Record what you charge, and the Customers tab shows revenue and margin next to
cost. Click **Price** on a customer's row, or **Default price** for everyone
without their own:

| Field                   | What it means                                                       |
| ----------------------- | ------------------------------------------------------------------- |
| **Monthly fee (USD)**   | A fixed amount the customer pays each month.                        |
| **Markup on usage (%)** | What you charge on top of their AI cost.                            |
| **Margin floor (%)**    | The lowest margin you accept.                                       |
| **At the floor**        | **Alert me, keep serving**, or **Refuse this customer's requests**. |

Revenue is the monthly fee plus cost marked up; margin is revenue minus cost, as
a share of revenue. The margin turns amber within ten points of the floor and
red below it. An email alert is sent when a customer comes within ten points
of the floor, and again when their margin falls below it, once each per month.

Pricing and margin need the Growth or Enterprise plan. For cost per customer
over time, see [Unit economics](/features/reports/unit-economics), and for a
month's figures by customer, [Chargeback](/features/reports/chargeback) grouped
by **Customer**.

## Who can do this [#who-can-do-this]

Seeing customer budgets and margins needs the `reports.read` permission;
changing them needs `spend_controls.write`. Owners and admins have both. See
[Roles and permissions](/references/roles).

## Related [#related]

* [Budget configuration](/guides/budget-configuration)
* [Budgets](/features/budgets/budgets-and-limits#set-a-customer-budget)
* [Cost attribution](/features/cost-attribution)


# Team chargeback (https://docs.corevalue.dev/guides/team-chargeback)



Chargeback answers the question finance asks every month: which team spent
what on AI. CoreValue answers it from the key each request used, so the split
is decided by your setup, not by what each application chooses to report.

<Callout type="info">
  **Hand this to your developers:** each team's services should use that team's
  CoreValue key. See the [OpenAI](/getting-started/openai),
  [Anthropic](/getting-started/anthropic) or [Claude Code](/getting-started/claude-code)
  quickstart.
</Callout>

## 1. Create the teams [#1-create-the-teams]

Go to **Governance → Access**, and below the members list click **Create team**.

| Field                         | What it does                                                                                                  |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Name**                      | The team's name, for example `Platform`.                                                                      |
| **Monthly budget (optional)** | The team's ceiling for the month.                                                                             |
| **At the budget**             | **Alert only**, **Downgrade to a cheaper model** or **Block requests**, or inherit the organisation's action. |

Click the member count on a team's row (for example **3 members**) to add the people in it.

## 2. Give each team its own keys [#2-give-each-team-its-own-keys]

Spend belongs to the team of the key that made the request. On **Governance →
Keys**, create keys with **Generate API key** or **Create virtual key** and pick
the **Team**. For an existing key, set its team in the key's edit dialog, or
with **Edit caps** on **Budgets & Limits → Keys**.

<Callout type="info">
  The `Cova-Team-Id` header is recorded on the request for your own filtering,
  but chargeback and team budgets always use the key's team. A request cannot
  move its spend to another team by sending a different header.
</Callout>

## 3. Set team budgets [#3-set-team-budgets]

With a **Monthly budget** on each team, a team that runs out is stopped,
downgraded or alerted on its own while other teams carry on. See
[Budget configuration](/guides/budget-configuration).

## 4. Send finance the report [#4-send-finance-the-report]

Go to **Reports → Chargeback**.

* **Month**: any of the last twelve calendar months (UTC).
* **Group by**: **Team**, **Customer**, **Model** or **Flat**.
* **Download CSV**: the same rows with prompt and completion tokens, named
  `chargeback-YYYY-MM.csv` (`-provisional` while the month is open).

The report shows cost and request counts per team, with revenue and margin
where you have set [customer prices](/guides/per-customer-budgets#3-see-each-customers-margin).

**Close month** freezes a month's figures so a later download matches what
finance received. Months are also closed by a monthly job, and the report shows
who closed it and when.

<Callout type="info">
  Chargeback covers LLM requests. The cost of MCP tool calls and agent calls is
  not included in its totals yet.
</Callout>

## Who can do this [#who-can-do-this]

Viewing the report needs the `reports.read` permission; closing a month needs
`spend_controls.write`; creating teams needs `org.members.write`. Owners and
admins have all three. See [Roles and permissions](/references/roles).

## Related [#related]

* [Chargeback](/features/reports/chargeback)
* [Teams](/features/organization/teams)
* [Budgets](/features/budgets/budgets-and-limits#set-a-team-budget)


# Your own model prices (Preview) (https://docs.corevalue.dev/features/model-prices)





<Callout type="warn">
  **Preview: saved, not yet applied.** You can set and save your own model prices today, but the gateway does not use them yet. Every request is still priced from CoreValue's price registry. The tab says so: &#x2A;Prices you set here are saved now and take effect when organisation pricing is enabled for the gateway.*
</Callout>

CoreValue prices each request from its registry of provider list prices. If your contract with a provider has different rates, or you use a model the registry does not carry, you can record your own rates on the **Model prices** tab. Once organisation pricing is switched on, requests served by your own provider keys will be priced at them.

<img alt="Budgets & Limits › Model prices" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                                     |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Budgets & Limits** → **Model prices** (`/budgets?tab=model-prices`).                                                                           |
| **Role**    | Adding, changing, importing or removing prices needs `spend_controls.write`, which owners and admins hold. See [Roles and permissions](/references/roles). |
| **Plan**    | Growth or Enterprise. On another plan **Add model** and **Import JSON** are disabled; prices set earlier are kept and can be removed.                      |

## What the prices will apply to [#what-the-prices-will-apply-to]

* **Only requests on your own provider keys.** Requests paid with CoreValue credits are always priced from the registry.
* **Only the exact provider and model.** A price applies to requests for that provider and that provider's model ID.

## Read the table [#read-the-table]

The table lists the registry's models with your prices layered on top. Use the provider menu (**All providers** or one provider) and **Only my prices** to narrow it.

| Column                          | What it means                                                                                                                                                                                                                       |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Provider**                    | The provider the price is for.                                                                                                                                                                                                      |
| **Model**                       | The model ID as the provider names it.                                                                                                                                                                                              |
| **Source**                      | **Registry** (CoreValue's list price), **Your price** (you override a registry model) or **Added by you** (a model the registry does not carry). **registry changed** means the registry's rate has moved since you set your price. |
| **Input $/1M**, **Output $/1M** | Rates per million input and output tokens.                                                                                                                                                                                          |
| **Per 1k+1k tokens**            | What a request of 1,000 input and 1,000 output tokens would cost at these rates.                                                                                                                                                    |

## Set a price [#set-a-price]

1. Click **Override** on a registry row, **Edit** on one of yours, or **Add model** for a model that is not listed.

2. Fill in the dialog:

   | Field                                                                      | What it means                                                                           |
   | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
   | **Provider**                                                               | Chosen from the list. Fixed when you edit an existing row.                              |
   | **Model id**                                                               | The model ID as the provider names it. Fixed when you edit an existing row.             |
   | **Input ($ / 1M tokens)**, &#x2A;*Output ($ / 1M tokens)**                 | Your rates. For a registry model the registry's rate is shown under each field.         |
   | **Cache read ($ / 1M, optional)**, &#x2A;*Cache write ($ / 1M, optional)** | Rates for cached input, where the provider charges them. Blank means the same as input. |

3. Check the preview line, which compares a 1,000 + 1,000 token request at your rates with the registry's.

4. Click **Save price**.

To go back to the registry rate, or delete a model you added, open it and click **Remove price**.

<Callout type="info">
  Take rates from your signed provider agreement or invoice, and have a second person check them before saving. A wrong rate is applied silently to every matching request once organisation pricing is on.
</Callout>

## Export and import [#export-and-import]

* **Export JSON** downloads your own prices as `model-prices.json`. Do this before a large change, so you can restore the previous set.
* **Import JSON** uploads a JSON array of prices in the same shape, with `provider`, `provider_model_id`, `input_usd_per_million`, `output_usd_per_million`, and optionally `cache_read_usd_per_million` and `cache_write_usd_per_million`. Every row is checked first; if any row is invalid, nothing in the file is saved.

## Related [#related]

* [Provider keys](/features/organization/provider-keys)
* [Spend](/features/cost-tracking/spend)


# Rate limits (https://docs.corevalue.dev/features/rate-limits)



# Rate Limiting [#rate-limiting]

CoreValue Gateway applies two layers of rate limiting: a global sliding window and a configurable token bucket.

<Callout type="info">
  **Tier:** Global rate limiting available on all tiers. Per-org bucket
  configuration requires Growth tier or higher.
</Callout>

## Layer 1: Global Sliding Window [#layer-1-global-sliding-window]

Always applied. Prevents DoS at the org level.

* **Algorithm:** Redis sorted sets (sliding window)
* **Scope:** Per-org, per-segment (global, user, model)
* **Default:** 1000 requests per 60 seconds
* **Response:** 429 with `Retry-After` header (seconds)

## Layer 2: Token Bucket [#layer-2-token-bucket]

Configurable per-org. Supports count-based or cost-based limits.

* **Algorithm:** Token bucket with lazy refill (Lua script for atomicity)
* **Units:** "request" (count-based) or "cents" (cost-based)
* **Scope:** Per-org, per-segment-type (global, user, property, model)
* **Configuration:** Org-level config or header-based via `Cova-RateLimit-Policy`

### Segments [#segments]

| Segment | Scope              | Header                |
| ------- | ------------------ | --------------------- |
| global  | All requests       | —                     |
| user    | Per end-user       | `Cova-User-Id`        |
| prop    | Per property value | `Cova-Property-*`     |
| model   | Per model          | — (from request body) |

## Header-Based Override [#header-based-override]

Set `Cova-RateLimit-Policy` to override the org-level config for a single request. The header uses a semicolon-delimited format: `quota;w=windowSeconds;u=unit;s=segment` (e.g. `100;w=3600;u=cents` for 100 cents per hour, or `50;w=3600;u=request;s=user` for 50 requests per hour per user). When `w` is omitted the window defaults to 3600 seconds (1 hour).

```bash
curl https://gateway.corevalue.dev/v1/chat/completions \
  -H "Authorization: Bearer sk-cova-..." \
  -H "Content-Type: application/json" \
  -H "Cova-RateLimit-Policy: 100;w=60;u=request" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}'
```

## Response Format [#response-format]

### 429 Rate Limited [#429-rate-limited]

```
HTTP/1.1 429 Too Many Requests
Retry-After: 30
```

## Configuration [#configuration]

Rate limit policies are configured via the CoreValue dashboard or API. The gateway fetches policies from Core and caches them.

<Callout>
  **Related:** [Cova-\* Headers Guide](/api-reference/headers)
  for all request/response headers.
</Callout>


# Velocity limits (https://docs.corevalue.dev/features/velocity-limits)



A monthly budget tells you a key overspent after the month is gone. A **velocity
limit** watches a short, rolling window instead, for example "no more than $25 in
any 5 minutes", so a script stuck in a loop is caught while it is still cheap.

<Callout type="info">
  **Preview.** While velocity limits and session caps are in preview, you can add
  them but they are not checked: no request is refused, no breach is recorded and
  no alert is sent. No limit is set by default.
</Callout>

## What a limit is [#what-a-limit-is]

A limit applies to one **proxy key**, or to one **agent**, over one **window**:

* **Window:** a whole number of minutes, from 1 minute to 1 day (for example 5
  minutes, 1 hour or 1 day).
* **Limit:** an amount in US dollars. You choose it; nothing is filled in for you.
* **Action:** what happens when spend in the window goes above the limit.

| Action             | What it does                                                                              |
| ------------------ | ----------------------------------------------------------------------------------------- |
| **Alert only**     | You are notified when spend in the window goes above the limit. Requests are not refused. |
| **Block requests** | Requests on this key are refused until the window clears.                                 |

You can set more than one window on the same key, for example 5 minutes and 1
hour. A request that would take spend in a window past its limit is the one
that is stopped; a window that reaches exactly the limit is still served.
An organization can have up to 50 velocity limits.

## Limits on an agent [#limits-on-an-agent]

An agent limit counts every request made with any key bound to the agent, as one
window. An agent that calls with three keys has one ceiling, where three key
limits would give it three. Register the agent under **Gateway › Agents** (one
with no endpoint is enough) and bind its keys there.

* **API keys count too.** A key limit applies to proxy keys only; an agent limit
  counts the agent's API keys and proxy keys alike.
* **The agent is read from the key**, never from a header, so a caller cannot
  choose to escape it. A request made with a key bound to no agent is not
  counted by any agent limit.
* **Binding changes take up to a minute** to reach the gateway.
* **MCP tool calls are not priced yet**, so they add nothing to an agent's window.

A refused request is recorded with the rule `velocity:agent:<agent id>:<window>s`,
where the agent id is the agent's internal id, not its name.

## Session caps [#session-caps]

A **session cap** limits what each session on a proxy key may spend in total,
so one run that goes wrong is refused while the key's other sessions carry on.
A session is the requests that carry the same
[`Cova-Session-Id`](/api-reference/headers#cova-session-id-cova-session-name-cova-session-path) header on the same key;
a request without the header is not counted by any session cap.

* **Limit:** an amount in US dollars for the whole session. A session cap always
  blocks: the request that would take the session past it is refused.
* **Idle timeout:** a session that sends no request for this long is forgotten,
  and the same id starts again from zero. **One hour** unless you set another,
  from 5 minutes to 1 day.
* **One per key.** A key has at most one session cap.

<Callout type="warning">
  The session id is whatever your application sends, so a session cap bounds an
  honest or buggy agent, not an adversary: a caller that sends a new id gets a
  fresh cap. Pair it with a key or agent velocity limit, which a caller cannot
  escape that way.
</Callout>

A refused request gets HTTP `429` with no `Retry-After` (waiting does not clear
a session cap), and is recorded with the rule `session_cap:key`. Requests served
on your own provider key are never refused by a session cap. Session caps send
no alerts; refusals show in the Governance panel and on the request.

## What is counted [#what-is-counted]

Spend is counted in dollars, the same way budgets count it: CoreValue reserves
the worst-case cost of a request before it runs, then corrects to the actual
cost when it finishes. Spend on your own provider keys counts toward the
window, because a runaway on your own key costs you just the same. A limit on
a key that uses your own provider key alerts but never blocks.

The window slides: it always covers the most recent stretch of time, so a
limit does not reset at the top of the hour. The counted span can run slightly
longer than the window you set (by at most about a twelfth of it), never
shorter.

## Set a limit [#set-a-limit]

1. Open **Budgets & Limits → Velocity**.
2. Press **Add limit**.
3. Choose whether it applies to a **key**, an **agent** or **each session** on a
   key. For a key or an agent, choose it, the **window**, the **limit** in
   dollars and the **action**. For a session cap, choose the key, the **limit**
   and, if you want other than one hour, the **idle timeout**.
4. Save.

You can switch a limit off without deleting it, edit its amount or action, or
delete it. What it applies to and the window cannot be changed on an existing
limit; add a new one instead. Deleting an agent deletes its limits. Anyone in the organization can see the limits; adding, editing,
switching and deleting need permission to manage spend controls.

## When a request is blocked [#when-a-request-is-blocked]

With **Block requests** and enforcement on, a refused request gets HTTP
`429` in your provider's own error format, with a `Retry-After` header that
says how many seconds until enough spend leaves the window. The limit clears
by itself as the window slides; nobody needs to reset it. The request is
recorded with the rule `velocity:key:<window>s`, for example
`velocity:key:300s` for a 5-minute window.

## If CoreValue cannot check [#if-corevalue-cannot-check]

Velocity limits are a backstop, so they fail open: if the limits cannot be
read or the counter is unavailable, the request is served and CoreValue
logs why. The one exception is an organization whose budget fail mode is set to
fail closed; a blocking limit that could not be checked refuses the request
with `503`.

## Alerts [#alerts]

While limits are in preview, they are not checked, so no breach is recorded
and no alert is sent. Once
enforcement is on, an alert names the key (or the agent), the window, the limit and the spend,
and says whether requests are being refused. It is sent to your budget alert recipients by email.
Webhook delivery of velocity alerts is not available
yet; [webhooks](/features/alerts/webhooks) carry request logs only. A key that keeps breaching sends one alert every
five minutes, not one per request.

## What is not included yet [#what-is-not-included-yet]

* Velocity limits per member, team, customer or organization. Today a velocity
  limit applies to a proxy key or an agent, and a session cap to each session
  on a proxy key.


# For AI-native teams (https://docs.corevalue.dev/start-here/ai-native-teams)



When several teams use AI models, the provider bills the company once and
nobody can say who spent what. CoreValue answers three questions, in this order.

## 1. Who is spending what? [#1-who-is-spending-what]

Give each team, service or developer its own CoreValue key, assigned to a
team. Every request is then counted against that key and team, so **Spend**
shows cost by team, member, key and model with no change to how teams write
code beyond the base URL and key.

<Callout type="info">
  **Hand this to your developers:** the [OpenAI](/getting-started/openai),
  [Anthropic](/getting-started/anthropic) or [Claude Code](/getting-started/claude-code)
  quickstart. Coding assistants count too: Claude Code can be routed
  through CoreValue from its settings file.
</Callout>

See [Spend](/features/cost-tracking/spend) and [Teams](/features/organization/teams).

## 2. How do we keep it in bounds? [#2-how-do-we-keep-it-in-bounds]

Set a monthly budget for the company, each team, each person and each key.
At the budget, CoreValue refuses the request, switches to a cheaper model, or
alerts you, before the provider is called.

See [Budget configuration](/guides/budget-configuration).

## 3. How do we charge it back? [#3-how-do-we-charge-it-back]

**Reports → Chargeback** shows each month's spend by team, ready to download for
finance, and a closed month stays fixed.

See [Team chargeback](/guides/team-chargeback).

## Next [#next]

* [Roles](/features/organization/roles) and the [audit log](/features/organization/audit-log): who can change what.
* [Access control](/features/access/block-models): limit a key to certain models or providers.


# For SaaS companies (https://docs.corevalue.dev/start-here/saas)



If you sell a product that calls AI models, whether it is built on AI or
adds AI features, your AI bill grows with every customer, but the provider's invoice does not say which customer ran it up.
CoreValue answers three questions, in this order.

## 1. What does each customer cost us? [#1-what-does-each-customer-cost-us]

Your developer adds one header to each request, `Cova-Customer-Id`, with the
customer's ID from your own system. From then on, **Spend** shows cost per
customer, per feature and per model.

<Callout type="info">
  **Hand this to your developer:** [Tag your traffic](/features/cost-attribution)
  and the [OpenAI](/getting-started/openai) or [Anthropic](/getting-started/anthropic)
  quickstart. It is two lines of code and one header.
</Callout>

See [Spend](/features/cost-tracking/spend).

## 2. Are we making money on them? [#2-are-we-making-money-on-them]

Record what each customer pays you (a monthly fee, a markup on usage, or both),
and **Budgets & Limits → Customers** shows revenue and margin next to cost. Set
a margin floor and you are emailed before a customer drops below it.

See [Customer pricing and margin](/features/reports/customer-pricing) and
[Unit economics](/features/reports/unit-economics).

## 3. Can we stop a customer costing too much? [#3-can-we-stop-a-customer-costing-too-much]

Give each customer a monthly budget. When they reach it, their requests are
refused before the provider is called, and every other customer carries on.

See [Per-customer budgets](/guides/per-customer-budgets).

## Next [#next]

* [Budget configuration](/guides/budget-configuration): the rest of your budgets.
* [Chargeback](/features/reports/chargeback): a month's cost and margin by customer.


# Authentication (https://docs.corevalue.dev/api-reference/authentication)



## API Key Authentication [#api-key-authentication]

Every request to the CoreValue gateway must be authenticated with a single
organization API key. The key is sent in the standard `Authorization` header
using the `Bearer` scheme:

```http
Authorization: Bearer sk-cova-XXXXXXXXXXXXXXXX
```

<Callout type="info">
  An organisation can have many CoreValue keys, one per service, environment
  or person. There is no public/secret key split: each key is used for all
  gateway operations. See [API keys](/features/organization/api-keys).
</Callout>

## Required on Every Endpoint [#required-on-every-endpoint]

Authentication is required on **every** gateway endpoint except `/healthcheck`.
Requests missing or failing authentication receive a `401 Unauthorized`
response.

| Endpoint                          | Auth Required |
| --------------------------------- | ------------- |
| `GET /healthcheck`                | No            |
| `POST /v1/feedback`               | Yes           |
| `GET /v1/models`                  | Yes           |
| `GET /gateway/v1/meta/:requestId` | Yes           |
| `POST *` (provider dispatch)      | Yes           |

## Examples [#examples]

<Tabs items="[&#x22;cURL&#x22;, &#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/v1/chat/completions \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer sk-cova-XXXXXXXXXXXXXXXX" \
      -d '{
        "model": "gpt-4o-mini",
        "messages": [{"role": "user", "content": "Hello!"}]
      }'
    ```
  </Tab>

  <Tab>
    ```python
    from openai import OpenAI

    client = OpenAI(
        base_url="https://gateway.corevalue.dev/v1",
        api_key="sk-cova-XXXXXXXXXXXXXXXX",
    )

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hello!"}],
    )
    ```
  </Tab>

  <Tab>
    ```typescript
    import { OpenAI } from "openai";

    const client = new OpenAI({
      baseURL: "https://gateway.corevalue.dev/v1",
      apiKey: "sk-cova-XXXXXXXXXXXXXXXX",
    });

    const response = await client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [{ role: "user", content: "Hello!" }],
    });
    ```
  </Tab>
</Tabs>

## Passing the Key Explicitly [#passing-the-key-explicitly]

If your HTTP client does not set the `Authorization` header automatically from
an `apiKey` field, you can pass it explicitly via custom/default headers:

<Tabs items="[&#x22;cURL&#x22;, &#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/v1/chat/completions \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer sk-cova-XXXXXXXXXXXXXXXX" \
      -d '{
        "model": "gpt-4o-mini",
        "messages": [{"role": "user", "content": "Hello!"}]
      }'
    ```
  </Tab>

  <Tab>
    ```python
    from openai import OpenAI

    client = OpenAI(
        base_url="https://gateway.corevalue.dev/v1",
        default_headers={
            "Authorization": "Bearer sk-cova-XXXXXXXXXXXXXXXX",
        },
    )

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hello!"}],
    )
    ```
  </Tab>

  <Tab>
    ```typescript
    import { OpenAI } from "openai";

    const client = new OpenAI({
      baseURL: "https://gateway.corevalue.dev/v1",
      defaultHeaders: {
        Authorization: `Bearer sk-cova-XXXXXXXXXXXXXXXX`,
      },
    });

    const response = await client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [{ role: "user", content: "Hello!" }],
    });
    ```
  </Tab>
</Tabs>

<Callout type="warning">
  Do **not** use `CoreValue-Auth` or `TraceAI-Auth` headers — these are
  deprecated and no longer accepted by the gateway. Always use the standard
  `Authorization: Bearer` header.
</Callout>


# Errors (https://docs.corevalue.dev/api-reference/errors)



CoreValue AI Gateway automatically tries multiple billing methods to ensure your requests succeed. When one method fails, it falls back to alternatives and returns the most actionable error to help you fix issues quickly.

## How Fallback Works [#how-fallback-works]

The AI Gateway supports two billing methods:

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Pass-Through Billing (PTB)">
    Pay-as-you-go with CoreValue credits. Simple, no provider account needed.
  </Card>

  <Card title="Bring Your Own Keys (BYOK)">
    Use your own provider API keys. You're billed directly by the provider.
  </Card>
</div>

**Automatic Fallback**: When you configure both methods, the gateway tries PTB first. If it fails (e.g., insufficient credits), it automatically falls back to BYOK.

***

## Error Priority Logic [#error-priority-logic]

When both billing methods fail, the gateway returns the **most actionable error** to help you resolve the issue:

### Priority Order [#priority-order]

1. **403 Forbidden** → Critical access issue, contact support
2. **401 Unauthorized** → Fix your provider API key
3. **400 Bad Request** → Fix your request format
4. **500 Server Error** → Provider issue or configuration problem
5. **429 Rate Limit** → Only shown if all attempts hit rate limits

<Callout type="info">
  **Why this order?** If you configured BYOK, errors from your provider keys (401, 500) are more actionable than PTB's "insufficient credits" (429). You chose BYOK for a reason!
</Callout>

***

## Common Error Scenarios [#common-error-scenarios]

| Error Code | What It Means           | Action Required                                               | Example                         |
| ---------- | ----------------------- | ------------------------------------------------------------- | ------------------------------- |
| **401**    | Authentication failed   | Check your provider API key in settings                       | Invalid OpenAI API key          |
| **403**    | Access forbidden        | Contact [support@corevalue.dev](mailto:support@corevalue.dev) | Wallet suspended, model blocked |
| **400**    | Invalid request format  | Fix your request body or parameters                           | Missing required field          |
| **429**    | Insufficient credits    | Add credits OR configure provider keys                        | No CoreValue credits, no BYOK   |
| **500**    | Upstream provider error | Check provider status or retry                                | Provider API timeout            |
| **503**    | Service unavailable     | Provider temporarily down, retry later                        | Provider maintenance            |

***

## Fallback Scenarios [#fallback-scenarios]

<Accordions>
  <Accordion title="Scenario 1: PTB Succeeds">
    **Setup**: You have CoreValue credits

    **Result**: ✅ Request completes using Pass-Through Billing

    **Error**: None - successful response
  </Accordion>

  <Accordion title="Scenario 2: PTB Fails, BYOK Succeeds">
    **Setup**: No CoreValue credits, but valid provider API key configured

    **Result**: ✅ Request completes using your provider key

    **Error**: None - successful response (PTB's 429 is hidden since BYOK succeeded)
  </Accordion>

  <Accordion title="Scenario 3: PTB Fails, BYOK Fails">
    **Setup**: No CoreValue credits, invalid/failing provider key

    **Result**: ❌ Request fails

    **Error Returned**: BYOK's error (401, 500, etc.) - NOT PTB's 429

    **Why**: You configured BYOK, so we show what's wrong with your provider key rather than "insufficient credits"

    **Example**:

    ```json
    {
      "error": {
        "message": "Authentication failed",
        "type": "invalid_api_key",
        "code": 401
      }
    }
    ```
  </Accordion>

  <Accordion title="Scenario 4: No BYOK Configured, PTB Fails">
    **Setup**: No CoreValue credits, no provider keys configured

    **Result**: ❌ Request fails

    **Error Returned**: 429 Insufficient credits

    **Why**: No alternative billing method available

    **Example**:

    ```json
    {
      "error": {
        "message": "Insufficient credits",
        "type": "request_failed",
        "code": 429
      }
    }
    ```

    **Solutions**:

    1. Add CoreValue credits at `/credits`
    2. Configure provider keys in `/settings/providers`
    3. The gateway automatically retries transient failures (429, 500, 502, 503, 504, 529) with exponential backoff — no configuration needed

    <Callout type="info">
      **Retries are automatic!** The gateway automatically retries transient failures (429, 500, 502, 503, 504, 529) with exponential backoff and jitter. The `X-Cova-Retry-Count` response header tells you how many retries were attempted.
    </Callout>
  </Accordion>
</Accordions>

***

## Understanding Error Sources [#understanding-error-sources]

When you see an error, you can determine which billing method it came from:

**PTB Errors**:

* 429: "Insufficient credits" → Add credits at `/credits`
* 403: "Wallet suspended" → Contact support

**BYOK Errors**:

* 401: "Invalid API key" → Check provider keys in `/settings/providers`
* 500: "Provider error" → Check provider status
* 503: "Service unavailable" → Provider having issues

***

## Best Practices [#best-practices]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Configure Both Methods">
    Set up both PTB and BYOK for maximum reliability. If one fails, the other serves as backup.
  </Card>

  <Card title="Monitor Credit Balance">
    Keep track of your CoreValue credits to avoid 429 errors during critical requests.
  </Card>

  <Card title="Automatic Retries">
    The gateway automatically retries transient errors (429, 500, 502, 503, 504, 529) with exponential backoff and jitter. No configuration needed.
  </Card>

  <Card title="Log Error Details">
    Log the full error response to debug provider-specific issues quickly.
  </Card>
</div>

***

## Error Handling in Code [#error-handling-in-code]

<Callout type="note">
  **Retries are built-in**: The gateway automatically retries transient failures (429, 500, 502, 503, 504, 529) with exponential backoff and jitter. You generally do not need to implement your own retry logic — the `X-Cova-Retry-Count` response header tells you how many retries were attempted.
</Callout>

### Retry Logic Example [#retry-logic-example]

```typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://gateway.corevalue.dev/v1",
  apiKey: process.env.COVA_API_KEY,
});

async function callWithRetry(maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await client.chat.completions.create({
        model: "gpt-4o-mini",
        messages: [{ role: "user", content: "Hello!" }],
      });
      return response;
    } catch (error: any) {
      const status = error?.status || 500;

      // Don't retry auth errors or bad requests
      if (status === 401 || status === 403 || status === 400) {
        throw error;
      }

      // Don't retry insufficient credits unless it's the last attempt
      if (status === 429 && i === maxRetries - 1) {
        throw error;
      }

      // Retry transient errors (500, 503) with exponential backoff
      if (status >= 500 || status === 429) {
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
        continue;
      }

      throw error;
    }
  }
}
```

### Error Classification [#error-classification]

```typescript
function classifyError(error: any) {
  const status = error?.status || 500;

  if (status === 401) {
    return {
      type: "authentication",
      action: "Check your API keys in settings",
      retryable: false
    };
  }

  if (status === 429) {
    return {
      type: "rate_limit",
      action: "Add credits or wait before retrying",
      retryable: true
    };
  }

  if (status >= 500) {
    return {
      type: "server_error",
      action: "Retry with exponential backoff",
      retryable: true
    };
  }

  return {
    type: "unknown",
    action: "Check error message for details",
    retryable: false
  };
}
```

***

## Related Resources [#related-resources]

* [Provider Routing](/features/routing/other-providers) - Learn how routing and retries work
* [Fallback Chains](/features/routing/fallbacks) - Configure model-level fallbacks with the `Cova-Fallbacks` header
* Settings: Provider Keys - Add your provider API keys
* Credits - Add CoreValue credits for Pass-Through Billing

<Callout type="info">
  **Need Help?** If you're seeing unexpected errors or need assistance configuring fallback, contact us at [support@corevalue.dev](mailto:support@corevalue.dev)
</Callout>


# Headers (https://docs.corevalue.dev/api-reference/headers)



The CoreValue gateway uses `Cova-*` headers for request-side configuration and
returns `Cova-*` headers on every response. Header matching is case-insensitive
(the gateway lowercases header keys before comparison), but the canonical names
below are recommended.

All examples below use the placeholder key `sk-cova-XXXXXXXXXXXXXXXX` and the
gateway base URL `https://gateway.corevalue.dev`. Authentication is always
`Authorization: Bearer <COVA_API_KEY>` — see
[Authentication](/api-reference/authentication).

## Quick Reference [#quick-reference]

<Tabs items="[&#x22;cURL&#x22;, &#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/v1/chat/completions \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer sk-cova-XXXXXXXXXXXXXXXX" \
      -H "Cova-User-Id: user_abc123" \
      -H "Cova-Actor: agent" \
      -H "Cova-Prompt-Id: prompt_42" \
      -H "Cova-Property-Region: us-east-1" \
      -H "Cova-Cache-Enabled: true" \
      -d '{
        "model": "gpt-4o-mini",
        "messages": [{"role": "user", "content": "Hello!"}]
      }'
    ```
  </Tab>

  <Tab>
    ```python
    from openai import OpenAI

    client = OpenAI(
        base_url="https://gateway.corevalue.dev/v1",
        api_key="sk-cova-XXXXXXXXXXXXXXXX",
    )

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hello!"}],
        extra_headers={
            "Cova-User-Id": "user_abc123",
            "Cova-Actor": "agent",
            "Cova-Prompt-Id": "prompt_42",
            "Cova-Property-Region": "us-east-1",
            "Cova-Cache-Enabled": "true",
        },
    )
    ```
  </Tab>

  <Tab>
    ```typescript
    import { OpenAI } from "openai";

    const client = new OpenAI({
      baseURL: "https://gateway.corevalue.dev/v1",
      apiKey: "sk-cova-XXXXXXXXXXXXXXXX",
    });

    const response = await client.chat.completions.create(
      {
        model: "gpt-4o-mini",
        messages: [{ role: "user", content: "Hello!" }],
      },
      {
        headers: {
          "Cova-User-Id": "user_abc123",
          "Cova-Actor": "agent",
          "Cova-Prompt-Id": "prompt_42",
          "Cova-Property-Region": "us-east-1",
          "Cova-Cache-Enabled": "true",
        },
      },
    );
    ```
  </Tab>
</Tabs>

***

## Request Headers [#request-headers]

All request headers use the `Cova-` prefix. They are read before the request is
sent to the provider.

### Cova-User-Id [#cova-user-id]

|                |                                                                                                    |
| -------------- | -------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                            |
| **Purpose**    | End-user identifier for analytics, per-user rate-limit segments, and logging.                      |
| **Validation** | `[a-zA-Z0-9_\-:.]`, max 256 chars. Invalid values are treated as null (empty string in analytics). |
| **Example**    | `Cova-User-Id: user_abc123`                                                                        |

### Cova-Prompt-Id [#cova-prompt-id]

|                |                                                                                                                                 |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                                         |
| **Purpose**    | Prompt identifier for prompt-expansion and logging. Can also be set via `body.prompt_id` (the body value overrides the header). |
| **Validation** | `[a-zA-Z0-9_\-:.]`, max 256 chars. Same sanitizer as `Cova-User-Id`.                                                            |
| **Example**    | `Cova-Prompt-Id: prompt_42`                                                                                                     |

### Cova-Customer-Id [#cova-customer-id]

|                |                                                                                                                                                                                                                  |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                                                                                                                          |
| **Purpose**    | Which of *your* customers a request was made on behalf of, for per-customer unit economics. Promoted to an indexed `customer_id` column, so cost can be grouped by customer without scanning the properties map. |
| **Validation** | Truncated at 256 characters. Whitespace-only values are ignored.                                                                                                                                                 |
| **Example**    | `Cova-Customer-Id: acme-corp`                                                                                                                                                                                    |

### Cova-Feature-Id [#cova-feature-id]

|                |                                                                                                                                                              |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Direction**  | Request                                                                                                                                                      |
| **Purpose**    | Which feature or surface produced the request — `checkout`, `support-bot`, `nightly-eval`. Answers "what is this spend for" independently of who it was for. |
| **Validation** | Truncated at 256 characters. Whitespace-only values are ignored.                                                                                             |
| **Example**    | `Cova-Feature-Id: checkout`                                                                                                                                  |

### Cova-Team-Id [#cova-team-id]

|                |                                                                                                                                                                                                                                                      |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                                                                                                                                                              |
| **Purpose**    | Which of your teams the spend belongs to. **Cost attribution only** — this header never affects what anyone can see. Access is decided from the credential, so tagging a request with another team's id changes the invoice split, not the audience. |
| **Validation** | Truncated at 256 characters. Whitespace-only values are ignored.                                                                                                                                                                                     |
| **Example**    | `Cova-Team-Id: platform`                                                                                                                                                                                                                             |

<Callout type="info">
  These three are **self-reported**: the gateway records what you send. A key
  [scoped to customers](/features/access/key-scopes) checks `Cova-Customer-Id`
  against its list, and a key scoped to one customer fills it in. Team budgets
  and chargeback use the key's team, never `Cova-Team-Id`.

  They cannot be forged by a caller supplying a matching `Cova-Property-*`
  header. Property names have their hyphens replaced with underscores, so
  `Cova-Property-Cova-Customer-Id` is stored as `cova_customer_id` and can
  never collide with the literal `Cova-Customer-Id` these are read from.
</Callout>

### Cova-Actor [#cova-actor]

|                |                                                                                                                                                                                                                                                                                  |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                                                                                                                                                                                          |
| **Purpose**    | Whether a person or a program produced this turn. Cost per model is a commodity; cost per *actor* is what makes an agent fleet governable, and you cannot budget for something you cannot name.                                                                                  |
| **Validation** | One of `human`, `agent`, `unknown`. Sanitized as an identifier, then matched exactly. An unrecognised value is not coerced to `unknown`: the request falls through to the heuristic below, since a misspelled header is no reason to discard evidence the payload still carries. |
| **Example**    | `Cova-Actor: agent`                                                                                                                                                                                                                                                              |

When the header is absent, the gateway infers the answer from the payload. The
signal is not that tools *exist* — people chat with search and code interpreters
enabled all day, and a capability is not an actor. It is that the turn is a
**continuation**: the last thing in the conversation is a tool result being fed
back, which no person typed.

| Dialect                        | Tail that reads as an agent continuation            |
| ------------------------------ | --------------------------------------------------- |
| OpenAI `messages`              | last message `role` is `tool` or `function`         |
| Anthropic / Bedrock `messages` | last message content contains `type: "tool_result"` |
| Google `contents`              | last content parts contain `functionResponse`       |
| Responses `input`              | last item `type` is `function_call_output`          |

<Callout type="warning">
  **The heuristic under-counts agents, by roughly one turn per run.** The first
  turn of an agent run is identical to a person asking a question of a
  tool-enabled assistant, because at that moment it is the same request. A
  ten-turn loop is recorded as one `unknown` and nine `agent`; a one-shot agent
  call is recorded as `unknown` outright.

  This is the argument for sending the header. The heuristic gives coverage with
  no integration work; the header gives correctness.
</Callout>

`unknown` is a real answer and never collapses into `human`. Treating "no
evidence of an agent" as "a person" is true for most workloads today and will
not stay true, and a default of `human` would silently reclassify the future as
the past.

### Cova-Actor-Source [#cova-actor-source]

|                |                                                                                                                                                                                               |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Written by the gateway. &#x2A;*Not read from your request.**                                                                                                                                  |
| **Purpose**    | How the actor was determined: `header` if you declared it, `inferred` if the payload tail matched, `none` if there was no evidence. Recorded beside the value so the number is interpretable. |
| **Validation** | Sending this header has no effect. It is ignored rather than rejected.                                                                                                                        |
| **Example**    | Appears in logs and filters as `cova_actor_source`, never as input                                                                                                                            |

Accepting this inbound would let a caller assert its own provenance, declaring
`header` while sending none, which destroys the only thing the field is for.
"45% agent" means nothing if you cannot tell whether that is declared or
guessed, and the two carry very different error bars.

<Callout type="info">
  `cova_actor` and `cova_actor_source` are **reserved property names**. A
  `Cova-Property-cova_actor` header, or `Cova-Property-cova-actor` which
  sanitises to the same key, is dropped, so a caller cannot overwrite the
  classification.

  Both appear on the **Requests** page as the **Actor** column, and
  as the **Actor** and **Actor source** filters.
</Callout>

### Cova-Session-Id, Cova-Session-Name, Cova-Session-Path [#cova-session-id-cova-session-name-cova-session-path]

|                |                                                                                                                                                                                                                                                                                   |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                                                                                                                                                                                           |
| **Purpose**    | Group related requests into one run, shown on the console's **Runs** page. `Cova-Session-Id` identifies the run, `Cova-Session-Name` labels it, and `Cova-Session-Path` places a request in the run's tree, for example `/plan/search`. See [Runs](/features/cost-tracking/runs). |
| **Validation** | Trimmed, then truncated at 256 characters. Blank values are ignored. A `Cova-Property-*` header cannot set or overwrite them.                                                                                                                                                     |
| **Example**    | `Cova-Session-Id: run_7f3a`                                                                                                                                                                                                                                                       |

A [session cap](/features/velocity-limits#session-caps) limits what each
session on a key may spend. The id is whatever your application sends, so a
session cap bounds an honest or buggy agent only: a caller that sends a new id
gets a fresh cap.

### Cova-Property-\{Name} [#cova-property-name]

|                |                                                                                                                                                                                                            |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                                                                                                                    |
| **Purpose**    | Arbitrary key-value metadata attached to the request for filtering, segmentation, and analytics. Multiple `Cova-Property-*` headers are allowed per request.                                               |
| **Validation** | Name: lowercased, hyphens converted to underscores, then `[a-z0-9_]`, max 64 chars, so `Cova-Property-Region` is stored as `region`. Value: max 256 chars (truncated). Invalid names are silently dropped. |
| **Example**    | `Cova-Property-Region: us-east-1`                                                                                                                                                                          |

<Tabs items="[&#x22;cURL&#x22;, &#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/v1/chat/completions \
      -H "Authorization: Bearer sk-cova-XXXXXXXXXXXXXXXX" \
      -H "Cova-Property-Region: us-east-1" \
      -H "Cova-Property-Env: production" \
      -H "Cova-Property-App: mobile" \
      -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hi"}]}'
    ```
  </Tab>

  <Tab>
    ```python
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hi"}],
        extra_headers={
            "Cova-Property-Region": "us-east-1",
            "Cova-Property-Env": "production",
            "Cova-Property-App": "mobile",
        },
    )
    ```
  </Tab>

  <Tab>
    ```typescript
    const response = await client.chat.completions.create(
      { model: "gpt-4o-mini", messages: [{ role: "user", content: "Hi" }] },
      {
        headers: {
          "Cova-Property-Region": "us-east-1",
          "Cova-Property-Env": "production",
          "Cova-Property-App": "mobile",
        },
      },
    );
    ```
  </Tab>
</Tabs>

### Cova-RateLimit-Policy [#cova-ratelimit-policy]

|                |                                                                                                                                                                                                       |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                                                                                                               |
| **Purpose**    | Header-based bucket rate-limit policy. Overrides the DB-configured policy when present. Parsed into capacity / window / unit / segment.                                                               |
| **Validation** | Must follow the format above. An invalid value is ignored, and the rate limits configured in the console apply.                                                                                       |
| **Format**     | `<quota>;w=<window seconds>;u=<request\|cents>;s=<segment>`. Only `w=`, `u=` and `s=` are read: a part spelled any other way, such as `window=60`, is ignored, and the window falls back to one hour. |
| **Example**    | `Cova-RateLimit-Policy: 100;w=60;u=request;s=user`                                                                                                                                                    |

### Cova-Fallbacks [#cova-fallbacks]

|                |                                                                                                                      |
| -------------- | -------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                              |
| **Purpose**    | JSON array of model names to try in order if the primary model fails with a retryable status code.                   |
| **Validation** | Valid JSON array, non-empty, max 5 elements, each a non-empty string. Invalid or missing → null (no fallback chain). |
| **Example**    | `Cova-Fallbacks: ["groq/llama-3.3-70b-versatile","together/meta-llama/Llama-3.3-70B-Instruct-Turbo"]`                |

### Cova-Cache-Enabled [#cova-cache-enabled]

|                |                                                                    |
| -------------- | ------------------------------------------------------------------ |
| **Direction**  | Request                                                            |
| **Purpose**    | Enables both cache read and write for this request.                |
| **Validation** | `"true"` (case-insensitive) → enabled. Any other value → disabled. |
| **Example**    | `Cova-Cache-Enabled: true`                                         |

### Cova-Cache-Save [#cova-cache-save]

|                |                                                                |
| -------------- | -------------------------------------------------------------- |
| **Direction**  | Request                                                        |
| **Purpose**    | Enables cache write only (store the response for future hits). |
| **Validation** | `"true"` (case-insensitive) → enabled.                         |
| **Example**    | `Cova-Cache-Save: true`                                        |

### Cova-Cache-Read [#cova-cache-read]

|                |                                                             |
| -------------- | ----------------------------------------------------------- |
| **Direction**  | Request                                                     |
| **Purpose**    | Enables cache read only (serve from cache if a hit exists). |
| **Validation** | `"true"` (case-insensitive) → enabled.                      |
| **Example**    | `Cova-Cache-Read: true`                                     |

### Cova-Cache-Bucket-Max-Size [#cova-cache-bucket-max-size]

|                |                                                                                                                |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                        |
| **Purpose**    | Maximum number of cached responses per cache key (bucket size). Enables load-balancing across cached variants. |
| **Validation** | Integer, default 1, max 20. If greater than 20, cache settings return null (cache disabled).                   |
| **Example**    | `Cova-Cache-Bucket-Max-Size: 5`                                                                                |

### Cova-Cache-Seed [#cova-cache-seed]

|                |                                                                                                              |
| -------------- | ------------------------------------------------------------------------------------------------------------ |
| **Direction**  | Request                                                                                                      |
| **Purpose**    | Cache isolation seed. Prepended to the cache key hash to namespace cache entries (e.g. for A/B experiments). |
| **Validation** | Any string. Included in the SHA-256 cache key computation.                                                   |
| **Example**    | `Cova-Cache-Seed: my-experiment-v2`                                                                          |

### Cova-Cache-Ignore-Keys [#cova-cache-ignore-keys]

|                |                                                                                                                               |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **Direction**  | Request                                                                                                                       |
| **Purpose**    | JSON array of body keys to exclude from cache key computation (e.g. ignore `user_id` so different users share a cache entry). |
| **Validation** | Valid JSON string array. Invalid JSON is ignored (no keys stripped).                                                          |
| **Example**    | `Cova-Cache-Ignore-Keys: ["user_id","timestamp"]`                                                                             |

<Callout type="info">
  `Cache-Control` (the standard HTTP header) also sets how long a cached
  response is kept (`s-maxage` or `max-age`, max 365 days, default 7 days). It
  is not a `Cova-*` header, but the cache reads it.
</Callout>

<Tabs items="[&#x22;cURL&#x22;, &#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/v1/chat/completions \
      -H "Authorization: Bearer sk-cova-XXXXXXXXXXXXXXXX" \
      -H "Cova-Cache-Enabled: true" \
      -H "Cova-Cache-Bucket-Max-Size: 5" \
      -H "Cova-Cache-Seed: my-experiment-v2" \
      -H "Cova-Cache-Ignore-Keys: [\"user_id\",\"timestamp\"]" \
      -H "Cache-Control: max-age=3600" \
      -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hi"}]}'
    ```
  </Tab>

  <Tab>
    ```python
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hi"}],
        extra_headers={
            "Cova-Cache-Enabled": "true",
            "Cova-Cache-Bucket-Max-Size": "5",
            "Cova-Cache-Seed": "my-experiment-v2",
            "Cova-Cache-Ignore-Keys": '["user_id","timestamp"]',
            "Cache-Control": "max-age=3600",
        },
    )
    ```
  </Tab>

  <Tab>
    ```typescript
    const response = await client.chat.completions.create(
      { model: "gpt-4o-mini", messages: [{ role: "user", content: "Hi" }] },
      {
        headers: {
          "Cova-Cache-Enabled": "true",
          "Cova-Cache-Bucket-Max-Size": "5",
          "Cova-Cache-Seed": "my-experiment-v2",
          "Cova-Cache-Ignore-Keys": '["user_id","timestamp"]',
          "Cache-Control": "max-age=3600",
        },
      },
    );
    ```
  </Tab>
</Tabs>

***

## Response Headers [#response-headers]

The gateway sets these on the response your SDK receives. Cost headers are
added once the provider has reported usage.

### Cova-Id [#cova-id]

|               |                                                                                                                                                                                             |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                                                                                                                    |
| **Purpose**   | Gateway-generated request ID (UUID). Used for log correlation and `/meta` endpoint lookups.                                                                                                 |
| **When Set**  | On every response the gateway forwards from a provider (streaming and non-streaming). Also set on the forwarded upstream request. Not set on cache hits or on requests the gateway refuses. |
| **Example**   | `Cova-Id: 550e8400-e29b-41d4-a716-446655440000`                                                                                                                                             |

### Cova-Provider [#cova-provider]

|               |                                                                                                                                        |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                                                               |
| **Purpose**   | The provider that actually served the request (after fallback resolution). Reflects the key that was used, not the requested provider. |
| **When Set**  | On every response the gateway forwards from a provider.                                                                                |
| **Example**   | `Cova-Provider: groq`                                                                                                                  |

### Cova-Model [#cova-model]

|               |                                                                                                                            |
| ------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                                                   |
| **Purpose**   | The model that actually served the request (after fallback or budget-gate downgrade). May differ from the requested model. |
| **When Set**  | On every response the gateway forwards from a provider.                                                                    |
| **Example**   | `Cova-Model: groq/llama-3.3-70b-versatile`                                                                                 |

### Cova-Status [#cova-status]

|               |                                                                                                                  |
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                                         |
| **Purpose**   | Gateway-level status: `"success"` or `"error"`. Derived from upstream HTTP status (200 → success, else → error). |
| **When Set**  | On every response the gateway forwards from a provider.                                                          |
| **Example**   | `Cova-Status: success`                                                                                           |

### Cova-Request-Cost [#cova-request-cost]

|               |                                                                                                                                 |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                                                        |
| **Purpose**   | Actual cost in USD for this request, computed from extracted usage.                                                             |
| **When Set**  | Non-streaming: always set (even when 0) after usage extraction. Streaming: set in the final SSE `cova` frame (not as a header). |
| **Example**   | `Cova-Request-Cost: 0.0015000000`                                                                                               |

### Cova-Request-Cost-Estimate [#cova-request-cost-estimate]

|               |                                                                                                           |
| ------------- | --------------------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                                  |
| **Purpose**   | An upfront worst-case cost estimate for requests paid with CoreValue credits. A hint, not the final cost. |
| **When Set**  | Not sent today.                                                                                           |
| **Example**   | `Cova-Request-Cost-Estimate: 0.0230000000`                                                                |

### Cova-Credits-Remaining [#cova-credits-remaining]

|               |                                                                                                                                                        |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Direction** | Response                                                                                                                                               |
| **Purpose**   | Post-request credit balance in USD (pre-request balance − actual cost).                                                                                |
| **When Set**  | Successful requests paid with CoreValue credits. Non-streaming: set as a header. Streaming: available from `/meta`, not in the final SSE `cova` frame. |
| **Example**   | `Cova-Credits-Remaining: 48.7700000000`                                                                                                                |

### Cova-Gateway-Mode [#cova-gateway-mode]

|               |                                                                                                                                                                                                      |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                                                                                                                             |
| **Purpose**   | Whether cross-provider translation was applied. `"passthrough"` (default) or `"translated"`.                                                                                                         |
| **When Set**  | On every response the gateway forwards from a provider. `"translated"` when the request was converted to another provider's format: an `anthropic/`, `google/` or `bedrock/` model on `/gateway/v1`. |
| **Example**   | `Cova-Gateway-Mode: translated`                                                                                                                                                                      |

### Cova-Translation-Warning [#cova-translation-warning]

|               |                                                                                          |
| ------------- | ---------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                 |
| **Purpose**   | Comma-separated list of request fields dropped during cross-provider translation.        |
| **When Set**  | Set only when `gatewayMode` is `"translated"` and fields were dropped. Absent otherwise. |
| **Example**   | `Cova-Translation-Warning: logprobs,top_k`                                               |

### Cova-Cache [#cova-cache]

|               |                                                                                                            |
| ------------- | ---------------------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                                   |
| **Purpose**   | Cache hit indicator. Set to `"HIT"` when the response is served from cache.                                |
| **When Set**  | Set only on cache hits (when `Cova-Cache-Read` or `Cova-Cache-Enabled` is true and a cached entry exists). |
| **Example**   | `Cova-Cache: HIT`                                                                                          |

### Cova-Cache-Bucket-Idx [#cova-cache-bucket-idx]

|               |                                                                  |
| ------------- | ---------------------------------------------------------------- |
| **Direction** | Response                                                         |
| **Purpose**   | The bucket slot index from which the cached response was served. |
| **When Set**  | Set only on cache hits.                                          |
| **Example**   | `Cova-Cache-Bucket-Idx: 2`                                       |

### Cova-Cache-Latency [#cova-cache-latency]

|               |                                                                                              |
| ------------- | -------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                     |
| **Purpose**   | Original response latency (in ms) of the cached response, recorded when it was first stored. |
| **When Set**  | Set only on cache hits.                                                                      |
| **Example**   | `Cova-Cache-Latency: 450`                                                                    |

### X-Cova-Retry-Count [#x-cova-retry-count]

|               |                                                                                                                                                                              |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Direction** | Response                                                                                                                                                                     |
| **Purpose**   | Number of retry attempts made before the request succeeded. Set by the retry executor for observability.                                                                     |
| **When Set**  | Set when gateway-configured retries are enabled (env-configured server-side) and at least one retry was attempted. Absent on first-try success or when retries are disabled. |
| **Example**   | `X-Cova-Retry-Count: 2`                                                                                                                                                      |

***

## Streaming Final SSE Frame [#streaming-final-sse-frame]

When the gateway processes a streaming request, it appends one final SSE event
after the upstream stream closes (including after `data: [DONE]` for
OpenAI-format providers).

### Exact Format [#exact-format]

```
data: {"cova":{"requestId":"...","provider":"...","model":"...","status":"success","requestCost":0.0015,"gatewayMode":"translated","translationWarning":"logprobs"}}

```

The frame is a single SSE `data:` line terminated by `\n\n` (two newlines).
The JSON payload is nested under a `"cova"` key so the SDK can detect it by
checking `"cova" in lastChunk`.

### CovaGatewayMeta Shape [#covagatewaymeta-shape]

The fields the CoreValue TypeScript and Python SDKs return:

| Field                | Type                             | Source                                                                                    |
| -------------------- | -------------------------------- | ----------------------------------------------------------------------------------------- |
| `requestId`          | `string`                         | `Cova-Id` header                                                                          |
| `provider`           | `string`                         | `Cova-Provider` header                                                                    |
| `model`              | `string`                         | `Cova-Model` header                                                                       |
| `status`             | `"success" \| "error"`           | `Cova-Status` header                                                                      |
| `requestCost`        | `number?`                        | `Cova-Request-Cost` (non-streaming) or final SSE event (streaming) or `/meta` endpoint    |
| `estimatedCost`      | `number?`                        | `Cova-Request-Cost-Estimate` (upfront, all responses)                                     |
| `creditsRemaining`   | `number?`                        | `Cova-Credits-Remaining`                                                                  |
| `gatewayMode`        | `"passthrough" \| "translated"?` | `Cova-Gateway-Mode`                                                                       |
| `translationWarning` | `string?`                        | `Cova-Translation-Warning` (comma-separated list of dropped fields, only when translated) |

<Callout type="warning">
  In the streaming final SSE frame, `requestCost` is set only when
  `usage.costUsd > 0`. If no usage was extracted (e.g. upstream errored), the
  field is absent and the SDK should fall back to the `/meta` endpoint.
</Callout>

### Retrieving Metadata After Streaming [#retrieving-metadata-after-streaming]

For streaming requests, cost and credit fields are not available as response
headers. Use the `/meta` endpoint to retrieve the `CovaGatewayMeta` after the
stream completes:

<Tabs items="[&#x22;cURL&#x22;, &#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/gateway/v1/meta/550e8400-e29b-41d4-a716-446655440000 \
      -H "Authorization: Bearer sk-cova-XXXXXXXXXXXXXXXX"
    ```
  </Tab>

  <Tab>
    ```python
    import requests

    resp = requests.get(
        "https://gateway.corevalue.dev/gateway/v1/meta/550e8400-e29b-41d4-a716-446655440000",
        headers={"Authorization": "Bearer sk-cova-XXXXXXXXXXXXXXXX"},
    )
    meta = resp.json()  # CovaGatewayMeta
    ```
  </Tab>

  <Tab>
    ```typescript
    const resp = await fetch(
      "https://gateway.corevalue.dev/gateway/v1/meta/550e8400-e29b-41d4-a716-446655440000",
      { headers: { Authorization: "Bearer sk-cova-XXXXXXXXXXXXXXXX" } },
    );
    const meta = await resp.json(); // CovaGatewayMeta
    ```
  </Tab>
</Tabs>

The `/meta` endpoint returns:

* **`200`** — metadata found. Returns `CovaGatewayMeta` JSON.
* **`202`** — requestId not found; may be in-flight, expired (TTL 5 min), or never existed. Returns `{ status: "pending" }`.
* **`404`** — requestId exists but belongs to a different organization.

<Callout type="info">
  Metadata TTL is 300 seconds (5 minutes) after the request completes. The
  `requestId` must be a valid UUID v4.
</Callout>


# List models (https://docs.corevalue.dev/api-reference/list-models)



The gateway exposes an OpenAI-compatible `GET /v1/models` endpoint that returns the list of models available through your CoreValue account. This lets you use the standard OpenAI SDK's `models.list()` method to discover available models.

## Authentication [#authentication]

The endpoint requires authentication. Provide your CoreValue API key as a Bearer token:

```http
Authorization: Bearer $COVA_API_KEY
```

Requests are IP rate-limited before authentication and org rate-limited after authentication succeeds.

## Behavior [#behavior]

| Aspect        | Detail                                                                                                                              |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Format**    | OpenAI-compatible `{"object": "list", "data": [...]}`                                                                               |
| **Filtering** | Server-side filter by `requireExplicitRouting` — models that require explicit routing are excluded. There is no client-side bypass. |
| **Caching**   | In-memory cache for 60 seconds                                                                                                      |
| **Auth**      | Required (Bearer token or `api-key` header)                                                                                         |

<Callout type="info">
  The model list is filtered server-side based on `requireExplicitRouting`. Models flagged as requiring explicit routing are omitted from the response. This filtering cannot be bypassed by the client.
</Callout>

## Example [#example]

### cURL [#curl]

```bash
curl https://gateway.corevalue.dev/v1/models \
  -H "Authorization: Bearer $COVA_API_KEY"
```

### SDK [#sdk]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import OpenAI from "openai";

    const client = new OpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/v1",
    });

    const models = await client.models.list();

    for (const model of models.data) {
      console.log(model.id);
    }
    ```
  </Tab>

  <Tab>
    ```python
    from openai import OpenAI

    client = OpenAI(
        api_key=os.environ["COVA_API_KEY"],
        base_url="https://gateway.corevalue.dev/v1",
    )

    models = client.models.list()

    for model in models.data:
        print(model.id)
    ```
  </Tab>
</Tabs>

### Sample Response [#sample-response]

```json
{
  "object": "list",
  "data": [
    {
      "id": "gpt-4o",
      "object": "model",
      "created": 1715367049,
      "owned_by": "openai"
    },
    {
      "id": "groq/llama-3.3-70b-versatile",
      "object": "model",
      "created": 1715367049,
      "owned_by": "groq"
    }
  ]
}
```

## Related [#related]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Provider Routing" href="/features/routing/other-providers">
    The 18 model prefixes and routing behavior
  </Card>

  <Card title="Gateway Overview" href="/">
    All six gateway routes
  </Card>
</div>


# Look up a request's cost (https://docs.corevalue.dev/api-reference/request-cost-lookup)



The `/meta` endpoint is the fallback mechanism for retrieving per-request FinOps metadata (cost, provider, credits) when the final streaming SSE frame is missing or when you need to look up cost data after a non-streaming request has completed.

## Endpoint [#endpoint]

```
GET /gateway/v1/meta/:requestId
```

The `requestId` is the value returned in the `Cova-Id` response header (or the `requestId` field of the streaming `cova` frame). It must be a valid **UUID v4**.

## Authentication [#authentication]

Required. Provide your CoreValue API key as a Bearer token:

```http
Authorization: Bearer $COVA_API_KEY
```

## Path Parameter Validation [#path-parameter-validation]

The `requestId` must match the UUID v4 pattern:

```
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
```

A malformed `requestId` returns `400 Bad Request`.

## orgId-Scoped Storage [#orgid-scoped-storage]

Metadata is stored in Redis under an **orgId-scoped key**: `{orgId}:{requestId}`. A separate global existence marker is stored at `exists:{requestId}`.

<Callout type="warning">
  Cross-org access returns **404**, not 403. This is intentional — returning 404 avoids confirming to a caller that a given `requestId` exists under a different organization.
</Callout>

## Response Codes [#response-codes]

| Status | Meaning                                                                                        | Body                      |
| ------ | ---------------------------------------------------------------------------------------------- | ------------------------- |
| `200`  | Metadata found under this orgId                                                                | `CovaGatewayMeta` JSON    |
| `202`  | RequestId not found in the global marker — could be in-flight, expired (TTL), or never existed | `{ "status": "pending" }` |
| `404`  | RequestId exists (global marker present) but not under this orgId                              | —                         |
| `400`  | Malformed `requestId` (not a UUID v4)                                                          | —                         |

Metadata is stored for &#x2A;*300 seconds (5 minutes)** after the request completes. After the TTL expires, lookups return `202`.

## When to Use It [#when-to-use-it]

<Callout type="info">
  Use `/meta` as a **fallback** when the final streaming SSE `cova` frame is missing — for example, when the upstream provider errored before the stream completed. The SDK's `streamChat()` yields an empty meta in that case and you should then call `/meta` to retrieve the cost.
</Callout>

## Example [#example]

### cURL [#curl]

```bash
curl https://gateway.corevalue.dev/gateway/v1/meta/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $COVA_API_KEY"
```

### SDK [#sdk]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { parseGatewayMeta } from "@cova/gateway";

    const res = await fetch(
      "https://gateway.corevalue.dev/gateway/v1/meta/550e8400-e29b-41d4-a716-446655440000",
      { headers: { Authorization: `Bearer ${process.env.COVA_API_KEY}` } },
    );

    const meta = parseGatewayMeta(res.headers);

    if (meta) {
      console.log(meta.requestCost);  // actual cost in USD
      console.log(meta.provider);     // provider that served the request
      console.log(meta.model);        // model that served the request
    }
    ```
  </Tab>

  <Tab>
    ```python
    from cova_gateway import fetch_meta

    meta = fetch_meta(
        "550e8400-e29b-41d4-a716-446655440000",
        api_key=os.environ["COVA_API_KEY"],
    )

    if meta:
        print(meta.request_cost)
        print(meta.provider)
        print(meta.model)
    ```
  </Tab>
</Tabs>

### 200 Response Body [#200-response-body]

```json
{
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "provider": "groq",
  "model": "groq/llama-3.3-70b-versatile",
  "status": "success",
  "requestCost": 0.0015,
  "estimatedCost": 0.023,
  "creditsRemaining": 48.77,
  "gatewayMode": "passthrough"
}
```

### 202 Response Body [#202-response-body]

```json
{ "status": "pending" }
```

## CovaGatewayMeta Fields [#covagatewaymeta-fields]

| Field                | Type                             | Source                                 |
| -------------------- | -------------------------------- | -------------------------------------- |
| `requestId`          | `string`                         | `Cova-Id` header                       |
| `provider`           | `string`                         | `Cova-Provider` header                 |
| `model`              | `string`                         | `Cova-Model` header                    |
| `status`             | `"success" \| "error"`           | `Cova-Status` header                   |
| `requestCost`        | `number?`                        | Actual cost in USD                     |
| `estimatedCost`      | `number?`                        | Upfront worst-case estimate            |
| `creditsRemaining`   | `number?`                        | Post-request credit balance (PTB only) |
| `gatewayMode`        | `"passthrough" \| "translated"?` | `Cova-Gateway-Mode` header             |
| `translationWarning` | `string?`                        | Dropped fields (only when translated)  |

## Related [#related]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Streaming FinOps" href="/features/routing/streaming">
    The primary delivery path for cost metadata in streaming responses
  </Card>

  <Card title="Request Pipeline" href="#">
    How the gateway records metadata during post-processing
  </Card>
</div>


# Response headers (https://docs.corevalue.dev/api-reference/response-headers)



When CoreValue forwards a request to a provider, the response you get back is the provider's response with a few `Cova-*` headers added. They tell you which request this was, who served it, and what it cost. Every request header you can send is on [Headers](/api-reference/headers).

## On every forwarded response [#on-every-forwarded-response]

These are set on every response that came back from a provider, streaming or not, including a provider's own error.

| Header              | What it says                                                                                                                                                                       | Example                                         |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `Cova-Id`           | The request's ID. Search for it on **Requests**, or pass it to [`/meta`](/api-reference/request-cost-lookup).                                                                      | `Cova-Id: 550e8400-e29b-41d4-a716-446655440000` |
| `Cova-Status`       | `success` when the provider answered HTTP `200`, otherwise `error`.                                                                                                                | `Cova-Status: success`                          |
| `Cova-Provider`     | The provider that served the request, after any [fallback](/features/routing/fallbacks) or [routing by model name](/features/routing/model-routing). Gemini is `google-ai-studio`. | `Cova-Provider: groq`                           |
| `Cova-Model`        | The model that served the request. It can differ from the one you asked for after a fallback, a budget **Downgrade model**, or routing by model name.                              | `Cova-Model: gpt-4o-mini`                       |
| `Cova-Gateway-Mode` | `translated` when CoreValue translated an OpenAI-format request into Anthropic, Gemini or Bedrock format and mapped the reply back; otherwise `passthrough`.                       | `Cova-Gateway-Mode: translated`                 |

## Present only sometimes [#present-only-sometimes]

| Header                       | When it is present                                                                        | What it says                                                                                                                                                                           |
| ---------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Cova-Request-Cost`          | Every **non-streaming** response. Never on a stream.                                      | The request's cost in US dollars, to 10 decimal places. `0.0000000000` when the request failed or the model is [unpriced](/additional/supported-models#models-corevalue-cannot-price). |
| `Cova-Translation-Warning`   | A translated request that had fields the target provider does not accept.                 | The dropped request fields, comma-separated, for example `logprobs,top_k`.                                                                                                             |
| `X-Cova-Retry-Count`         | CoreValue retried the request at least once (a provider `429`, `5xx` or network failure). | How many retries were made before this response.                                                                                                                                       |
| `Cova-Credits-Remaining`     | A non-streaming request paid from CoreValue credits rather than your own provider key.    | Your credit balance after this request, in US dollars.                                                                                                                                 |
| `Cova-Request-Cost-Estimate` | Reserved for requests paid from credits. Not sent today.                                  | The upfront worst-case estimate held for the request.                                                                                                                                  |

Requests made with your own provider keys never carry the two credit headers.

## Streaming: the final `cova` frame [#streaming-the-final-cova-frame]

A stream's headers are sent before the first token, when the cost is not yet known, so a streamed response has no `Cova-Request-Cost`. Instead, after the provider's stream ends (after `data: [DONE]` on OpenAI-format streams), CoreValue appends one more Server-Sent Event:

```
data: {"cova":{"status":"success","requestId":"550e8400-e29b-41d4-a716-446655440000","provider":"openai","model":"gpt-4o-mini","gatewayMode":"passthrough","requestCost":…}}
```

| Field                                                     | Same as                                                                                                       |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `requestId`, `status`, `provider`, `model`, `gatewayMode` | The headers above                                                                                             |
| `translationWarning`                                      | `Cova-Translation-Warning`, when present                                                                      |
| `requestCost`                                             | The request's cost. **Left out when the cost is $0** — an unpriced model, or a stream that returned no usage. |

The frame is added only to `text/event-stream` responses, such as OpenAI-format chat completions and Anthropic Messages. A Bedrock `invoke-with-response-stream` (a binary event stream) and a Gemini `streamGenerateContent` without `alt=sse` (streaming JSON) are passed through untouched; read their cost from [`GET /gateway/v1/meta/{requestId}`](/api-reference/request-cost-lookup) instead, for up to five minutes after the request ends.

<Callout type="info">
  If your SSE client does not expect an extra event after `[DONE]`, ignore a final chunk whose JSON has a top-level `cova` key. The [CoreValue SDK](/sdks/cova-sdk) reads it for you.
</Callout>

## Cache hits [#cache-hits]

A response served from the [cache](/features/routing/caching) is the provider's original response, replayed with its original headers and these three added:

| Header                  | What it says                                                                                   |
| ----------------------- | ---------------------------------------------------------------------------------------------- |
| `Cova-Cache`            | `HIT`                                                                                          |
| `Cova-Cache-Bucket-Idx` | Which cached copy was returned, when you keep more than one with `Cova-Cache-Bucket-Max-Size`. |
| `Cova-Cache-Latency`    | How long, in milliseconds, the original request took.                                          |

A cache hit has no `Cova-Id`, `Cova-Request-Cost` or other headers from the tables above. A miss is an ordinary forwarded response, with no `Cova-Cache` header.

## Responses CoreValue writes itself [#responses-corevalue-writes-itself]

When CoreValue refuses a request before it reaches the provider — a bad key, a budget or key cap, a [key scope](/features/access/key-scopes), a rate limit — the response is an error body in your provider's format, without `Cova-*` headers. A rate-limit refusal carries `Retry-After`, in seconds. See [Errors](/api-reference/errors).

## Other gateways [#other-gateways]

* **MCP** responses carry no `Cova-*` headers. Tool calls are recorded on **Requests** → **MCP tool calls**; see [Tool-call logging and cost](/features/mcp/tool-calls).
* **A2A** responses carry `Cova-Task-Trace` and `Cova-Call-Id`; see [Call an agent](/features/agents/call).

Other headers from the provider are passed through unchanged, except `Content-Encoding` and `Content-Length`, which CoreValue removes because it has already decoded the body.


# Tag your traffic (https://docs.corevalue.dev/features/cost-attribution)



CoreValue already knows which key, team, model and provider each request used. It does not know who in *your* product the request was for, or what feature made it. Send that with a few `Cova-*` headers, and every page of the console can split spend by it.

## The headers [#the-headers]

| Header                              | Tells CoreValue                                      | Where it shows up                                                                                                  |
| ----------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `Cova-Customer-Id`                  | Which of your customers the request was for          | **Requests** → **Customer** column · **Spend** → **Customer** · **Budgets & Limits** → **Customers** · **Reports** |
| `Cova-Feature-Id`                   | Which feature or surface made it, such as `checkout` | **Requests** → **Feature** column · **Spend** → **Feature**                                                        |
| `Cova-User-Id`                      | The person using your product                        | **Requests** → **User** column · **Spend** → **End user**                                                          |
| `Cova-Session-Id`, `-Name`, `-Path` | Which run it belongs to, and which step              | **Requests** → **Run** column · **Spend** → **Run** · [Runs](/features/cost-tracking/runs)                         |
| `Cova-Property-<Name>`              | Anything else you want to split by                   | **Requests** → a column per property · **Spend** → **Property**                                                    |
| `Cova-Actor`                        | Whether a person or a program produced the turn      | **Requests** → **Actor** column · the run's **Actors**                                                             |
| `Cova-Team-Id`                      | A team label for your own records                    | The request's **Properties** only — see below                                                                      |

Every header is optional. The &#x2A;*Spend by …** panel on the [Dashboard](/features/cost-tracking/dashboard) uses the same tabs as Spend.

## Send them [#send-them]

<Tabs items="[&#x22;cURL&#x22;, &#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/v1/chat/completions \
      -H "Authorization: Bearer $COVA_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Cova-Customer-Id: acme-corp" \
      -H "Cova-Feature-Id: support-bot" \
      -H "Cova-User-Id: user_abc123" \
      -H "Cova-Property-Environment: production" \
      -d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Hello"}]}'
    ```
  </Tab>

  <Tab>
    ```python
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hello"}],
        extra_headers={
            "Cova-Customer-Id": "acme-corp",
            "Cova-Feature-Id": "support-bot",
            "Cova-User-Id": "user_abc123",
            "Cova-Property-Environment": "production",
        },
    )
    ```
  </Tab>

  <Tab>
    ```typescript
    const response = await client.chat.completions.create(
      { model: "gpt-4o-mini", messages: [{ role: "user", content: "Hello" }] },
      {
        headers: {
          "Cova-Customer-Id": "acme-corp",
          "Cova-Feature-Id": "support-bot",
          "Cova-User-Id": "user_abc123",
          "Cova-Property-Environment": "production",
        },
      },
    );
    ```
  </Tab>
</Tabs>

The client is the one from the [Quickstart](/getting-started/quickstart). To check a tag arrived, open the request on [Requests](/features/cost-tracking/requests) and look under **Properties**.

## Customers and features [#customers-and-features]

`Cova-Customer-Id` and `Cova-Feature-Id` are cut at 256 characters; a value of only spaces is ignored. Spend groups customers without regard to case, so `Acme-Corp` and `acme-corp` are one customer — the same rule [customer budgets](/features/budgets/budgets-and-limits#set-a-customer-budget) use. The **Customer** column on Requests shows the ID exactly as it was sent.

To cap a customer's spend, see [Per-customer budgets](/guides/per-customer-budgets).

## End users [#end-users]

`Cova-User-Id` takes letters, digits and `_ - : .`, up to 256 characters. A value with any other character is dropped. It also lets a [rate limit](/features/rate-limits) count per user.

An end user is not a member of your organisation, and member budgets never use this header.

## Properties [#properties]

`Cova-Property-<Name>` attaches any label you choose. Send as many as you need.

* **Name**: lowercased, hyphens become underscores, then only letters, digits and `_` are allowed, up to 64 characters. A name that breaks the rule is dropped. `Cova-Property-Deploy-Region` is stored as `deploy_region`.
* **Value**: cut at 256 characters.

Each property appears in **Requests** as a column, off by default — turn it on with **Manage columns** — and as a field in **Filter**. On **Spend**, the **Property** tab groups spend by the values of the property you pick.

## Runs [#runs]

Send `Cova-Session-Id` on every request of one agent run or workflow, with `Cova-Session-Name` and `Cova-Session-Path` if you want them. See [Runs](/features/cost-tracking/runs).

## Actor [#actor]

`Cova-Actor` is `human`, `agent` or `unknown`. Without it, the gateway infers `agent` when the request feeds a tool result back to the model, and `unknown` otherwise. Hover the **Actor** column to see whether a value was declared or inferred; filter on it with **Actor** and **Actor source**. The inference misses the first turn of every agent run, so send the header if you need the count to be right. Details are in [Headers](/api-reference/headers#cova-actor).

## What you can trust [#what-you-can-trust]

These headers are **self-reported**: the gateway records what your application sends. Anyone holding a key can send a different customer or user ID.

* To pin a key to particular customers, [scope it](/features/access/key-scopes). A key scoped to one customer fills in `Cova-Customer-Id` itself.
* **Teams come from the key, never from `Cova-Team-Id`.** The **Team** tab on Spend, team budgets, chargeback and who can see a request all use the team the key is assigned to. `Cova-Team-Id` is kept on the request as a property and changes none of them.
* A property cannot impersonate these headers. `Cova-Property-Cova-Customer-Id` is stored as the property `cova_customer_id` and is not a customer ID; `Cova-Property-cova_actor` is dropped.

Keep IDs stable — an ID that changes splits one customer into two rows. Do not put secrets or personal data in a header value.

## Related [#related]

* [Headers reference](/api-reference/headers) — every header and its exact rules
* [Spend](/features/cost-tracking/spend) — spend by each of these tags
* [Budgets & Limits](/features/budgets/budgets-and-limits) — cap a customer
* [Unit economics](/features/reports/unit-economics) — cost per customer against what you charge


# Use the Anthropic SDK (https://docs.corevalue.dev/getting-started/anthropic)



CoreValue's gateway accepts Anthropic's native Messages API. Point the Anthropic
SDK at it and use your CoreValue key in place of your Anthropic key. Requests
are forwarded in Anthropic's own format, so tool use, extended thinking, prompt
caching and streaming are passed through unchanged.

<Callout type="info">
  **Before you start:** add your Anthropic key under **Gateway → Provider
  keys** and create a CoreValue key under **Governance → Keys**. The
  [Quickstart](/getting-started/quickstart) walks through both.
</Callout>

## Change two lines [#change-two-lines]

<Tabs items="[&#x22;Python&#x22;, &#x22;TypeScript&#x22;, &#x22;Environment variables&#x22;]">
  <Tab>
    ```python
    import os
    from anthropic import Anthropic

    client = Anthropic(
        base_url="https://gateway.corevalue.dev",
        api_key=os.environ["COVA_API_KEY"],  # sk-cova-…
    )

    message = client.messages.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello!"}],
    )
    print(message.content[0].text)
    ```
  </Tab>

  <Tab>
    ```typescript
    import Anthropic from "@anthropic-ai/sdk";

    const client = new Anthropic({
      baseURL: "https://gateway.corevalue.dev",
      apiKey: process.env.COVA_API_KEY, // sk-cova-…
    });

    const message = await client.messages.create({
      model: "claude-sonnet-4-5-20250929",
      max_tokens: 1024,
      messages: [{ role: "user", content: "Hello!" }],
    });
    console.log(message.content[0].type === "text" ? message.content[0].text : "");
    ```
  </Tab>

  <Tab>
    Both SDKs read these when you construct the client with no arguments:

    ```bash
    ANTHROPIC_BASE_URL=https://gateway.corevalue.dev
    ANTHROPIC_API_KEY=sk-cova-…
    ```
  </Tab>
</Tabs>

The base URL has no `/v1`: the SDK adds `/v1/messages` itself.

<Callout type="warn">
  Pass the CoreValue key as the **API key**, not as an auth token. The gateway
  reads it from the `x-api-key` header, which is where the SDK sends `api_key`.
  `auth_token` (and the `ANTHROPIC_AUTH_TOKEN` variable) sends it as a bearer
  token instead, and the request is refused.
</Callout>

## Tag requests with who and what they are for [#tag-requests-with-who-and-what-they-are-for]

<Tabs items="[&#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```python
    client.messages.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello!"}],
        extra_headers={
            "Cova-Customer-Id": "acme-corp",
            "Cova-Feature-Id": "support-bot",
        },
    )
    ```
  </Tab>

  <Tab>
    ```typescript
    await client.messages.create(
      {
        model: "claude-sonnet-4-5-20250929",
        max_tokens: 1024,
        messages: [{ role: "user", content: "Hello!" }],
      },
      { headers: { "Cova-Customer-Id": "acme-corp", "Cova-Feature-Id": "support-bot" } },
    );
    ```
  </Tab>
</Tabs>

Set them once on the client with `default_headers` (Python) or
`defaultHeaders` (TypeScript) if every request from a service shares them. See
[Tag your traffic](/features/cost-attribution) and the full list in
[Headers](/api-reference/headers).

## Read the cost of a request [#read-the-cost-of-a-request]

Non-streaming responses carry `Cova-Request-Cost` in US dollars:

<Tabs items="[&#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```python
    raw = client.messages.with_raw_response.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello!"}],
    )
    print(raw.headers.get("Cova-Request-Cost"))
    message = raw.parse()
    ```
  </Tab>

  <Tab>
    ```typescript
    const { data, response } = await client.messages
      .create({
        model: "claude-sonnet-4-5-20250929",
        max_tokens: 1024,
        messages: [{ role: "user", content: "Hello!" }],
      })
      .withResponse();
    console.log(response.headers.get("Cova-Request-Cost"));
    ```
  </Tab>
</Tabs>

For a streamed request, the cost arrives after the stream ends. See
[Request cost lookup](/api-reference/request-cost-lookup) and
[How cost is calculated](/additional/how-cost-is-calculated).

## When a budget runs out [#when-a-budget-runs-out]

If a budget is set to block, the gateway refuses the request with HTTP `429`
before it reaches Anthropic, and the SDK raises its usual rate-limit error. The
Anthropic SDK retries a `429` by default, so a refused request may be tried
again a few times before the error reaches your code. See
[Errors](/api-reference/errors) and [Budgets and limits](/features/budgets/budgets-and-limits).

## Using Claude Code [#using-claude-code]

Claude Code reads the same two variables. See [Claude Code](/getting-started/claude-code).


# Claude Code (https://docs.corevalue.dev/getting-started/claude-code)



Claude Code reads two environment variables that send its requests to any
Anthropic-compatible endpoint. Point them at CoreValue, and every request your
developers make is logged with its cost, attributed to a key, and checked
against your budgets before it reaches Anthropic.

<Callout type="info">
  **You will need:** an Anthropic API key added to CoreValue as a
  [provider key](/features/organization/provider-keys). Claude Code must use an API key.
  A Claude Pro or Max subscription login goes straight to Anthropic and cannot
  be routed through a gateway.
</Callout>

## Set up one developer [#set-up-one-developer]

<Steps>
  <Step>
    ### Create a CoreValue key for the developer [#create-a-corevalue-key-for-the-developer]

    In the console, click **Governance**, then **Keys**, then **Generate API key**.
    Name the key after the developer, for example `claude-code-priya`, and pick
    their **Team**. Copy the `sk-cova-…` key.

    One key per developer is what makes per-developer spend and per-developer
    budgets possible. A shared key gives you one total.
  </Step>

  <Step>
    ### Point Claude Code at CoreValue [#point-claude-code-at-corevalue]

    Add this to `~/.claude/settings.json`:

    ```json
    {
      "apiKeyHelper": "echo sk-cova-…",
      "env": {
        "ANTHROPIC_BASE_URL": "https://gateway.corevalue.dev"
      }
    }
    ```

    `apiKeyHelper` is a command whose output Claude Code uses as the API key. `echo`
    works; a command that reads the key from your secrets manager is better. Claude
    Code uses it without asking, including in non-interactive runs such as
    `claude -p` in CI.

    You can use environment variables instead:

    ```bash
    export ANTHROPIC_BASE_URL=https://gateway.corevalue.dev
    export ANTHROPIC_API_KEY=sk-cova-…
    ```

    The first time, Claude Code asks whether to use the API key it found. Answer
    yes. Until you have, `claude -p` stops with *Not logged in*, so use
    `apiKeyHelper` on machines where nobody can answer.

    Either way, do not use `ANTHROPIC_AUTH_TOKEN`. It sends the key as a bearer
    token, and the gateway, which reads the key from the `x-api-key` header,
    refuses it.
  </Step>

  <Step>
    ### Run Claude Code and check the console [#run-claude-code-and-check-the-console]

    Start `claude` and ask it anything. Then open **Requests** in the console: the
    requests appear under the developer's key, with model and cost.
  </Step>
</Steps>

## Roll it out to a team [#roll-it-out-to-a-team]

* **Set it centrally.** Claude Code reads a managed settings file that
  developers cannot override. Put `ANTHROPIC_BASE_URL` there, and an
  [`apiKeyHelper`](https://code.claude.com/docs/en/settings) script that
  fetches each developer's own key from your secrets tooling.
* **Label the work.** Claude Code sends extra headers from
  `ANTHROPIC_CUSTOM_HEADERS`, one `Name: Value` per line. Use it to tag the
  repository or project:

  ```bash
  export ANTHROPIC_CUSTOM_HEADERS="Cova-Feature-Id: payments-repo"
  ```

  `Cova-Team-Id` works the same way, but the key's team already attributes the
  spend, so you only need it when one key is shared across teams.

## Cap the spend [#cap-the-spend]

Budgets reset on the 1st of each month (UTC). Two work well for Claude Code:

| Budget                         | When it runs out                                                                                                                                                   |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Key cap**, one per developer | Their requests are refused with HTTP `429`, and Claude Code shows the error, until the month rolls over. Other developers carry on.                                |
| **Team budget**                | You choose: **block** the team, **downgrade** to a cheaper model from your downgrade mappings (for example Opus to Sonnet), or **alert** and let requests through. |

Blocking and downgrading need the Growth plan or higher; alerts work on every
plan. See [Budgets and limits](/features/budgets/budgets-and-limits).

## See where the money went [#see-where-the-money-went]

* **Spend** shows cost by key, team and model, so you can compare developers
  and teams. See [Spend](/features/cost-tracking/spend).
* **Chargeback** turns the month into a per-team report for finance. See
  [Chargeback](/features/reports/chargeback).
* A request that feeds a tool result back to the model is recorded with actor
  `agent`; the first request of each task is recorded as `unknown`. See
  [Cova-Actor](/api-reference/headers#cova-actor).


# Use the Google Gemini SDK (https://docs.corevalue.dev/getting-started/gemini)



## Overview [#overview]

Google's official `@google/genai` SDK (JavaScript) and `google-genai` SDK (Python) send requests in **Google's native Gemini format** — not OpenAI format. Cova AI Gateway supports this natively, so your application sends **only your Cova API key**. Add your Google API key once under **Gateway → Provider keys**; the gateway uses it upstream and your application never sees it.

The gateway handles:

* **Auth translation** — your Cova key authenticates with the gateway; the gateway injects the Google key upstream
* **Native format passthrough** — `contents`/`candidates` format preserved, no lossy translation
* **Billing** — Google bills you directly, on the Google key you add to CoreValue (BYOK)
* **Observability** — cost, tokens, latency tracked automatically
* **Streaming** — `:streamGenerateContent` with SSE passthrough

<Callout type="info">
  This uses Google's native SDK, so the full feature set (grounding, function calling, multimodal, etc.) is preserved without translation loss.
</Callout>

## Quick Start [#quick-start]

### JavaScript / TypeScript [#javascript--typescript]

<Steps>
  <Step title="Install the SDK">
    ```bash
    npm install @google/genai
    ```
  </Step>

  <Step title="Point the SDK at Cova">
    ```typescript
    import { GoogleGenAI } from "@google/genai";

    const genAI = new GoogleGenAI({
      apiKey: process.env.COVA_API_KEY,  // Your Cova key; the Google key stays in CoreValue
      httpOptions: {
        baseUrl: "https://gateway.corevalue.dev",
      },
    });

    const response = await genAI.models.generateContent({
      model: "gemini-2.5-flash",
      contents: "What is the capital of France?",
    });

    console.log(response.text);
    ```
  </Step>
</Steps>

### Python [#python]

<Steps>
  <Step title="Install the SDK">
    ```bash
    pip install google-genai
    ```
  </Step>

  <Step title="Point the SDK at Cova">
    ```python
    from google import genai

    client = genai.Client(
        api_key=os.environ["COVA_API_KEY"],  # Your Cova key; the Google key stays in CoreValue
        http_options={"baseUrl": "https://gateway.corevalue.dev"},
    )

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents="What is the capital of France?",
    )
    print(response.text)
    ```
  </Step>
</Steps>

## How It Works [#how-it-works]

The Google SDK sends requests in Google's native format:

| Aspect           | Value                                                                 |
| ---------------- | --------------------------------------------------------------------- |
| **Endpoint**     | `https://gateway.corevalue.dev/v1beta/models/{model}:generateContent` |
| **Auth header**  | `x-goog-api-key: <your-cova-key>`                                     |
| **Request body** | `contents: [{role, parts}]`                                           |
| **Streaming**    | `:streamGenerateContent?alt=sse`                                      |
| **Response**     | `{candidates: [{content}]}`                                           |

The gateway:

1. Detects the native Gemini format from the URL path (`:generateContent` / `:streamGenerateContent`)
2. Authenticates your Cova key
3. Resolves the model to a provider and the Google key you added
4. Forwards the request to Google's API in the same format
5. Streams the response back unmodified
6. Logs usage (tokens, cost, latency) asynchronously

## Streaming [#streaming]

```typescript
const response = await genAI.models.generateContentStream({
  model: "gemini-2.5-flash",
  contents: "Tell me a story",
});

for await (const chunk of response) {
  process.stdout.write(chunk.text);
}
```

The gateway passes SSE chunks through unmodified — no buffering, no format translation.

## Function Calling [#function-calling]

```typescript
const response = await genAI.models.generateContent({
  model: "gemini-2.5-flash",
  contents: "What's the weather in Tokyo?",
  config: {
    tools: [{
      functionDeclarations: [{
        name: "get_weather",
        parameters: {
          type: "object",
          properties: {
            city: { type: "string" }
          },
          required: ["city"]
        }
      }]
    }],
  },
});
```

## Vertex AI [#vertex-ai]

For Vertex AI (Google Cloud) models, use the same SDK with a different base URL:

```typescript
const genAI = new GoogleGenAI({
  apiKey: process.env.COVA_API_KEY,
  httpOptions: {
    baseUrl: "https://gateway.corevalue.dev",
    apiVersion: "v1beta1",
    headers: {
      "Cova-Provider": "vertex",
    },
  },
});
```

<Callout type="info">
  Vertex AI requires a Google Cloud project with Vertex AI API enabled. Configure your GCP service account key in [Provider keys](https://app.corevalue.dev/gateway?tab=provider-keys) for BYOK.
</Callout>

## Related [#related]

* [Provider Routing](/features/routing/other-providers) — How the gateway routes to the best provider
* [Error Handling](/api-reference/errors) — Fallback behavior and error codes


# Use the OpenAI SDK (https://docs.corevalue.dev/getting-started/openai)



CoreValue's gateway speaks the OpenAI API. Point the OpenAI SDK at it and use
your CoreValue key in place of your OpenAI key. Every request is then checked
against your budgets, sent to OpenAI with the provider key stored in CoreValue,
and logged with its cost.

<Callout type="info">
  **Before you start:** add your OpenAI key under **Gateway → Provider keys**
  and create a CoreValue key under **Governance → Keys**. The
  [Quickstart](/getting-started/quickstart) walks through both.
</Callout>

## Change two lines [#change-two-lines]

<Tabs items="[&#x22;Python&#x22;, &#x22;TypeScript&#x22;, &#x22;Environment variables&#x22;]">
  <Tab>
    ```python
    import os
    from openai import OpenAI

    client = OpenAI(
        base_url="https://gateway.corevalue.dev/v1",
        api_key=os.environ["COVA_API_KEY"],  # sk-cova-…
    )

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hello!"}],
    )
    print(response.choices[0].message.content)
    ```
  </Tab>

  <Tab>
    ```typescript
    import OpenAI from "openai";

    const client = new OpenAI({
      baseURL: "https://gateway.corevalue.dev/v1",
      apiKey: process.env.COVA_API_KEY, // sk-cova-…
    });

    const response = await client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [{ role: "user", content: "Hello!" }],
    });
    console.log(response.choices[0].message.content);
    ```
  </Tab>

  <Tab>
    Both SDKs read these when you construct the client with no arguments:

    ```bash
    OPENAI_BASE_URL=https://gateway.corevalue.dev/v1
    OPENAI_API_KEY=sk-cova-…
    ```
  </Tab>
</Tabs>

The rest of your code stays as it is. Streaming and tool calls work as they do
against OpenAI.

## What works through the gateway [#what-works-through-the-gateway]

| Endpoint             | Notes                                                                                                                   |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `chat.completions`   | Including streaming.                                                                                                    |
| `completions`        | Legacy completions.                                                                                                     |
| `embeddings`         |                                                                                                                         |
| `images` and `audio` |                                                                                                                         |
| `responses`          | Supported for a subset of models and providers. See [Responses API](/features/routing/provider-features/responses-api). |

## Call other providers with the same client [#call-other-providers-with-the-same-client]

To reach Anthropic, Groq, Google and the rest through the OpenAI SDK, use the
base URL `https://gateway.corevalue.dev/gateway/v1` and put the provider in
front of the model name:

```python
client = OpenAI(
    base_url="https://gateway.corevalue.dev/gateway/v1",
    api_key=os.environ["COVA_API_KEY"],
)

client.chat.completions.create(
    model="groq/llama-3.3-70b-versatile",
    messages=[{"role": "user", "content": "Hello!"}],
)
```

On the plain `/v1` base URL the prefix is not read, and the request goes to
OpenAI. The full list of prefixes is on [Other providers](/features/routing/other-providers).

## Tag requests with who and what they are for [#tag-requests-with-who-and-what-they-are-for]

Send attribution headers so spend can be split, and budgeted, by customer,
feature or team:

<Tabs items="[&#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```python
    client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hello!"}],
        extra_headers={
            "Cova-Customer-Id": "acme-corp",
            "Cova-Feature-Id": "support-bot",
        },
    )
    ```
  </Tab>

  <Tab>
    ```typescript
    await client.chat.completions.create(
      { model: "gpt-4o-mini", messages: [{ role: "user", content: "Hello!" }] },
      { headers: { "Cova-Customer-Id": "acme-corp", "Cova-Feature-Id": "support-bot" } },
    );
    ```
  </Tab>
</Tabs>

Set them once on the client with `default_headers` (Python) or
`defaultHeaders` (TypeScript) if every request from a service shares them. See
[Tag your traffic](/features/cost-attribution) and the full list in
[Headers](/api-reference/headers).

## Read the cost of a request [#read-the-cost-of-a-request]

Every response carries `Cova-Id` and, for non-streaming requests,
`Cova-Request-Cost` in US dollars. Read them through the SDK's raw response:

<Tabs items="[&#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```python
    raw = client.chat.completions.with_raw_response.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Hello!"}],
    )
    print(raw.headers.get("Cova-Request-Cost"))
    response = raw.parse()
    ```
  </Tab>

  <Tab>
    ```typescript
    const { data, response } = await client.chat.completions
      .create({ model: "gpt-4o-mini", messages: [{ role: "user", content: "Hello!" }] })
      .withResponse();
    console.log(response.headers.get("Cova-Request-Cost"));
    ```
  </Tab>
</Tabs>

For a streamed request, the cost arrives after the stream ends. See
[Request cost lookup](/api-reference/request-cost-lookup).

## When a budget runs out [#when-a-budget-runs-out]

If a budget is set to block, the gateway refuses the request with HTTP `429`
before it reaches OpenAI, and the SDK raises its usual rate-limit error. The
OpenAI SDK retries a `429` by default, so a refused request may be tried again
a few times before the error reaches your code. See [Errors](/api-reference/errors)
and [Budgets and limits](/features/budgets/budgets-and-limits).


# Quickstart (https://docs.corevalue.dev/getting-started/quickstart)













By the end of this page you will have sent a request through the CoreValue gateway, seen what it cost and which customer it was for, and put a budget on that customer. You change two lines in your existing code — the **base URL** (where requests are sent) and the **API key** — and add one header that names the customer.

<Callout type="info">
  **You will need:** a CoreValue account, and an API key from at least one model provider (OpenAI, Anthropic, Google, Groq, …). CoreValue forwards your requests with your provider key — you keep your provider account and pay the provider directly.
</Callout>

<Steps>
  <Step>
    ### Sign in to the console [#sign-in-to-the-console]

    Go to [app.corevalue.dev](https://app.corevalue.dev/signin) and sign in with your email and password.

    <img src="/screenshots/guides/quickstart/signin.webp" alt="Sign-in page" width="450" style="{ border: &#x22;1px solid var(--color-fd-border)&#x22;, borderRadius: 8, padding: 16 }" />

    The first time you sign in you are asked to name your organisation. Everything you set up — keys, budgets, requests — belongs to that organisation, and you can invite teammates to it later.
  </Step>

  <Step>
    ### Add your provider key [#add-your-provider-key]

    CoreValue needs a key for each provider you want to call. It keeps the key and uses it on your behalf; your applications never see it.

    1. In the top bar, click **Gateway**, then the **Provider keys** tab.
    2. Find your provider — type in &#x2A;*Search providers...** to filter the list.
    3. Paste your key into the provider's API key field (for OpenAI, **OpenAI API Key**, starting `sk-`).
    4. Leave &#x2A;*Enable for AI Gateway (BYOK)** ticked — *BYOK* means "bring your own key": CoreValue calls the provider with your key, and the provider bills you — then click **Save**.

        <img alt="Gateway → Provider keys" src="__img0" />

    The card now reads *1 key configured*. Repeat for any other provider you use.

    <Callout type="info">
      **Azure OpenAI, AWS Bedrock and Vertex AI** ask for more than a key — base URI, API version and deployment name for Azure; region for Bedrock and Vertex. Have those to hand.
    </Callout>
  </Step>

  <Step>
    ### Create a CoreValue API key [#create-a-corevalue-api-key]

    Your applications send a **CoreValue API key** with each request, instead of your provider key. That way the provider key stays inside CoreValue, and every request is tied to a key you can see and limit. ([What's the difference?](/getting-started/key-concepts#keys))

    1. In the top bar, click **Governance**, then the **Keys** tab.
    2. Click **Generate API key**.
    3. Enter a **Key Name** that says where the key will be used — for example `checkout-service` — and pick a **Team** if you use teams.
    4. Click **Create Key**.

        <img alt="Create Key dialog" src="__img1" />

    Copy the key now. It starts with `sk-cova-`, and **you will not be able to see it again** after you close the dialog.

        <img alt="Your CoreValue Key, shown once" src="__img2" />

    Store it where your application reads secrets, for example:

    ```bash
    export COVA_API_KEY="sk-cova-..."
    ```
  </Step>

  <Step>
    ### Send your first request [#send-your-first-request]

    Point your OpenAI client at the gateway and use your CoreValue key. Nothing else in your code changes.

    <Tabs items="[&#x22;Python&#x22;, &#x22;TypeScript&#x22;, &#x22;cURL&#x22;]">
      <Tab>
        ```python
        import os
        from openai import OpenAI

        client = OpenAI(
            base_url="https://gateway.corevalue.dev/v1",
            api_key=os.environ["COVA_API_KEY"],
        )

        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": "Say hello in one sentence."}],
        )
        print(response.choices[0].message.content)
        ```
      </Tab>

      <Tab>
        ```typescript
        import OpenAI from "openai";

        const client = new OpenAI({
          baseURL: "https://gateway.corevalue.dev/v1",
          apiKey: process.env.COVA_API_KEY,
        });

        const response = await client.chat.completions.create({
          model: "gpt-4o-mini",
          messages: [{ role: "user", content: "Say hello in one sentence." }],
        });
        console.log(response.choices[0].message.content);
        ```
      </Tab>

      <Tab>
        ```bash
        curl -i https://gateway.corevalue.dev/v1/chat/completions \
          -H "Authorization: Bearer $COVA_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "model": "gpt-4o-mini",
            "messages": [{"role": "user", "content": "Say hello in one sentence."}]
          }'
        ```
      </Tab>
    </Tabs>

    The response is exactly what OpenAI returns. CoreValue adds a few headers of its own (`curl -i` shows them):

    | Header              | Example                | Meaning                                               |
    | ------------------- | ---------------------- | ----------------------------------------------------- |
    | `Cova-Id`           | `550e8400-e29b-41d4-…` | The request's ID. Search for it on the Requests page. |
    | `Cova-Status`       | `success`              | `success` or `error`.                                 |
    | `Cova-Provider`     | `openai`               | The provider that served the request.                 |
    | `Cova-Model`        | `gpt-4o-mini`          | The model that served it.                             |
    | `Cova-Request-Cost` | `0.0000210000`         | What the request cost, in US dollars.                 |

    For a streamed response the cost arrives in the stream's last event instead of a header — see [Streaming](/features/routing/streaming).
  </Step>

  <Step>
    ### See it in the console [#see-it-in-the-console]

    In the top bar, click **Requests**. Your request is at the top of the list, with its status, cost, model, provider, tokens and latency.

        <img alt="Requests page" src="__img3" />

    Click the row to open it. The panel shows the model, cost, latency, tokens and any properties you sent — and the request and response bodies when your organisation stores them (see [Body retention](/additional/request-storage)).

        <img alt="A request opened on the Requests page" src="__img4" />
  </Step>

  <Step>
    ### Say which customer each request is for [#say-which-customer-each-request-is-for]

    Send the `Cova-Customer-Id` header with the ID you already use for that customer — an account ID, a tenant slug, anything stable. This is what turns your AI bill into a cost per customer.

    <Tabs items="[&#x22;Python&#x22;, &#x22;TypeScript&#x22;, &#x22;cURL&#x22;]">
      <Tab>
        ```python
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": "Say hello in one sentence."}],
            extra_headers={"Cova-Customer-Id": "acme-corp"},
        )
        ```
      </Tab>

      <Tab>
        ```typescript
        const response = await client.chat.completions.create(
          {
            model: "gpt-4o-mini",
            messages: [{ role: "user", content: "Say hello in one sentence." }],
          },
          { headers: { "Cova-Customer-Id": "acme-corp" } },
        );
        ```
      </Tab>

      <Tab>
        ```bash
        curl https://gateway.corevalue.dev/v1/chat/completions \
          -H "Authorization: Bearer $COVA_API_KEY" \
          -H "Content-Type: application/json" \
          -H "Cova-Customer-Id: acme-corp" \
          -d '{
            "model": "gpt-4o-mini",
            "messages": [{"role": "user", "content": "Say hello in one sentence."}]
          }'
        ```
      </Tab>
    </Tabs>

    Go to **Budgets & Limits → Customers**. `acme-corp` is listed with what it has cost you this month.
  </Step>

  <Step>
    ### Set a first customer budget [#set-a-first-customer-budget]

    On **Budgets & Limits → Customers**, click **Add limit**, choose the **Customer**, and click **Continue**. Enter the &#x2A;*Monthly budget (USD)**, set **At the budget** to **Refuse the request** or **Downgrade the model**, and click **Save**.

    The budget is checked before the model is called. Once the customer's spend reaches it, their requests get HTTP `429` (or move to a cheaper model from your downgrade mappings), and every other customer is unaffected. [Per-customer budgets](/guides/per-customer-budgets) covers the error your application sees and how to show each customer's margin.
  </Step>
</Steps>

## Call other providers [#call-other-providers]

**Anthropic** — use Anthropic's own SDK with the gateway as its base URL. Add your Anthropic key under **Provider keys** first.

<Tabs items="[&#x22;Python&#x22;, &#x22;TypeScript&#x22;]">
  <Tab>
    ```python
    import os
    from anthropic import Anthropic

    client = Anthropic(
        base_url="https://gateway.corevalue.dev",
        api_key=os.environ["COVA_API_KEY"],
    )

    message = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=256,
        messages=[{"role": "user", "content": "Say hello in one sentence."}],
    )
    print(message.content[0].text)
    ```
  </Tab>

  <Tab>
    ```typescript
    import Anthropic from "@anthropic-ai/sdk";

    const client = new Anthropic({
      baseURL: "https://gateway.corevalue.dev",
      apiKey: process.env.COVA_API_KEY,
    });

    const message = await client.messages.create({
      model: "claude-haiku-4-5-20251001",
      max_tokens: 256,
      messages: [{ role: "user", content: "Say hello in one sentence." }],
    });
    console.log(message.content[0].text);
    ```
  </Tab>
</Tabs>

**Groq, Mistral, DeepSeek, xAI and other OpenAI-compatible providers** — keep the OpenAI client, set the base URL to `https://gateway.corevalue.dev/gateway/v1`, and put the provider in front of the model name:

```python
client = OpenAI(
    base_url="https://gateway.corevalue.dev/gateway/v1",
    api_key=os.environ["COVA_API_KEY"],
)

response = client.chat.completions.create(
    model="groq/llama-3.3-70b-versatile",
    messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
```

| Provider             | Model name to send   |
| -------------------- | -------------------- |
| Groq                 | `groq/<model>`       |
| Mistral AI           | `mistral/<model>`    |
| DeepSeek             | `deepseek/<model>`   |
| xAI (Grok)           | `xai/<model>`        |
| Perplexity           | `perplexity/<model>` |
| Fireworks            | `fireworks/<model>`  |
| Cerebras             | `cerebras/<model>`   |
| DeepInfra            | `deepinfra/<model>`  |
| OpenRouter           | `openrouter/<model>` |
| Novita               | `novita/<model>`     |
| Nebius Token Factory | `nebius/<model>`     |
| Baseten              | `baseten/<model>`    |
| Chutes               | `chutes/<model>`     |
| Canopy Wave          | `canopywave/<model>` |

`<model>` is the provider's own model ID, exactly as the provider documents it.

## Add more context (optional) [#add-more-context-optional]

Two more headers break spend down further, beside `Cova-Customer-Id`:

```python
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Say hello in one sentence."}],
    extra_headers={
        "Cova-Customer-Id": "acme-corp",     # the customer account
        "Cova-User-Id": "user-123",          # the end user in your product
        "Cova-Property-Feature": "onboarding",  # any label you want to group by
    },
)
```

See the [headers reference](/api-reference/headers) for every header.

## Troubleshooting [#troubleshooting]

<Accordions>
  <Accordion title="401 Unauthorized">
    The `Authorization` header is missing or the key is wrong. Send `Authorization: Bearer sk-cova-...` with your **CoreValue** key — not your provider key.
  </Accordion>

  <Accordion title="&#x22;No openai provider key available&#x22; (or another provider)">
    Your organisation has no key for that provider. Add one under **Gateway** → **Provider keys**, and make sure &#x2A;*Enable for AI Gateway (BYOK)** is ticked.
  </Accordion>

  <Accordion title="The provider says the model does not exist">
    * For OpenAI models on `/v1`, send the bare model name (`gpt-4o-mini`), with no prefix.
    * For other OpenAI-compatible providers, use the `/gateway/v1` base URL and a prefixed name (`groq/...`).
    * For Anthropic, use the Anthropic SDK with base URL `https://gateway.corevalue.dev`.
  </Accordion>

  <Accordion title="429 Too Many Requests">
    A budget, key cap or rate limit on your organisation refused the request. The error message says which one. See [Budgets & Limits](/features/budgets/budgets-and-limits#what-your-application-sees).
  </Accordion>

  <Accordion title="The request isn't on the Requests page">
    * Check the time range at the top of the page covers now.
    * Check the team picker in the top bar: if the key belongs to a team, choose that team or **All teams**.
    * Requests appear within a few seconds. Refresh the page.
  </Accordion>
</Accordions>

## Next steps [#next-steps]

* [Budgets & Limits](/features/budgets/budgets-and-limits) — cap what each team, member, customer or key can spend
* [Headers reference](/api-reference/headers) — every `Cova-*` header
* [Framework integrations](/sdks/integrations/overview) — LangChain, the Vercel AI SDK, the OpenAI Agents SDK and more


# Use with AI coding assistants (https://docs.corevalue.dev/guides/ai-coding-assistants)



Coding assistants write integration code from what they already know, and they
do not know CoreValue. Add the instructions below to your repository and the
assistant will route requests through the gateway, send the right key in the
right header, and tag spend the way your budgets and reports expect.

The same text works for every assistant; only the file it goes in changes.

## The instructions [#the-instructions]

```markdown
## CoreValue

This project sends LLM requests through the CoreValue gateway, which checks
budgets before each request and records its cost. Docs:
https://docs.corevalue.dev (index for tools: https://docs.corevalue.dev/llms.txt)

### Connecting
- Never put a provider key (OpenAI, Anthropic, Google) in this codebase. Provider
  keys are stored in CoreValue. The app sends only its CoreValue key, from the
  `COVA_API_KEY` environment variable. It starts with `sk-cova-`.
- OpenAI SDK: `base_url="https://gateway.corevalue.dev/v1"`, `api_key=COVA_API_KEY`.
- Other providers through the OpenAI SDK: `base_url="https://gateway.corevalue.dev/gateway/v1"`
  and a provider prefix on the model, e.g. `groq/llama-3.3-70b-versatile`.
  Prefixes are ignored on the plain `/v1` base URL.
- Anthropic SDK: `base_url="https://gateway.corevalue.dev"` (no `/v1`),
  `api_key=COVA_API_KEY`. Use `api_key`, never `auth_token`: the gateway reads
  the key from `x-api-key`.
- Google Gen AI SDK: `apiKey=COVA_API_KEY`, `httpOptions.baseUrl="https://gateway.corevalue.dev"`.

### Attributing spend (request headers)
- `Cova-Customer-Id`: which of our customers the request serves. Budgets and
  margin reports group by it.
- `Cova-Feature-Id`: which feature made the request, e.g. `support-bot`.
- `Cova-Team-Id`: which team the spend belongs to, when one key serves several.
- `Cova-Session-Id`: the same value on every request of one task or agent run.
- `Cova-Property-<Name>`: any other label, e.g. `Cova-Property-Env: production`.
Set shared ones once with the SDK's default headers.

### Responses
- `Cova-Request-Cost` header: the request's cost in US dollars (non-streaming).
- `Cova-Id` header: the request id.
- A refused request returns HTTP 429 when a budget is set to block. Handle it
  like a rate limit; the SDKs retry 429 automatically.
- `Cova-Fallbacks: ["model-a", "model-b"]` tries other models in order if the
  first returns a retryable error.
```

## For Claude Code [#for-claude-code]

Paste the instructions into `CLAUDE.md` at the root of the repository. Claude
Code reads it at the start of every session.

If you also run Claude Code itself through CoreValue, see
[Claude Code](/getting-started/claude-code).

## For Cursor [#for-cursor]

Save the instructions as `.cursor/rules/corevalue.mdc`, with this header above
them so the rule is always applied:

```markdown
---
description: How this project calls LLMs through CoreValue
alwaysApply: true
---
```

## For GitHub Copilot [#for-github-copilot]

Paste the instructions into `.github/copilot-instructions.md`. Copilot adds the
file to every chat in the repository.

## For other assistants [#for-other-assistants]

Codex and other agents that read `AGENTS.md` take the same text there. Any tool
that can fetch a URL can also read the docs directly:

| URL                                                          | What it is                                           |
| ------------------------------------------------------------ | ---------------------------------------------------- |
| [`/llms.txt`](https://docs.corevalue.dev/llms.txt)           | An index of every page, with links and descriptions. |
| [`/llms-full.txt`](https://docs.corevalue.dev/llms-full.txt) | The text of every page in one file.                  |

Point the assistant at `llms.txt` when it needs a detail the instructions above
leave out, such as a header's exact validation rules or a provider prefix.


# Migrate from Helicone (https://docs.corevalue.dev/guides/migrate-from-helicone)



Helicone was acquired by Mintlify in March 2026 and is in maintenance mode. If
you route traffic through Helicone today, moving to CoreValue is mostly a
rename: both are a proxy you reach by changing a base URL, and most
`Helicone-*` headers have a `Cova-*` header with the same name and meaning.

What you gain is control as well as visibility. Helicone tells you what you
spent. CoreValue also checks every request against your budgets **before** it
reaches the provider, and can stop or downgrade it when a budget has run out.

## What changes [#what-changes]

|                            | Helicone                              | CoreValue                                                     |
| -------------------------- | ------------------------------------- | ------------------------------------------------------------- |
| OpenAI-compatible base URL | `https://oai.helicone.ai/v1`          | `https://gateway.corevalue.dev/v1`                            |
| Anthropic base URL         | `https://anthropic.helicone.ai`       | `https://gateway.corevalue.dev`                               |
| Your provider key          | Sent by your app on every request     | Stored once in CoreValue, encrypted. Your app never sends it. |
| Your proxy key             | `Helicone-Auth: Bearer sk-helicone-…` | The SDK's normal API key field, set to your `sk-cova-…` key   |

The provider key is the one real change in shape. With Helicone your app holds
the OpenAI or Anthropic key and adds a second header for Helicone. With
CoreValue you add the provider key in the console, and your app sends only the
CoreValue key. One secret leaves your application instead of two.

## Migrate in five steps [#migrate-in-five-steps]

<Steps>
  <Step>
    ### Add your provider keys to CoreValue [#add-your-provider-keys-to-corevalue]

    Sign in at [app.corevalue.dev](https://app.corevalue.dev/signin). In the top
    bar, click **Gateway**, then **Provider keys**, and add a key for each provider
    you call through Helicone. See [Provider keys](/features/organization/provider-keys).
  </Step>

  <Step>
    ### Create a CoreValue API key [#create-a-corevalue-api-key]

    Click **Governance**, then **Keys**, then **Generate API key**. Copy the
    `sk-cova-…` key: it is shown once. If you used separate Helicone keys per
    service or environment, create one CoreValue key for each, so their spend stays
    separate.
  </Step>

  <Step>
    ### Change the base URL and key [#change-the-base-url-and-key]

    <Tabs items="[&#x22;Python (OpenAI)&#x22;, &#x22;TypeScript (OpenAI)&#x22;, &#x22;Python (Anthropic)&#x22;]">
      <Tab>
        ```python
        from openai import OpenAI

        # Before
        client = OpenAI(
            api_key=os.environ["OPENAI_API_KEY"],
            base_url="https://oai.helicone.ai/v1",
            default_headers={"Helicone-Auth": f"Bearer {os.environ['HELICONE_API_KEY']}"},
        )

        # After
        client = OpenAI(
            api_key=os.environ["COVA_API_KEY"],
            base_url="https://gateway.corevalue.dev/v1",
        )
        ```
      </Tab>

      <Tab>
        ```typescript
        import OpenAI from "openai";

        // Before
        const client = new OpenAI({
          apiKey: process.env.OPENAI_API_KEY,
          baseURL: "https://oai.helicone.ai/v1",
          defaultHeaders: { "Helicone-Auth": `Bearer ${process.env.HELICONE_API_KEY}` },
        });

        // After
        const client = new OpenAI({
          apiKey: process.env.COVA_API_KEY,
          baseURL: "https://gateway.corevalue.dev/v1",
        });
        ```
      </Tab>

      <Tab>
        ```python
        from anthropic import Anthropic

        # Before
        client = Anthropic(
            api_key=os.environ["ANTHROPIC_API_KEY"],
            base_url="https://anthropic.helicone.ai",
            default_headers={"Helicone-Auth": f"Bearer {os.environ['HELICONE_API_KEY']}"},
        )

        # After
        client = Anthropic(
            api_key=os.environ["COVA_API_KEY"],
            base_url="https://gateway.corevalue.dev",
        )
        ```
      </Tab>
    </Tabs>

    If you only set environment variables, the change is two lines:

    ```bash
    # Before
    OPENAI_BASE_URL=https://oai.helicone.ai/v1
    # After
    OPENAI_BASE_URL=https://gateway.corevalue.dev/v1
    OPENAI_API_KEY=sk-cova-…
    ```
  </Step>

  <Step>
    ### Rename your Helicone headers [#rename-your-helicone-headers]

    Replace the `Helicone-` prefix with `Cova-` for every header in the first table
    below. Remove `Helicone-Auth`. Then check the second table for the few headers
    that work differently.
  </Step>

  <Step>
    ### Send a request and check it in the console [#send-a-request-and-check-it-in-the-console]

    Send one request, then open **Requests** in the console. You should see it with
    its model, provider, cost and every property you sent. Each response also
    carries `Cova-Id` and `Cova-Request-Cost` headers, so you can confirm from code.
    See [Response headers](/api-reference/headers#response-headers).
  </Step>
</Steps>

## Header mapping [#header-mapping]

### Same name, same meaning [#same-name-same-meaning]

| Helicone                         | CoreValue                                                         |
| -------------------------------- | ----------------------------------------------------------------- |
| `Helicone-Property-{Name}`       | `Cova-Property-{Name}`                                            |
| `Helicone-User-Id`               | `Cova-User-Id`                                                    |
| `Helicone-Session-Id`            | `Cova-Session-Id`                                                 |
| `Helicone-Session-Name`          | `Cova-Session-Name`                                               |
| `Helicone-Session-Path`          | `Cova-Session-Path`                                               |
| `Helicone-Prompt-Id`             | `Cova-Prompt-Id`                                                  |
| `Helicone-Cache-Enabled`         | `Cova-Cache-Enabled`                                              |
| `Helicone-Cache-Bucket-Max-Size` | `Cova-Cache-Bucket-Max-Size`                                      |
| `Helicone-Cache-Seed`            | `Cova-Cache-Seed`                                                 |
| `Cache-Control`                  | `Cache-Control` (unchanged)                                       |
| `Helicone-RateLimit-Policy`      | `Cova-RateLimit-Policy`, same syntax: `100;w=60;u=request;s=user` |

<Callout type="info">
  If you used a custom property to label customers, features or teams, for
  example `Helicone-Property-Customer`, send it as `Cova-Customer-Id`,
  `Cova-Feature-Id` or `Cova-Team-Id` instead. These three get their own
  columns, so cost groups by customer, feature or team on the
  [reports](/features/reports/unit-economics) and budgets can be set on them. A
  `Cova-Property-*` header is recorded, but it is not a budget scope.
</Callout>

### Works differently [#works-differently]

| Helicone                                                                 | In CoreValue                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Helicone-Auth`                                                          | Remove it. The CoreValue key goes in the SDK's API key field.                                                                                                                                                                                                                                                                                                     |
| `Helicone-Fallbacks`                                                     | `Cova-Fallbacks` takes a JSON array of **model names**, tried in order, for example `["groq/llama-3.3-70b-versatile", "gpt-4o-mini"]`. Helicone took target URLs. Prefixed names need the `/gateway/v1` base URL (see below). See [Fallbacks](/features/routing/fallbacks).                                                                                       |
| `Helicone-Target-URL`, `Helicone-OpenAI-Api-Base`                        | Not needed. Use the base URL `https://gateway.corevalue.dev/gateway/v1` and choose the provider with a model prefix, such as `groq/llama-3.3-70b-versatile`. On the plain `/v1` base URL, prefixes are not read and requests go to OpenAI. Azure base URIs and deployments are set on the provider key. See [Other providers](/features/routing/other-providers). |
| `Helicone-Retry-Enabled` and the `helicone-retry-*` headers              | Retries are set on the gateway, not per request. `X-Cova-Retry-Count` tells you how many were made.                                                                                                                                                                                                                                                               |
| `Helicone-LLM-Security-Enabled`, `Helicone-Moderations-Enabled`          | Set as an organisation policy in the console, not per request. See [Guardrails](/features/access/guardrails).                                                                                                                                                                                                                                                     |
| `Helicone-Model-Override`                                                | Not supported. Cost is computed from the model that actually served the request.                                                                                                                                                                                                                                                                                  |
| `Helicone-Request-Id`                                                    | Not read. The gateway assigns `Cova-Id` to every request.                                                                                                                                                                                                                                                                                                         |
| `Helicone-Omit-Request`, `Helicone-Omit-Response`                        | Not per request. See [Request storage](/additional/request-storage) for what is kept.                                                                                                                                                                                                                                                                             |
| `Helicone-Posthog-Key`, `Helicone-Posthog-Host`                          | Not supported. To send request events to your own systems, use [Webhooks](/features/alerts/webhooks).                                                                                                                                                                                                                                                             |
| `Helicone-Token-Limit-Exception-Handler`, `Helicone-Stream-Force-Format` | Not supported.                                                                                                                                                                                                                                                                                                                                                    |

Requests that share a `Cova-Session-Id` appear together on the console's
[Runs](/features/cost-tracking/runs) page, which is where Helicone's Sessions view went.

Response headers follow the same rename: `Helicone-Id` becomes `Cova-Id`, and
`Helicone-Cache` becomes `Cova-Cache`.

## What you get that Helicone did not have [#what-you-get-that-helicone-did-not-have]

* **Budgets that act.** Set a monthly budget on the organisation, a team, a
  member, a customer or a key. When it runs out, CoreValue refuses the request
  with HTTP `429`, or, for organisation and team budgets, downgrades it to a
  cheaper model. Alerts work on every plan; refusing and downgrading need the
  Growth plan. See [Budgets and limits](/features/budgets/budgets-and-limits).
* **Chargeback and unit economics.** Monthly spend per team for finance, and
  cost and margin per customer. See [Chargeback](/features/reports/chargeback) and
  [Unit economics](/features/reports/unit-economics).
* **MCP and agent traffic.** Tool calls to MCP servers and calls between agents
  go through the same gateway, with access rules on who may call what. See
  [MCP gateway](/features/mcp/overview).
* **Roles, SSO and an audit log** for the console. See [Roles](/features/organization/roles).

## Before you switch [#before-you-switch]

* **History stays in Helicone.** CoreValue starts counting from your first
  request through the gateway. Export anything you need from Helicone first.
* **Run both for a while if you want to compare.** Point one service or one
  environment at CoreValue and leave the rest on Helicone until the numbers
  match what you expect.
* **Prompt templates** managed in Helicone do not move over. Keep the prompt
  text in your code, and send `Cova-Prompt-Id` to group requests by prompt.

Stuck on a header that is not listed here? Tell us at
[support@corevalue.dev](mailto:support@corevalue.dev).


# Keep AI working when a provider fails (https://docs.corevalue.dev/guides/provider-failover)



Providers have outages, rate limits and bad minutes. This guide covers what the
gateway does about them automatically, and the three things you can add so a
failure does not reach your users.

## What happens automatically: retries [#what-happens-automatically-retries]

Every request that fails with status `429`, `500`, `502`, `503`, `504` or `529`,
or with a network error, is retried up to three times. The wait between tries
grows from half a second to at most eight seconds, and a provider's
`Retry-After` is honoured up to that limit. The gateway stops retrying a request
after 60 seconds of waiting.

Nothing to set up. A response that needed retries carries `X-Cova-Retry-Count`.

## 1. Fall back to another model [#1-fall-back-to-another-model]

If a model still fails after its retries, the gateway can try the next model in
a list. Send the list with each request:

```python
client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello!"}],
    extra_headers={"Cova-Fallbacks": '["gpt-4o-mini"]'},
)
```

The next model is tried when the current one fails with `401`, `402`, `403`,
`429`, `500`, `502`, `503`, `504` or `529`. A list holds up to five models. The
`Cova-Model` response header tells you which model answered.

To set this once instead of in code, create a chain on **Gateway → Routing**
(**Fallback Chains**). A chain starts with the model it protects and lists what
to try after it; for each step, **On Codes** says which statuses move on to the
next (empty means any error). A `Cova-Fallbacks` header on a request takes
precedence over a saved chain.

## 2. Fall back to another provider [#2-fall-back-to-another-provider]

A fallback model goes to the same provider unless you name the provider. To
cross providers, use the base URL `https://gateway.corevalue.dev/gateway/v1`
and prefixed model names, with a provider key added for each provider:

```python
client = OpenAI(
    base_url="https://gateway.corevalue.dev/gateway/v1",
    api_key=os.environ["COVA_API_KEY"],
)

client.chat.completions.create(
    model="groq/llama-3.3-70b-versatile",
    messages=[{"role": "user", "content": "Hello!"}],
    extra_headers={
        "Cova-Fallbacks": '["anthropic/claude-sonnet-4-5-20250929", '
                          '"together/meta-llama/Llama-3.3-70B-Instruct-Turbo"]'
    },
)
```

If Groq is down, the request is answered by Anthropic, then Together, in the
OpenAI format your code expects. See [Other providers](/features/routing/other-providers)
for every prefix.

## 3. Downgrade instead of refusing [#3-downgrade-instead-of-refusing]

A budget that runs out does not have to stop your product. Set an organisation
or team budget's action to **Downgrade model** and add a mapping for each model
you use, for example `gpt-4o` → `gpt-4o-mini`. Over budget, requests are
rewritten to the cheaper model and served; `Cova-Model` shows the model used.

A request for a model with no mapping is refused, so map every model. See
[Budget configuration](/guides/budget-configuration).

## Repeated requests: caching [#repeated-requests-caching]

For requests that repeat exactly, such as the same prompt sent many times,
`Cova-Cache-Enabled: true` serves later copies from CoreValue's cache without
calling the provider. A cache hit carries `Cova-Cache: HIT`. It does not help
with new requests during an outage. See [Caching](/features/routing/caching).

## Not available yet [#not-available-yet]

Adding a second key for the same provider does not spread traffic across keys
or move to the other key when one is rate-limited; the gateway uses the first
key. Use a fallback model on another provider instead.

## Related [#related]

* [Fallbacks](/features/routing/fallbacks): the header format in full.
* [Errors](/api-reference/errors): what reaches your code when everything fails.
* [Streaming](/features/routing/streaming)


# Cova SDK (TypeScript and Python) (https://docs.corevalue.dev/sdks/cova-sdk)



The `@cova/gateway` SDK (TypeScript) and `cova-gateway` SDK (Python) are thin
header helpers that attach per-call FinOps metadata — user IDs, prompt IDs,
custom properties, and fallback chains — to your LLM requests through the
CoreValue AI Gateway.

<Callout type="info">
  The SDK is a **thin header helper** — it does NOT wrap or intercept your LLM
  calls. You still use the OpenAI SDK (or any HTTP client) to make requests.
  The SDK only builds the `Cova-*` headers and parses the gateway response
  metadata. See [D-5: SDK Scope](/#sdk-scope) for the rationale.
</Callout>

## Prerequisites [#prerequisites]

1. [Sign up](https://corevalue.dev/signup) for a CoreValue account
2. Generate an API key at [API Keys](https://app.corevalue.dev/governance?tab=keys)
   — it will start with `sk-cova-`
3. Set your gateway base URL to `https://gateway.corevalue.dev/v1`

## Install [#install]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```bash
    npm install @cova/gateway
    # peer dependency: openai ^5.10.2
    npm install openai
    ```
  </Tab>

  <Tab>
    ```bash
    pip install cova-gateway
    # runtime dependency: httpx >=0.28.0
    ```
  </Tab>
</Tabs>

## buildHeaders / build\_headers [#buildheaders--build_headers]

Build a `Cova-*` header record to attach to any HTTP client. This is the
simplest way to add FinOps metadata to your requests.

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { buildHeaders } from "@cova/gateway";

    const headers = buildHeaders({
      userId: "user_abc123",
      promptId: "prompt_42",
      properties: {
        region: "us-east-1",
        tier: "premium",
      },
      fallbacks: ["groq/llama-3.3-70b-versatile", "together/meta-llama/Llama-3.3-70B-Instruct-Turbo"],
    });

    // headers === {
    //   "Cova-User-Id": "user_abc123",
    //   "Cova-Prompt-Id": "prompt_42",
    //   "Cova-Property-Region": "us-east-1",
    //   "Cova-Property-Tier": "premium",
    //   "Cova-Fallbacks": '["groq/llama-3.3-70b-versatile","together/meta-llama/Llama-3.3-70B-Instruct-Turbo"]',
    // }
    ```
  </Tab>

  <Tab>
    ```python
    from cova_gateway import build_headers

    headers = build_headers(
        user_id="user_abc123",
        prompt_id="prompt_42",
        properties={
            "region": "us-east-1",
            "tier": "premium",
        },
        fallbacks=["groq/llama-3.3-70b-versatile", "together/meta-llama/Llama-3.3-70B-Instruct-Turbo"],
    )

    # headers === {
    #     "Cova-User-Id": "user_abc123",
    #     "Cova-Prompt-Id": "prompt_42",
    #     "Cova-Property-Region": "us-east-1",
    #     "Cova-Property-Tier": "premium",
    #     "Cova-Fallbacks": '["groq/llama-3.3-70b-versatile","together/meta-llama/Llama-3.3-70B-Instruct-Turbo"]',
    # }
    ```
  </Tab>
</Tabs>

Pass these headers to your HTTP client alongside `Authorization: Bearer $COVA_API_KEY`.

## wrap [#wrap]

Wrap a raw provider response and its headers into a `CovaResponse<T>` that
includes the parsed `CovaGatewayMeta` (request ID, provider, model, cost, etc.).

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { wrap } from "@cova/gateway";

    // `response` is the raw fetch Response from the gateway
    const data = await response.json();
    const result = wrap(data, response.headers);

    console.log(result.data);          // raw provider response
    console.log(result.gateway);       // CovaGatewayMeta
    console.log(result.gateway.requestId);   // "550e8400-..."
    console.log(result.gateway.requestCost); // 0.0015
    ```
  </Tab>

  <Tab>
    ```python
    from cova_gateway import wrap

    # `response` is the raw httpx.Response from the gateway
    data = response.json()
    result = wrap(data, dict(response.headers))

    print(result.data)              # raw provider response
    print(result.gateway)           # CovaGatewayMeta
    print(result.gateway.request_id)   # "550e8400-..."
    print(result.gateway.request_cost) # 0.0015
    ```
  </Tab>
</Tabs>

## streamChat / stream\_chat [#streamchat--stream_chat]

Stream a chat completion through the OpenAI client while automatically
capturing the gateway's final SSE `cova` frame. The last yielded value is
`{ cova: CovaGatewayMeta }` — all prior values are normal OpenAI chunks.

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import OpenAI from "openai";
    import { streamChat } from "@cova/gateway";

    const client = new OpenAI({
      baseURL: "https://gateway.corevalue.dev/v1",
      apiKey: process.env.COVA_API_KEY, // sk-cova-XXXXXXXXXXXXXXXX
    });

    const stream = streamChat(
      client,
      {
        model: "gpt-4o-mini",
        messages: [{ role: "user", content: "Hello, world!" }],
        stream: true,
      },
      {
        userId: "user_abc123",
        properties: { feature: "chat" },
      }
    );

    for await (const chunk of stream) {
      if ("cova" in chunk) {
        // Final frame — gateway metadata
        console.log(chunk.cova.requestCost);   // 0.0015
        console.log(chunk.cova.provider);      // "openai"
      } else {
        // Normal OpenAI streaming chunk
        process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
      }
    }
    ```
  </Tab>

  <Tab>
    ```python
    import asyncio
    from openai import AsyncOpenAI
    from cova_gateway import stream_chat

    client = AsyncOpenAI(
        base_url="https://gateway.corevalue.dev/v1",
        api_key="sk-cova-XXXXXXXXXXXXXXXX",  # os.environ["COVA_API_KEY"]
    )

    async def main():
        stream = stream_chat(
            client,
            {
                "model": "gpt-4o-mini",
                "messages": [{"role": "user", "content": "Hello, world!"}],
                "stream": True,
            },
            cova_opts={
                "user_id": "user_abc123",
                "properties": {"feature": "chat"},
            },
        )

        async for chunk in stream:
            if "cova" in chunk:
                # Final frame — gateway metadata
                print(chunk["cova"].request_cost)   # 0.0015
                print(chunk["cova"].provider)        # "openai"
            else:
                # Normal OpenAI streaming chunk
                delta = chunk.get("choices", [{}])[0].get("delta", {})
                print(delta.get("content", ""), end="")

    asyncio.run(main())
    ```
  </Tab>
</Tabs>

## fetchNative / fetch\_native [#fetchnative--fetch_native]

Make a direct HTTP request to the gateway and get back both the raw response
and the parsed `CovaGatewayMeta`. Useful when you're not using the OpenAI SDK.

<Callout type="info">
  `fetchNative` is **async** in TypeScript (uses global `fetch`).\
  `fetch_native` is **synchronous** in Python (uses `httpx.Client`).
</Callout>

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { fetchNative } from "@cova/gateway";

    const { response, gateway } = await fetchNative(
      "https://gateway.corevalue.dev/v1/chat/completions",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${process.env.COVA_API_KEY}`,
        },
        body: JSON.stringify({
          model: "gpt-4o-mini",
          messages: [{ role: "user", content: "Hello!" }],
        }),
        cova: {
          userId: "user_abc123",
          properties: { feature: "chat" },
        },
      }
    );

    const data = await response.json();
    console.log(gateway.requestId);   // "550e8400-..."
    console.log(gateway.requestCost);  // 0.0015
    ```
  </Tab>

  <Tab>
    ```python
    from cova_gateway import fetch_native

    response, gateway = fetch_native(
        "https://gateway.corevalue.dev/v1/chat/completions",
        init={
            "method": "POST",
            "headers": {
                "Content-Type": "application/json",
                "Authorization": "Bearer sk-cova-XXXXXXXXXXXXXXXX",
            },
            "json": {
                "model": "gpt-4o-mini",
                "messages": [{"role": "user", "content": "Hello!"}],
            },
        },
        cova_opts={
            "user_id": "user_abc123",
            "properties": {"feature": "chat"},
        },
    )

    data = response.json()
    print(gateway.request_id)    # "550e8400-..."
    print(gateway.request_cost)  # 0.0015
    ```
  </Tab>
</Tabs>

## CovaHeaderOptions [#covaheaderoptions]

All SDK functions accept the same options object for per-call metadata:

| Field      | TS name      | Python name  | Type                     | Validation                                |
| ---------- | ------------ | ------------ | ------------------------ | ----------------------------------------- |
| User ID    | `userId`     | `user_id`    | `string`                 | `[a-zA-Z0-9_\-:.]`, max 256               |
| Prompt ID  | `promptId`   | `prompt_id`  | `string`                 | `[a-zA-Z0-9_\-:.]`, max 256               |
| Properties | `properties` | `properties` | `Record<string, string>` | name `[a-zA-Z0-9_]` max 64; value max 256 |
| Fallbacks  | `fallbacks`  | `fallbacks`  | `string[]`               | max 5 models, compact JSON                |

## CovaGatewayMeta [#covagatewaymeta]

The gateway returns metadata via response headers (non-streaming) or the final
SSE frame (streaming). The SDK parses it into this shape:

| Field               | TS                   | Python                | Source                       |
| ------------------- | -------------------- | --------------------- | ---------------------------- |
| Request ID          | `requestId`          | `request_id`          | `Cova-Id`                    |
| Provider            | `provider`           | `provider`            | `Cova-Provider`              |
| Model               | `model`              | `model`               | `Cova-Model`                 |
| Status              | `status`             | `status`              | `Cova-Status`                |
| Request Cost        | `requestCost`        | `request_cost`        | `Cova-Request-Cost`          |
| Estimated Cost      | `estimatedCost`      | `estimated_cost`      | `Cova-Request-Cost-Estimate` |
| Credits Remaining   | `creditsRemaining`   | `credits_remaining`   | `Cova-Credits-Remaining`     |
| Gateway Mode        | `gatewayMode`        | `gateway_mode`        | `Cova-Gateway-Mode`          |
| Translation Warning | `translationWarning` | `translation_warning` | `Cova-Translation-Warning`   |

## Next Steps [#next-steps]

<Card title="SDK Migration" href="/sdks/cova-sdk">
  Migrating from @cova/sdk or @cova/async? See the side-by-side migration guide.
</Card>

<Card title="Provider Routing" href="/features/routing/other-providers">
  Learn how the gateway routes requests across major providers.
</Card>

<Card title="Header Directory" href="/api-reference/headers">
  Full reference for all Cova-\* request and response headers.
</Card>


# Claude Agent SDK (https://docs.corevalue.dev/sdks/mcp-package)





## Introduction [#introduction]

The [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/typescript) allows you to build powerful AI agents that can use tools and make decisions autonomously.

<Callout type="info">
  This integration uses [CoreValue's Model Context Protocol (MCP)](https://github.com/CoreValue/corevalue/tree/main/corevalue-mcp) to provide seamless AI Gateway access to your Claude agents.
</Callout>

<Callout type="info">
  The `@cova/mcp` package is a **Claude Desktop / Agent SDK integration tool**, generally available.
  It is separate from the [MCP Gateway](/features/mcp/overview), which proxies and logs tool calls
  to MCP servers and is available on plans that include it.
</Callout>

## Integration Steps [#integration-steps]

<Steps>
  <Step title="strings.generateKey">
    Sign up at [corevalue.dev](https://www.corevalue.dev) and generate an [API key](https://app.corevalue.dev/governance?tab=keys).

    Add the key for each provider you call under [Gateway › Provider keys](https://app.corevalue.dev/gateway?tab=provider-keys).
  </Step>

  <Step title="Install the CoreValue MCP Package">
    ```bash npm
    npm install @cova/mcp
    ```

    ```bash yarn
    yarn add @cova/mcp
    ```

    ```bash pnpm
    pnpm add @cova/mcp
    ```
  </Step>

  <Step title="Configure MCP Server in Your Application">
    <Tabs items="[&#x22;Claude Desktop (Development)&#x22;, &#x22;Claude Agent SDK&#x22;]">
      <Tab>
        Add to your Claude Desktop configuration:

        * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
        * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

        ```json
        {
          "mcpServers": {
            "corevalue": {
              "command": "npx",
              "args": ["@cova/mcp@latest"],
              "env": {
                "COVA_API_KEY": "sk-cova-XXXXXXXXXXXXXXXX"
              }
            }
          }
        }
        ```

        The CoreValue MCP tools will be automatically available in Claude Desktop.
      </Tab>

      <Tab>
        ```typescript
        import { query } from '@anthropic-ai/claude-agent-sdk';

        // Make a query with CoreValue MCP
        const result = await query({
          prompt: 'Use the use_ai_gateway tool to ask GPT-4o: "What is CoreValue?"',
          options: {
            mcpServers: {
              corevalue: {
                command: 'npx',
                args: ['@cova/mcp'],
                env: {
                  COVA_API_KEY: process.env.COVA_API_KEY
                }
              }
            },
            // Explicitly allow CoreValue MCP tools (recommended for production)
            allowedTools: [
              'mcp__corevalue__use_ai_gateway',
              'mcp__corevalue__query_requests',
              'mcp__corevalue__query_sessions'
            ]
          }
        });

        // Extract the response
        for await (const message of result.sdkMessages) {
          if (message.type === 'result' && message.result) {
            console.log('Response:', message.result);
          }
        }
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Test the Integration">
    ```typescript
    import { query } from '@anthropic-ai/claude-agent-sdk';

    const result = await query({
      prompt: 'Use the use_ai_gateway tool to generate a creative story about AI using gpt-4o with temperature 0.8',
      options: {
        mcpServers: {
          corevalue: {
            command: 'npx',
            args: ['@cova/mcp'],
            env: {
              COVA_API_KEY: process.env.COVA_API_KEY
            }
          }
        },
        allowedTools: ['mcp__corevalue__use_ai_gateway']
      }
    });

    // Get the response
    for await (const message of result.sdkMessages) {
      if (message.type === 'result' && message.result) {
        console.log(message.result);
      }
    }
    ```

    The agent will automatically use the `use_ai_gateway` tool to make the request through CoreValue AI Gateway.
  </Step>
</Steps>

## Available MCP Tools [#available-mcp-tools]

### `use_ai_gateway` [#use_ai_gateway]

Make requests to any LLM provider through CoreValue AI Gateway with automatic observability.

**Parameters:**

* `model` (required): Model name (e.g., `gpt-4o`, `anthropic/claude-sonnet-4-5-20250929`, `google/gemini-2.5-flash-lite` - see [Supported Models](https://corevalue.dev/models) for more)
* `messages` (required): Array of conversation messages
* `max_tokens` (optional): Maximum tokens to generate
* `temperature` (optional): Response randomness (0-2)
* `sessionId` (optional): Session ID for request grouping
* `sessionName` (optional): Human-readable session name
* `userId` (optional): User identifier for tracking
* `customProperties` (optional): Custom metadata for filtering

### `query_requests` [#query_requests]

Query historical requests for debugging and analysis with filters, pagination, and sorting.

### `query_sessions` [#query_sessions]

Query conversation sessions with filtering, search, and time range capabilities.

## Complete Working Examples [#complete-working-examples]

### Basic Agent with Session Tracking [#basic-agent-with-session-tracking]

```typescript
import { query } from '@anthropic-ai/claude-agent-sdk';

// Configure MCP server
const mcpConfig = {
  corevalue: {
    command: 'npx',
    args: ['@cova/mcp'],
    env: {
      COVA_API_KEY: process.env.COVA_API_KEY
    }
  }
};

// Make a request with session tracking
const sessionId = `chat-${Date.now()}`;
const result = await query({
  prompt: `Use the use_ai_gateway tool to ask Claude Sonnet: "Plan a 3-day trip to Japan"

Use these settings:
- sessionId: "${sessionId}"
- sessionName: "travel-planning"
- customProperties: {"topic": "travel", "destination": "japan"}`,
  options: {
    mcpServers: mcpConfig,
    allowedTools: ['mcp__corevalue__use_ai_gateway']
  }
});

// Extract response
for await (const message of result.sdkMessages) {
  if (message.type === 'result' && message.result) {
    console.log('Travel Plan:', message.result);
  }
}
```

### Multi-Model Comparison [#multi-model-comparison]

```typescript
import { query } from '@anthropic-ai/claude-agent-sdk';

const sessionId = `comparison-${Date.now()}`;
const result = await query({
  prompt: `Compare responses from multiple models on: "Explain quantum computing in simple terms"

1. Use GPT-4o-mini (fast, cost-effective)
2. Use Claude Sonnet (high quality)
3. Use GPT-4o (balanced)

Use sessionId: "${sessionId}" for all requests so I can compare them later.`,
  options: {
    mcpServers: {
      corevalue: {
        command: 'npx',
        args: ['@cova/mcp'],
        env: {
          COVA_API_KEY: process.env.COVA_API_KEY
        }
      }
    },
    allowedTools: ['mcp__corevalue__use_ai_gateway']
  }
});

// Get comparison results
for await (const message of result.sdkMessages) {
  if (message.type === 'result') {
    console.log('Comparison:', message.result);
  }
}
```

### Self-Analyzing Agent [#self-analyzing-agent]

```typescript
import { query } from '@anthropic-ai/claude-agent-sdk';

const result = await query({
  prompt: `Perform a task and then analyze your own performance:

1. Use the use_ai_gateway tool to generate a haiku about AI
2. Then use query_requests to check how much the request cost
3. Use query_sessions to see your recent activity
4. Provide a summary of your performance and costs`,
  options: {
    mcpServers: {
      corevalue: {
        command: 'npx',
        args: ['@cova/mcp'],
        env: {
          COVA_API_KEY: process.env.COVA_API_KEY
        }
      }
    },
    allowedTools: [
      'mcp__corevalue__use_ai_gateway',
      'mcp__corevalue__query_requests',
      'mcp__corevalue__query_sessions'
    ]
  }
});

// Get self-analysis
for await (const message of result.sdkMessages) {
  if (message.type === 'result') {
    console.log('Self-Analysis:', message.result);
  }
}
```

## Next Steps [#next-steps]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all supported models and providers
  </Card>

  <Card title="Dashboard" href="https://app.corevalue.dev">
    View your agent's requests and analytics
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Set up automatic failovers and routing
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution">
    Learn about advanced filtering and analytics
  </Card>
</div>


# Slack (https://docs.corevalue.dev/features/alerts/slack)



CoreValue posts to Slack through one connection per organisation. You connect it once on **Notifications** → **Slack**; each kind of notification then picks its own channels where it is configured.

## Before you start [#before-you-start]

| Requirement  | Detail                                                                                                                                          |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**    | Top bar → **Notifications** → **Slack** (`/notifications?tab=slack`).                                                                           |
| **Role**     | `egress.write`, which owners and admins hold: Slack is a destination outside your organisation. See [Roles and permissions](/references/roles). |
| **Plan**     | Every plan.                                                                                                                                     |
| **In Slack** | Permission to install apps in your workspace.                                                                                                   |

## Connect Slack [#connect-slack]

<Steps>
  <Step>
    ### Start the connection [#start-the-connection]

    Click **Connect Slack**. Slack opens and asks you to approve the CoreValue app for your workspace. The app asks to see your public and private channel lists and to post messages, including to public channels it has not joined.
  </Step>

  <Step>
    ### Approve [#approve]

    Choose the workspace and click **Allow**. Slack returns you to the CoreValue dashboard.
  </Step>

  <Step>
    ### Check [#check]

    Go back to **Notifications** → **Slack**. It now reads &#x2A;Connected to your Slack workspace.*
  </Step>
</Steps>

If the tab says &#x2A;Slack is not configured on this deployment.*, the CoreValue installation you are using has no Slack app set up.

## Private channels [#private-channels]

The app can post to public channels without joining them. For a private channel, invite the app first: mention **@CoreValue** in the channel and accept Slack's prompt to add it.

## What posts to Slack [#what-posts-to-slack]

| Notification                                                                           | Where you choose channels                                | Today                                                                                                          |
| -------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| [Scheduled reports](/features/reports/scheduled)                                       | **Reports** → **Scheduled reports** → **Slack Channels** | Channels can be chosen, but the weekly summary is not sent yet.                                                |
| [Budget alerts](/features/budgets/budget-alerts), including margin and velocity alerts | Not in the console yet                                   | Alerts post to Slack only where Slack channels have been set up for budget alerts; otherwise they go by email. |

Use a dedicated channel for budget notifications, so they do not get lost among other traffic.

## Change or remove the connection [#change-or-remove-the-connection]

The console has no button to disconnect Slack or switch it to another workspace. To stop CoreValue posting, remove the app from your workspace in Slack's app management settings; Slack then stops accepting its messages, although the tab here still reads *Connected*.

## Related [#related]

* [Budget alerts](/features/budgets/budget-alerts)
* [Scheduled reports](/features/reports/scheduled)


# Webhooks (https://docs.corevalue.dev/features/alerts/webhooks)



A webhook sends each request the gateway logs to an HTTPS endpoint you run: the
request and response bodies and, if you ask for them, the model, provider, cost,
tokens and latency. Use it to feed your own warehouse, billing or alerting.

<Callout type="info">
  Webhooks carry **request logs**. Budget alerts are sent by email and to
  [Slack](/features/alerts/slack); see [Budget alerts](/features/budgets/budget-alerts).
</Callout>

## Add a webhook [#add-a-webhook]

You need the Owner or Admin role.

1. Open **Notifications**, then the **Webhooks** tab.

2. Enter the **Endpoint URL**. It must be `https://`. Private, internal and
   cloud-metadata addresses are refused.

3. Optionally set:

   | Field                     | What it does                                                                                                                                                |
   | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
   | **Sample Rate**           | The percentage of requests to send, from 0 to 100. Default 100.                                                                                             |
   | **Properties Filters**    | Only send requests whose [properties](/features/cost-attribution) match. Each filter is a key and an exact value, and a request must match **all** of them. |
   | **Include Enhanced Data** | Adds the model, provider, cost, tokens, latency and a link to the full request and response. Only an Owner can turn this on.                                |

4. Save. The webhook's **HMAC Key** is shown in the list, hidden until you
   reveal it. Copy it: it is the secret you verify deliveries with.

Use **Test** on the webhook to send a sample delivery with made-up data.

## What you receive [#what-you-receive]

Each delivery is a `POST` with `Content-Type: application/json`:

```json
{
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "request_body": "{\"model\":\"gpt-4o-mini\",\"messages\":[…]}",
  "response_body": "{\"id\":\"chatcmpl-…\",\"choices\":[…]}",
  "user_id": "user_abc123",
  "model": "gpt-4o-mini",
  "provider": "openai",
  "request_response_url": "https://…",
  "metadata": {
    "cost": 0.0015,
    "promptTokens": 120,
    "completionTokens": 48,
    "totalTokens": 168,
    "latencyMs": 812
  }
}
```

| Field                                                   | Notes                                                                                                                                     |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `request_id`                                            | The request's `Cova-Id`.                                                                                                                  |
| `request_body`, `response_body`                         | The bodies as strings. A body over 10 KB is replaced with a short message; fetch the full one from `request_response_url` or the console. |
| `user_id`                                               | Present when the request carried `Cova-User-Id`.                                                                                          |
| `model`, `provider`, `metadata`, `request_response_url` | Only with **Include Enhanced Data**. `metadata.cost` is in US dollars. `request_response_url` is a signed link that expires.              |

## Verify the signature [#verify-the-signature]

Every delivery carries a `Cova-Signature` header: the HMAC-SHA256 of the raw
request body, keyed with the webhook's HMAC Key, as lowercase hex. Compute it over
the body exactly as received, before parsing the JSON, and compare in constant
time.

<Tabs items="[&#x22;Node.js&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { createHmac, timingSafeEqual } from "node:crypto";

    function isFromCoreValue(rawBody: string, signature: string, secret: string) {
      const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
      return (
        signature.length === expected.length &&
        timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
      );
    }
    ```
  </Tab>

  <Tab>
    ```python
    import hashlib
    import hmac

    def is_from_corevalue(raw_body: bytes, signature: str, secret: str) -> bool:
        expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
        return hmac.compare_digest(signature, expected)
    ```
  </Tab>
</Tabs>

The signature covers the body only, with no timestamp, so it proves a delivery
came from CoreValue but not when. Use `request_id` to discard a delivery you
have already processed.

## Delivery [#delivery]

* **Timing.** A delivery is sent about 30 seconds after the request is logged.
* **Success.** Any `2xx` response. Respond within 90 seconds.
* **No retries.** A delivery that fails or times out is not sent again. If you
  must not miss a request, reconcile against the [request log](/features/cost-tracking/requests),
  which records a request whether or not its delivery succeeded.
* **Order.** Deliveries are not guaranteed to arrive in the order the requests
  were made.


# Budget alerts (https://docs.corevalue.dev/features/budgets/budget-alerts)





Budget alerts tell people about spend before and when it hits a budget. They do not replace a budget's action: an **Alert only** budget still serves requests, and a **Block requests** budget still refuses them. Alerts give the budget's owner time to respond.

<img alt="Notifications › Budget alerts" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                        |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Notifications** → **Budget alerts** (`/notifications?tab=budget-alerts`).                                                         |
| **Role**    | Changing recipients or thresholds needs `spend_controls.write`, which owners and admins hold. See [Roles and permissions](/references/roles). |
| **Plan**    | Every plan.                                                                                                                                   |

## What sends an alert [#what-sends-an-alert]

| Alert              | When it is sent                                                                                                                          | How often                                                                                                |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| **Early warning**  | The **organisation** budget's spend crosses a percentage you set under **Warn before the budget is reached**.                            | Once per mark per month. If you raise the budget mid-month, a mark can be crossed, and announced, again. |
| **Budget reached** | An organisation or team budget is reached, whatever its action, or a member or customer budget refuses a request.                        | At most once an hour for each budget.                                                                    |
| **Margin**         | A customer's margin nears or falls below its floor. See [Customer pricing](/features/reports/customer-pricing#what-a-margin-floor-does). | Once per customer per month for each.                                                                    |
| **Velocity**       | A [velocity limit](/features/velocity-limits) trips.                                                                                     | See that page.                                                                                           |

Only the highest mark crossed is announced. If spend jumps from 75% to 96% between two requests and your marks are 80% and 95%, you hear about 95% only.

Budgets are monthly and reset on the 1st (UTC); see [Budgets & Limits](/features/budgets/budgets-and-limits#how-budgets-work).

## Set recipients and warnings [#set-recipients-and-warnings]

<Steps>
  <Step>
    ### Recipients [#recipients]

    Enter email addresses, separated by commas. Until you save a list, alerts go to the organisation owner, and the hint under the field says so.
  </Step>

  <Step>
    ### Warn before the budget is reached [#warn-before-the-budget-is-reached]

    Enter percentages of the organisation's monthly budget, for example `80%, 95%`. `80` and `0.8` both mean 80%. Leave it empty to be told only when a budget is reached.
  </Step>

  <Step>
    ### Channels per threshold [#channels-per-threshold]

    Each percentage gets a row with **Email** and **Slack** switches. By default every threshold uses every configured channel (*All channels*). Turn a channel off to keep that threshold out of it, for example 80% by email only and 95% by email and Slack. **Use all channels** puts a row back to the default. A threshold keeps at least one channel.

    Budget reached, margin and velocity alerts always use every channel.
  </Step>

  <Step>
    ### Save [#save]

    Click **Save Recipients*&#x2A;. You will see &#x2A;"Budget alert recipients saved"*.
  </Step>
</Steps>

To go back to sending alerts to the owner, click **Reset to owner**.

## Where alerts go [#where-alerts-go]

* **Email**: to the recipients above, or the owner if none are saved.
* **Slack**: to the Slack channels set up for budget alerts, through your [Slack connection](/features/alerts/slack).

<Callout type="info">
  The console does not yet let you choose Slack channels for budget alerts. Until channels are set up for your organisation, the **Slack*&#x2A; switches stay off with the note &#x2A;"No Slack channel is configured for budget alerts, so Slack cannot be chosen."*, and alerts go by email only.
</Callout>

Webhooks do not carry budget or margin alerts today.

## Troubleshooting [#troubleshooting]

<Accordions>
  <Accordion title="No warning arrived at a threshold">
    * Warnings apply to the **organisation** budget only, not to team, member or customer budgets.
    * Each mark is sent once a month. If it was crossed earlier this month, it will not be sent again unless the budget amount changed.
    * Only the highest mark crossed is sent.
  </Accordion>

  <Accordion title="Alerts go to the owner, not my list">
    The list was never saved, or was reset. Enter the addresses and click **Save Recipients**.
  </Accordion>
</Accordions>

## Related [#related]

* [Budgets & Limits](/features/budgets/budgets-and-limits)
* [Budget configuration](/guides/budget-configuration)
* [Customer pricing and margin](/features/reports/customer-pricing)


# Daily and yearly ceilings (https://docs.corevalue.dev/features/budgets/budget-windows)



A [monthly budget](/features/budgets/budgets-and-limits) caps a calendar month. A **daily or
yearly ceiling** caps a shorter or a longer stretch of the calendar: "no more
than $200 a day for this team", or "no more than $50,000 in our fiscal year for
the whole organisation". Each one resets at a time you can name: local midnight
for a day, the first day of your fiscal year for a year.

<Callout type="info">
  **Preview.** While daily and yearly ceilings are in preview, they do not
  refuse requests and no alert is sent. Nothing is limited until you add a
  ceiling, and no ceiling is set by default.
</Callout>

## What a window is [#what-a-window-is]

A **window** is the stretch of time a ceiling counts spend over. Every request's
cost lands in exactly one window of each kind, and when a window ends the next
one starts from zero.

| Window   | Starts                                         | Ends                       | Named                                          |
| -------- | ---------------------------------------------- | -------------------------- | ---------------------------------------------- |
| **Day**  | Midnight in your budget time zone              | The next midnight          | By its date, for example `2026-10-06`          |
| **Year** | Midnight on the 1st of your fiscal start month | The same date a year later | By the year it starts in, for example `FY2026` |

A fiscal year is named for the calendar year it **starts** in. If your fiscal
year starts in April, the year that begins on 1 April 2026 is `FY2026` and runs
to the end of 31 March 2027. With the default start month, January, a year is
the calendar year.

A day on which the clocks change is still one day: it is 23 or 25 hours long,
and it still runs from midnight to midnight.

### Monthly budgets are unchanged [#monthly-budgets-are-unchanged]

Monthly budgets, and the **Default cap per virtual key**, stay as they are. They
are set on the Organization, Teams, Members, Customers and Keys tabs, and **they
follow the UTC calendar month**: they reset at 00:00 UTC on the 1st, whatever
time zone you choose below. Daily and yearly ceilings sit alongside them. A
request has to have room in every ceiling and budget that covers it.

## A ceiling [#a-ceiling]

A ceiling applies to one **scope** over one **period**:

* **Scope:** the whole **organisation**, one **team** (every key assigned to
  it), or one **key** (a single proxy key).
* **Period:** **Daily** or **Yearly**.
* **Ceiling:** an amount in US dollars. You choose it; nothing is filled in for
  you.
* **Action:** what happens when spend in the window reaches the ceiling.

| Action             | What it does                                                                             |
| ------------------ | ---------------------------------------------------------------------------------------- |
| **Alert only**     | You are notified when spend in the window reaches the ceiling. Requests are not refused. |
| **Block requests** | Requests the ceiling covers are refused until the window resets.                         |

**Downgrade model** is not offered for daily and yearly ceilings. Each scope can
have one daily and one yearly ceiling, and an organisation can have up to 50
daily and yearly ceilings in all.

### What is counted [#what-is-counted]

Ceilings are in US dollars and count spend the same way budgets do: CoreValue
reserves the worst-case cost of a request before it runs, then corrects to the
actual cost when it finishes. Spend on your own provider keys counts, as it
does for monthly budgets.

A request that would take spend in a window past a **Block requests** ceiling
is the one that is stopped; a window that reaches exactly the ceiling is still
served. An **Alert only** ceiling is checked against spend from requests that
have finished, so its alert can come shortly after the ceiling is passed.

A ceiling added partway through a window counts the spend already in it. A $50
daily ceiling added in the afternoon of a day that has already spent $80 is
over at once.

## The budget calendar [#the-budget-calendar]

Your organisation's **budget calendar** is a **time zone** and the month your
**fiscal year starts in**. It decides where every daily and yearly window
begins and ends. Until you set it, it is UTC with a fiscal year starting in
January.

* **Time zone:** an IANA name, for example `UTC`, `Asia/Kolkata` or
  `America/Los_Angeles`.
* **Fiscal year starts in:** any month, January to December.

A change applies at once, not at the next boundary: today and this year are
redrawn the moment you save. Because that can put a ceiling over its limit with
no new spend, the console shows, before you save, each enabled ceiling's spend
in its window now and in the window the new calendar would give it. Every
change is recorded in the audit log.

### What the calendar does not move [#what-the-calendar-does-not-move]

The calendar moves the daily and yearly ceilings' windows and nothing else.

* **Monthly budgets** stay on the UTC calendar month.
* **Chargeback** stays on UTC calendar months, and **a closed month never
  changes**: a month you have closed keeps the figures it was closed with,
  whatever you later set here.
* The month-end **forecast** and **margin alerts** stay on UTC calendar months.

## Set a ceiling [#set-a-ceiling]

1. Open **Budgets & Limits → Windows**.
2. Press **Add ceiling**.
3. Choose the **scope** (and the team or key), the **period**, the **ceiling**
   in dollars and the **action**.
4. Press **Save ceiling**.

Each row shows the ceiling's current window and when it resets, in your budget
time zone. You can switch a ceiling off without deleting it, edit its amount or
action, or delete it. The scope and period cannot be changed on an existing
ceiling; add a new one instead. A change reaches the gateway within about two
minutes.

To set the calendar, use the **Budget calendar** section on the same tab, check
the preview, and press **Save calendar**.

Anyone in the organisation can see the ceilings and the calendar; adding,
editing, switching and deleting ceilings, and changing the calendar, need
permission to manage spend controls.

### Plans [#plans]

Daily and yearly ceilings are included in the Growth and Enterprise plans. A
time zone other than UTC, or a fiscal year that does not start in January, is
included in Enterprise. If your plan changes, the ceilings and the calendar you
already set keep working; you cannot add or change them, but you can still
delete a ceiling and return the calendar to UTC and January.

## When a request is blocked [#when-a-request-is-blocked]

With **Block requests** and enforcement on, a refused request gets HTTP `429`
in your provider's own error format, as a monthly budget refusal does. The
message names the ceiling, for example `Team daily budget of $200.00 exceeded`,
and a `Retry-After` header says how many seconds remain until the window
resets. The ceiling clears by itself when the next window starts; nobody needs
to reset it.

When a request is covered by several ceilings, the key's is checked first, then
the team's, then the organisation's, and a daily ceiling before a yearly one.
The first that has no room refuses the request. It is recorded with the rule
`budget_window:<scope>:<period>`, for example `budget_window:team:day`.

## If CoreValue cannot check [#if-corevalue-cannot-check]

Ceilings fail open: if they cannot be read or a window's spend cannot be
counted, the request is served and CoreValue logs why. The one exception is an
organisation whose budget fail mode is set to fail closed; a **Block requests**
ceiling whose spend could not be counted refuses the request with `503`.

## Alerts [#alerts]

While ceilings are in preview, no alert is sent. Once enforcement is on, an
organisation or team ceiling that is reached sends a breach alert to your
budget alert recipients by email. The alert says
"daily" or "yearly" in place of "monthly". Webhook delivery of budget alerts
is not available yet; [webhooks](/features/alerts/webhooks) carry request logs
only. A ceiling that keeps being reached sends at most one alert
an hour, not one per request. A key's ceiling records the breach but sends no
alert, as a key cap does.

## Daily ceilings and velocity limits [#daily-ceilings-and-velocity-limits]

A daily ceiling and a [velocity limit](/features/velocity-limits) both cap spend
over a stretch of time, and they answer different questions.

|                            | Daily ceiling                                                                     | Velocity limit                                                                          |
| -------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| **The window**             | A calendar day, midnight to midnight in your budget time zone                     | A rolling window, 1 minute to 1 day, that always covers the most recent stretch of time |
| **Resets**                 | At midnight                                                                       | Never all at once; old spend slides out of the window                                   |
| **Answers**                | "How much did we spend today?", against a figure you can show as "$X of $Y today" | "Is something spending too fast right now?", to catch a runaway loop in minutes         |
| **Applies to**             | The organisation, a team or a key                                                 | A key                                                                                   |
| **Your own provider keys** | Counted, and can be blocked                                                       | Counted, but only alert                                                                 |

Use both if you need both: a velocity limit to stop a loop quickly, and a daily
ceiling for a daily allowance.

## What is not included yet [#what-is-not-included-yet]

* Daily and yearly ceilings for a member or a customer. Today a ceiling applies
  to the organisation, a team or a key.
* **Downgrade model** as an action.
* Early warnings (for example at 80%) before a daily or yearly ceiling is
  reached.


# Budgets & Limits (https://docs.corevalue.dev/features/budgets/budgets-and-limits)

























**Budgets & Limits** is where you decide how much the gateway is allowed to spend, and what it does when it gets there. Before a request reaches the provider, CoreValue checks it against every budget that covers it.

<img alt="The Budgets & Limits page, Organization tab" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                                                    |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Role**    | Owner or Admin to create or change a budget. Members can view the page; the controls are disabled with the note &#x2A;"Owners and admins …"*.                             |
| **Plan**    | Budgets and **Alert only** work on every plan. **Block requests** and **Downgrade model** need the Growth plan or higher; on Free they show a <kbd>🔒 Growth</kbd> badge. |
| **Where**   | Top bar → **Budgets & Limits** (`/budgets`).                                                                                                                              |

## How budgets work [#how-budgets-work]

A request can be covered by up to five budgets at once — for example, the organisation's, its team's, and its key's own cap. Every one that applies must have room left; the first one that is full decides what happens:

| Budget for       | What it counts                                                      | Set it on                                    |
| ---------------- | ------------------------------------------------------------------- | -------------------------------------------- |
| **Key cap**      | Spend through one [virtual key](/getting-started/key-concepts#keys) | [Keys](#cap-a-virtual-key)                   |
| **Member**       | Spend on every key a member owns                                    | [Members](#set-a-member-budget)              |
| **Customer**     | Requests that carry your `Cova-Customer-Id` header                  | [Customers](#set-a-customer-budget)          |
| **Team**         | Spend on every key assigned to the team                             | [Teams](#set-a-team-budget)                  |
| **Organisation** | All spend, every key                                                | [Organization](#set-the-organisation-budget) |

True of every budget:

* **Every budget on this page is monthly and resets on the 1st of the calendar month (UTC).** A budget set mid-month counts the spend already made that month. Daily and yearly ceilings, in preview, are on [Daily and yearly ceilings](/features/budgets/budget-windows); there are no weekly budgets.
* **Spend is what the provider charged**, priced by CoreValue at the model's list price — whether the request used your own [provider key](/getting-started/key-concepts#keys) or credits.
* **Changes take effect within about a minute.** The gateway caches budget settings for up to 60 seconds.
* **If CoreValue cannot read a budget** (for example during an outage of its own database), the request is served rather than refused. Budgets never take your traffic down.

### What happens at the budget [#what-happens-at-the-budget]

Each budget has an action. You choose it for each budget, or leave it to follow the organisation's.

| Action              | Organisation & team                                                                                                                                                             | Member & customer                                                                                                                                                                                                 |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Alert only**      | Request is served. The breach is recorded on the [Actions tab](#see-what-your-budgets-did) and the [alert recipients](#get-told-before-the-budget-runs-out) are emailed.        | Request is served.                                                                                                                                                                                                |
| **Downgrade model** | The model is rewritten to a cheaper one from your [downgrade mappings](#downgrade-mappings), and the request is served. With no mapping for that model, the request is refused. | The same, and the downgrade is recorded on the [Actions tab](#see-what-your-budgets-did). If the organisation or team budget already downgraded the request and there is no further mapping, it keeps that model. |
| **Block requests**  | Request is refused with HTTP `429` until the month rolls over.                                                                                                                  | Request is refused with HTTP `429` until the month rolls over.                                                                                                                                                    |

<Callout type="warn">
  **Alert is not enforced on member and customer budgets yet.** The request is served and nothing is recorded. On a member or customer budget, choose **Downgrade** to move them to a cheaper model, or **Refuse the request** to stop spend.
</Callout>

## Set the organisation budget [#set-the-organisation-budget]

<Steps>
  <Step>
    ### Open the Organization tab [#open-the-organization-tab]

    Go to **Budgets & Limits** → **Organization**. The top of the tab shows this month so far:

    * **Monthly Budget**, **Current Month Spend** and **Action on Breach**.
    * **Spend Progress** — how much of the budget is used, how much is left, and how far through the month you are.
    * **Month-End Forecast** — the projected spend at the end of the month at the current rate, and whether you are *On track* or projected to exceed it.
  </Step>

  <Step>
    ### Fill in Budget Configuration [#fill-in-budget-configuration]

    Scroll to **Budget Configuration**.

        <img alt="Budget Configuration form" src="__img1" />

    | Field                                                     | What it does                                                                                                                                                                                      | Default            |
    | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
    | **Monthly Budget (USD)**                                  | The organisation's ceiling for the calendar month. `0` or blank turns the organisation budget off.                                                                                                | Blank (off)        |
    | **Default cap per virtual key (USD, per calendar month)** | A cap applied to every virtual key that has no cap of its own, including keys created later. A key's own cap always wins. A key already past it this month is refused until the month rolls over. | Blank (no default) |
    | **Action When Budget Exceeded**                           | **Alert only**, **Block requests** or **Downgrade model** — see [What happens at the budget](#what-happens-at-the-budget).                                                                        | Alert only         |
  </Step>

  <Step>
    ### Save [#save]

    Click **Save Budget Settings*&#x2A;. You will see &#x2A;"Budget settings saved"*. The gateway applies the new budget within about a minute.
  </Step>
</Steps>

### Downgrade mappings [#downgrade-mappings]

When **Downgrade model** is selected, a **Model Downgrade Mappings*&#x2A; section appears under the form. Each mapping says &#x2A;"when the budget is exceeded, rewrite requests for this model to that one"*.

1. Enter the **Source Model** exactly as your application sends it, for example `gpt-4o` or `anthropic/claude-sonnet-4-5-20250929`.
2. Enter the **Target Model** to use instead, for example `gpt-4o-mini`.
3. Click **Add Mapping**.

A request for a model with no mapping is refused, so add a mapping for every model you use.

## Set a team budget [#set-a-team-budget]

Teams group keys, and a team budget caps what all of the team's keys spend together.

<img alt="Teams tab" src="__img2" />

<Steps>
  <Step>
    ### Open Teams [#open-teams]

    Go to **Budgets & Limits** → **Teams**. Each row shows the team's members, its keys, how much of its budget is used this month, and its action (**At the budget**).
  </Step>

  <Step>
    ### Create or edit the team [#create-or-edit-the-team]

    Click **Create team**, or a team's row to edit it.

        <img alt="Create team dialog" src="__img3" />

    | Field                         | What it does                                                                                         | Default                           |
    | ----------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------- |
    | **Name**                      | Shown across the console, and in the team picker in the top bar. Must be unique in the organisation. | —                                 |
    | **Monthly budget (optional)** | The team's ceiling for the calendar month. Must be more than $0; leave it empty for no budget.       | No budget                         |
    | **At the budget**             | **Alert only**, **Downgrade to a cheaper model** or **Block requests**.                              | Inherit the organisation's action |
  </Step>

  <Step>
    ### Save [#save-1]

    Click **Create** (or **Save**). The team budget applies to every key assigned to the team — assign keys to a team on the [Keys](#cap-a-virtual-key) tab.
  </Step>
</Steps>

## Set a member budget [#set-a-member-budget]

A member budget caps the spend on every key that member owns. Keys with no owner are shared service keys: they count towards no member, so cap them on the Keys tab instead.

<img alt="Members tab" src="__img4" />

1. Go to **Budgets & Limits** → **Members**.
2. Click **Set budget** (or **Edit budget**) on the member's row.
3. Fill in the dialog and click **Save**.

<img alt="Member budget dialog" src="__img5" />

| Field                    | What it does                                                                                                                                                          | Default                       |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| **Monthly budget (USD)** | The member's ceiling for the calendar month, including spend made before the budget was set.                                                                          | —                             |
| **At the budget**        | **Alert, and allow**, **Downgrade the model** or **Refuse the request**. Only **Refuse the request** stops spend — see the note [above](#what-happens-at-the-budget). | Use the organisation's action |

To remove a budget, open the dialog and click **Remove budget**.

<Callout type="info">
  A plain member sees only their own row on this tab when the organisation limits request visibility.
</Callout>

## Set a customer budget [#set-a-customer-budget]

If your product serves your own customers, send their ID on each request with the `Cova-Customer-Id` header. The Customers tab then shows spend per customer, and lets you budget each one.

```bash
curl https://gateway.corevalue.dev/v1/chat/completions \
  -H "Authorization: Bearer $COVA_API_KEY" \
  -H "Cova-Customer-Id: acme-corp" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Hello"}]}'
```

<img alt="Customers tab" src="__img6" />

1. Go to **Budgets & Limits** → **Customers**. Customers with a budget or a price are listed; every customer ID seen this month can be picked when you add one.
2. Click **Add limit**, choose the **Customer**, and click **Continue**. (Or **Edit budget** on a row that has one.)
3. Enter the &#x2A;*Monthly budget (USD)** and choose **At the budget**, then click **Save**.

Customer IDs are matched without regard to case: `Acme-Corp` and `acme-corp` are the same customer.

<Callout type="warn">
  A customer budget is enforced on the ID **your application sends**. Anyone holding the key can send a different ID. To bound what a key can spend whatever it sends, [cap the key](#cap-a-virtual-key).
</Callout>

**Price** and **Default price** on this tab record what you charge each customer, so the tab can show revenue and margin next to cost. They are available on the Growth and Enterprise plans.

## Cap a virtual key [#cap-a-virtual-key]

A [virtual key](/getting-started/key-concepts#keys) is a CoreValue key that can carry its own limits: a monthly spending cap, and limits on how many requests it can make. Give a service its own virtual key when you want to cap that service on its own.

<img alt="Keys tab" src="__img7" />

<Steps>
  <Step>
    ### Find the key [#find-the-key]

    Go to **Budgets & Limits** → **Keys**. Under **Virtual keys**, each row shows the key's team, how much of its cap is used this month, its request limits, and when a limit last acted.
  </Step>

  <Step>
    ### Edit its caps [#edit-its-caps]

    Click **Edit caps**.

        <img alt="Edit virtual key dialog" src="__img8" />

    | Field                | What it does                                                                                                                                              |
    | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Owner (optional)** | The member whose [member budget](#set-a-member-budget) this key counts towards. Leave it as *Unassigned — shared service key* for keys used by a service. |
    | **Team**             | The team whose [team budget](#set-a-team-budget) this key counts towards.                                                                                 |
    | **Limits**           | One row per limit. Click **Add limit** to add another.                                                                                                    |

    Each limit row is one of:

    * **Cost** — US dollars per calendar month, for this key alone.
    * **Count** — a number of requests per rolling window, in seconds, minutes, hours or days. For example *1,000 requests per 1 hour*.
  </Step>

  <Step>
    ### Save [#save-2]

    Click **Save Changes**. A key over any of its limits gets HTTP `429` until the limit resets.
  </Step>
</Steps>

A key with no cost cap of its own uses the organisation's **Default cap per virtual key**, if you set one on the [Organization](#set-the-organisation-budget) tab.

**API keys** (listed under the virtual keys) have no cap of their own. They are limited by their team's budget and the organisation budget.

## Get told before the budget runs out [#get-told-before-the-budget-runs-out]

Alerts are configured on **Notifications** → **Budget alerts**, not on this page.

<img alt="Budget alerts on the Notifications page" src="__img9" />

| Field                                 | What it does                                                                                                                |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Recipients**                        | Email addresses to notify. Defaults to the organisation owner — click **Reset to owner** to go back to that.                |
| **Warn before the budget is reached** | Percentages of the budget at which to send an early warning, for example `80%, 95%`. Only the highest mark crossed is sent. |

Click **Save Recipients**. Recipients are also emailed when a budget refuses, at most once an hour per budget.

## See what your budgets did [#see-what-your-budgets-did]

**Budgets & Limits** → **Actions** shows what budgets, key caps, daily and yearly ceilings and velocity limits did to requests. Policy refusals (PII, key scopes, rate limits) are not included.

<img alt="Actions tab, last 7 days" src="__img10" />

Pick a range (last 24 hours, 7 days, 30 days, this budget month, or custom dates up to 92 days) and, optionally, filter by scope, outcome and window. The tab shows:

| View                  | What it shows                                                                                                                                                                                                                |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Requests acted on** | One figure per outcome: **Refused**, **Downgraded**, **Over an alert ceiling**, &#x2A;*Would refuse (preview)** and **Not evaluated**. Each request is counted once, by the rule that decided it.                            |
| **Over time**         | The same requests per hour (ranges of 7 days or less) or per day, by outcome or by scope, in your budget time zone.                                                                                                          |
| **By scope**          | Each outcome per organisation, teams, members, customers, keys and velocity limits. Refusals made because a daily, yearly or velocity check could not be run belong to no single scope and have their own row.               |
| **Breakdown**         | The keys, teams, members, customers or models with the most requests acted on. **Filter** narrows the whole tab to one of them.                                                                                              |
| **Event log**         | One row each time a budget, cap or ceiling acted in enforce: when, which scope and subject, the window, the action, the model, and the spend against the ceiling at that moment, with a link to the request. 25 rows a page. |

What the outcomes mean:

* **Would refuse (preview)**: a ceiling that is still in preview was over its limit. The request was served. It is never counted as **Refused**.
* **Over an alert ceiling**: the request went over a ceiling whose action is alert, and was served. It does not mean an alert was delivered.
* **Not evaluated**: the budget could not be checked and your organisation fails closed, so the request was refused.

Member and customer budgets act only by refusing. Requests before 17 September 2026 carry no verdict and are not counted.

The counts and the event log count different things: the counts are requests, and the log has one row per layer that acted, so a request can appear in it more than once. The log records enforced actions only; filters it cannot apply (model, would refuse, not evaluated, velocity) are shown greyed out above it.

**Breach History** at the bottom of the **Organization** tab now links to this tab. The Teams, Members, Customers and Keys tabs still show a **Last acted** date per row.

**Who can see it.** The counts and the chart need the **Read traffic** permission; the event log needs **Read reports**. A member without **Read reports** sees the counts and a locked event log.

## Rate limits [#rate-limits]

The **Rate limits** tab limits how many requests (or how many cents) the gateway accepts in a rolling window, independent of budgets.

1. Go to **Budgets & Limits** → **Rate limits** → **Rate Limit Rules**, and click **Create Rule**.

2. Fill in the rule:

   | Field                 | What it does                                                                                                                                 | Default               |
   | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
   | **Rule Name**         | A name for the rule.                                                                                                                         | —                     |
   | **Quota**             | How many units are allowed per window.                                                                                                       | —                     |
   | **Unit**              | **Requests** or **Cents**.                                                                                                                   | Requests              |
   | **Time Window (sec)** | The rolling window, in seconds — `3600` for one hour.                                                                                        | —                     |
   | **Apply To**          | **All Requests (Global)**, **Each User ID** (per `Cova-User-Id`), or **Custom Property** (per value of a `Cova-Property-*` header you name). | All Requests (Global) |

3. Click **Create Rule**.

When several rules match a request, property rules apply first, then user rules, then global rules. **Rate Limited Requests** lists the requests that were refused.

## What your application sees [#what-your-application-sees]

A request stopped by a budget or cap gets HTTP `429`, in the same error format as the provider you called, so your existing error handling keeps working. For an OpenAI-format request:

<Tabs items="[&#x22;Budget reached&#x22;, &#x22;Key cap reached&#x22;, &#x22;Rate limit&#x22;]">
  <Tab>
    ```json
    HTTP/1.1 429 Too Many Requests

    {
      "error": {
        "message": "Monthly budget of $125.00 exceeded. Current spend: $125.04. Manage your budget at https://app.corevalue.dev/settings/budget",
        "type": "rate_limit_error",
        "code": null
      }
    }
    ```

    The message names the budget that refused: `Monthly budget` (organisation), `Team "Platform" budget`, `Member budget`, or the customer's budget.
  </Tab>

  <Tab>
    ```json
    HTTP/1.1 429 Too Many Requests

    {
      "error": {
        "message": "Proxy key spend limit exceeded",
        "type": "rate_limit_error"
      }
    }
    ```
  </Tab>

  <Tab>
    ```json
    HTTP/1.1 429 Too Many Requests
    Retry-After: 42

    {
      "error": {
        "message": "Rate limit exceeded",
        "type": "rate_limit_error"
      }
    }
    ```

    Rate-limit refusals carry a `Retry-After` header. Budget and cap refusals do not: they last until the month rolls over or you raise the limit.
  </Tab>
</Tabs>

Anthropic-format requests get `{"type": "error", "error": {"type": "rate_limit_error", ...}}`, and Gemini-format requests get `"status": "RESOURCE_EXHAUSTED"`.

## Troubleshooting [#troubleshooting]

<Accordions>
  <Accordion title="I set a budget but requests still go through past it">
    * Check the action. **Alert only** records the breach and serves the request. Use **Block requests** to stop spend.
    * On a member or customer budget, only **Refuse the request** stops spend.
    * Allow up to a minute after saving for the gateway to pick up the change.
    * Requests already in flight when the budget is reached still complete, so the final spend can land slightly above the budget.
  </Accordion>

  <Accordion title="Block requests and Downgrade model are greyed out">
    They need the Growth plan or higher. Upgrade from the account menu → **Billing**.
  </Accordion>

  <Accordion title="I can't change anything on this page">
    Changing budgets needs the Owner or Admin role. Ask an owner to change your role in **Settings** → **Members**.
  </Accordion>

  <Accordion title="Downgrade is selected but requests are refused">
    The requested model has no downgrade mapping. Add one under **Model Downgrade Mappings** for each model your application uses.
  </Accordion>

  <Accordion title="The Customers tab is empty">
    No request this month carried a `Cova-Customer-Id` header. Add the header to your requests; the customer appears once a request with it is logged.
  </Accordion>

  <Accordion title="A customer budget didn't stop a customer">
    The budget applies to the exact ID your application sends (case-insensitive). If the ID changed, it is a different customer. Cap the key to bound it regardless of the header.
  </Accordion>
</Accordions>

## Related [#related]

* [Quickstart](/getting-started/quickstart) — send your first request
* [Block models](/features/access/block-models) — limit a key to certain models or providers
* [Headers reference](/api-reference/headers) — `Cova-Customer-Id`, `Cova-User-Id` and the rest


# Overview (https://docs.corevalue.dev/features/cost-tracking/dashboard)







The Dashboard is the console's **Overview** page. It answers three questions for the range you pick: how much did we spend, where did it go, and did anything go wrong. Each panel links to the page where you can dig in or act.

<img alt="The Overview page" src="__img0" />

## Before you start [#before-you-start]

| Requirement      | Detail                                                                                                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Where**        | Top bar → **Overview** (`/dashboard`).                                                                                                                                                           |
| **Role**         | Every role can open it (`traffic.read`). Owners and admins also see the links that change settings, such as **Set a budget**.                                                                    |
| **What you see** | Only the traffic your [request visibility](/features/access/request-visibility&#x29; allows. A member on no team, under team visibility, sees &#x2A;"You aren't on a team yet"* instead of data. |

## Controls [#controls]

The controls sit in the bar at the top of the page. The range and the filter are shared with [Requests](/features/cost-tracking/requests) and [Spend](/features/cost-tracking/spend), so a view you set up on one page follows you to the others.

| Control                              | What it does                                                                                                                                                   |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Date range**                       | The window every panel uses, except **Budget health** and **Projected Month-End**, which always cover this month.                                              |
| **Filter**                           | Narrows the panels to the requests that match, for example one model or one customer. See [Filter requests](/features/cost-tracking/requests#filter-requests). |
| **Team scope** (top bar)             | **All teams**, one team, or **No team** (keys not assigned to a team). The team is the one the key belongs to, never a header your application sends.          |
| **Export dashboard** (download icon) | Downloads the dashboard's data for the range as an Excel file.                                                                                                 |
| **Start Live**                       | Refreshes the panels as new requests arrive. Click it again to stop.                                                                                           |

## The panels [#the-panels]

| Panel                         | What it shows                                                                                                                                                                                                                      |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Billing**                   | How your requests are paid for — for example **Your provider keys** with the number of keys, or **Plan usage** with the requests used against your plan's limit.                                                                   |
| **Total Spend**               | Spend in the range. With a monthly budget set it reads &#x2A;"$X of $Y"*, with the share of the monthly budget used and what happens at the limit. Without one, it shows the request count and the average cost per request.       |
| **Projected Month-End**       | What the calendar month will close at if the recent daily rate holds, and the date the budget runs out if it will. On plans without forecasting it reads &#x2A;"On paid plans"*.                                                   |
| **Change vs Previous Window** | Spend against the window of the same length just before it, as a percentage and in dollars.                                                                                                                                        |
| **Costs**                     | Spend over time, stacked by model.                                                                                                                                                                                                 |
| **Spend by …**                | The top eight by **Team**, **Customer**, **Feature**, **End user**, **Run**, **Key**, **Model** or **Provider**, each with its change against the previous window.                                                                 |
| **Traffic**                   | Total requests, error rate and average latency. The chart stacks requests by **Status** or **Model**, with **P95 latency** below it.                                                                                               |
| **Budget health**             | Every organisation, team and customer budget, month to date, nearest its limit first.                                                                                                                                              |
| **All Errors**                | Failed requests, as a count and a share of all requests, broken down by status code.                                                                                                                                               |
| **Findings**                  | Things that need a decision: key caps or team budgets that add up to more than the organisation budget, plan usage nearing its limit, budgets that acted in the range, and suggestions to move a model's traffic to a cheaper one. |
| **Governance**                | How many requests a rule acted on in the range, one line per kind of rule — budgets, key caps, rate limits, key scopes, the PII policy and the rest. Each line links to where the rule is set.                                     |

Under a team scope, **Total Spend** is measured against that team's own budget when it has one.

## Common tasks [#common-tasks]

### Find what caused a spike [#find-what-caused-a-spike]

1. Click the bar for the spike in **Costs** or **Traffic**. [Requests](/features/cost-tracking/requests) opens on exactly that time slice.
2. Sort by **Cost**, or use **Filter** to narrow to one model or customer.

### See who is spending [#see-who-is-spending]

1. In &#x2A;*Spend by …**, pick a tab, for example **Customer**.
2. Click **View all in Spend** for the full list. On the **Team** and **Key** tabs, **Team budgets** and **Key caps** open the limits for each one.

<img alt="The Spend by panel, Customer tab" src="__img1" />

### Check your budgets [#check-your-budgets]

**Budget health** lists every budget with its spend this month. If none is set, owners and admins see **Set a budget**. Budgets are set on [Budgets & Limits](/features/budgets/budgets-and-limits).

<Callout type="info">
  Customer rows in **Budget health** and budget actions in **Findings** need the `reports.read` permission. Without it, the panels say those rows are hidden rather than showing them as empty. See [Roles and permissions](/references/roles).
</Callout>

## Related [#related]

* [Spend](/features/cost-tracking/spend) — every dimension, the whole list
* [Requests](/features/cost-tracking/requests) — the request log behind every figure
* [Tag your traffic](/features/cost-attribution) — fill the Customer, Feature, End user and Run tabs
* [Budgets & Limits](/features/budgets/budgets-and-limits) — set the budgets this page measures against


# Requests (https://docs.corevalue.dev/features/cost-tracking/requests)







**Requests** is the log of every request the gateway handled, newest first. Each row says when it happened, whether it succeeded, whether a rule acted on it, what it cost and who it was for. Click a row to see the detail.

<img alt="The Requests page" src="__img0" />

## Before you start [#before-you-start]

| Requirement      | Detail                                                                                                                                                                               |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Where**        | Top bar → **Requests** (`/requests`).                                                                                                                                                |
| **Role**         | Every role can read the log (`traffic.read`) and add properties, scores and feedback to a request (`traffic.annotate`). Saving a filter needs `org.settings.write` (Owner or Admin). |
| **What you see** | Only the requests your [request visibility](/features/access/request-visibility) allows, narrowed further by the **Team scope** picker in the top bar.                               |

If your organisation also uses the MCP or agent gateway, a switch at the top of the page offers **LLM requests**, **MCP tool calls** and **A2A calls**. This page covers **LLM requests**; see [Tool-call logging and cost](/features/mcp/tool-calls) for the others.

## The columns [#the-columns]

| Column                         | What it shows                                                                                                                                                                                                   |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Created At**                 | When the request reached the gateway.                                                                                                                                                                           |
| **Status**                     | **Success**, **Cached** for a response served from the cache, or the error status code.                                                                                                                         |
| **Verdict**                    | What a rule decided: **Allowed**, **Refused**, **Would refuse**, **Downgraded**, **Alerted** or **Redacted**. Hover for the rule. Empty when no rule judged the request.                                        |
| **Cost**                       | What the request cost. **Unpriced** means nothing priced the model, so it was recorded at $0 and no budget saw it. **declared** beside a figure means it was priced at [your own rate](/features/model-prices). |
| **Model** / **Provider**       | The model and provider that actually served the request, after any fallback or downgrade.                                                                                                                       |
| **User**                       | The `Cova-User-Id` your application sent.                                                                                                                                                                       |
| **Actor**                      | **Human**, **Agent** or **Unknown**. Hover to see whether it was declared with `Cova-Actor` or inferred.                                                                                                        |
| **Run**                        | The run's name (or ID) with the step below it. Click to open the [run](/features/cost-tracking/runs).                                                                                                           |
| **Total Tokens** / **Latency** | Tokens in and out, and the time the request took.                                                                                                                                                               |
| **Customer** / **Feature**     | Appear when your traffic carries `Cova-Customer-Id` or `Cova-Feature-Id`.                                                                                                                                       |

More columns — the token split, prompt cache tokens, and one column per [property](/features/cost-attribution#properties) you send — are off by default. Turn them on with **Manage columns** (the columns icon).

## Filter requests [#filter-requests]

The time range in the page header and the **Team scope** in the top bar apply first. For anything else:

<Steps>
  <Step>
    ### Open the filter [#open-the-filter]

    Click **Filter**, then **Add Condition**.
  </Step>

  <Step>
    ### Choose a field and a value [#choose-a-field-and-a-value]

    Pick a field — for example **Model**, **Provider**, **Status Code**, **Verdict**, **Rule action**, **Cost**, &#x2A;*Latency (ms)**, **User ID**, **Cached**, **Actor**, **Run ID**, or any property you send, such as `Cova-Customer-Id`. Then pick an operator and a value. Add more conditions and set **Match** to **All** or **Any**.
  </Step>

  <Step>
    ### Keep it [#keep-it]

    The filter stays applied as you move between Requests, [Overview](/features/cost-tracking/dashboard) and [Spend](/features/cost-tracking/spend). **Save New** keeps it under **Saved Filters*&#x2A;. Clear it with the &#x2A;*×** on the **Filters** button.
  </Step>
</Steps>

Click a column header such as **Cost** or **Latency** to sort by it. **Copy cURL** in the filter copies the same query as an API call.

## Inspect a request [#inspect-a-request]

Click a row to open the request beside the table.

<img alt="A request opened beside the log" src="__img1" />

* **The header** shows the model, the latency, the cost and the status. Hover the cost to see where the price came from.
* **The line below it** shows the user, the run and the prompt ID, when the request carried them. Click the run to open it.
* **Show Details** (the chevron) adds the **Provider**, **Created At**, **Request ID**, **User**, **Target URL** and the token counts.
* **Verdict** appears when a rule acted, with the **Rule**, what it **Applies to**, the **Action** and the **Rule id**.
* **Properties** lists every tag on the request, including `Cova-Customer-Id`, `Cova-Feature-Id` and `Cova-Team-Id`. **Scores** and feedback can be added here.
* **Copy share link** copies a link that opens this request.

Use the up and down arrow keys to move between requests, and <kbd>Esc</kbd> to close the panel.

## Export [#export]

Click **Export data** (the download icon), choose **CSV** or **JSONL**, and click **Export**. It exports the rows on the current page, or only the rows you have selected (tick a row's checkbox, or hold <kbd>Shift</kbd> or <kbd>⌘</kbd> and click rows).

## Related [#related]

* [Tag your traffic](/features/cost-attribution) — the headers behind the User, Customer, Feature, Actor and Run columns
* [Spend](/features/cost-tracking/spend) — the same requests, totalled by who spent
* [Budgets & Limits](/features/budgets/budgets-and-limits#see-what-your-budgets-did) — what budgets did, in aggregate
* [How cost is calculated](/additional/how-cost-is-calculated)


# Runs (https://docs.corevalue.dev/features/cost-tracking/runs)





A **run** is the set of requests one agent loop, workflow or user task made. Tag each of those requests with the same run ID, and the console shows them together: what the run cost in total, which step cost the most, and the order the requests happened in.

## Tag a run [#tag-a-run]

Send these headers on every request that belongs to the run:

| Header              | What it does                                                                                                   | Example        |
| ------------------- | -------------------------------------------------------------------------------------------------------------- | -------------- |
| `Cova-Session-Id`   | **Required.** Identifies the run. Use a new value for each run.                                                | `run_7f3a`     |
| `Cova-Session-Name` | Names it. Many runs can share a name — it is what the agent or workflow is called.                             | `Code review`  |
| `Cova-Session-Path` | Places the request in the run's tree. `/plan/search` sits under `/plan`. A request without a path sits at `/`. | `/plan/search` |

```bash
curl https://gateway.corevalue.dev/v1/chat/completions \
  -H "Authorization: Bearer $COVA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Cova-Session-Id: run_7f3a" \
  -H "Cova-Session-Name: Code review" \
  -H "Cova-Session-Path: /plan/search" \
  -d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Hello"}]}'
```

Values are trimmed and cut at 256 characters; blank values are ignored. See [Headers](/api-reference/headers#cova-session-id-cova-session-name-cova-session-path).

## Find a run [#find-a-run]

There is no list of runs on its own. You open a run from where you notice it:

* **Spend** → **Run** tab: runs ranked by cost. Click **Open** on a row. See [Spend](/features/cost-tracking/spend).
* **Requests** → the **Run** column: click the run's name. See [Requests](/features/cost-tracking/requests).
* **A request's detail**: click the run under the model name.

When you open a run from one of these, **Back to Spend** (or **Requests**, or **Overview**) at the top returns you to where you were, filters included.

## The run page [#the-run-page]

<img alt="A run, with its timeline and steps" src="__img0" />

| Part               | What it shows                                                                                                                                                               |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Breadcrumb**     | **Runs** / the run's name / a menu of run IDs that share the name, to switch to another run of the same agent.                                                              |
| **Summary**        | **Start Time**, **End Time**, **Cost**, **Avg Latency**, **Requests**, **Actors** (how many turns were human, agent or unknown) and **Tokens**.                             |
| **Timeline**       | One bar per request, by path, against time in seconds. Long bars are slow requests.                                                                                         |
| **Steps table**    | Every request in the run as a tree by path, with **Status**, **Created At**, **Model**, **Cost**, **Latency** and **Feedback**. Expand a path to see the requests under it. |
| **Request detail** | Click a bar or a row to open that request beside the run, as on Requests.                                                                                                   |

The page header also has **Filter**, **Export raw data** (download icon) and **Add Tag**, which labels the run (`traffic.annotate`).

Requests are listed oldest first, 500 to a page by default. The run page reads the last three months of traffic.

## Common tasks [#common-tasks]

### Find the step that made a run expensive [#find-the-step-that-made-a-run-expensive]

1. On **Spend**, click the **Run** tab and set **Mix by** to **Step**.
2. Click the arrow on the run to see the cost of each step.
3. Click **Open**, then click that step in the steps table to see its requests.

### Find the slow request [#find-the-slow-request]

Open the run and look at the **Timeline**. Click the longest bar to open that request, and check its **Latency**, **Model** and **Status**.

## Who can see runs [#who-can-see-runs]

Runs follow the same rules as Requests: every role can open them (`traffic.read`), and you see only the requests your [request visibility](/features/access/request-visibility) allows.

## Related [#related]

* [Tag your traffic](/features/cost-attribution) — every header CoreValue records
* [Spend](/features/cost-tracking/spend) — the **Run** tab
* [Requests](/features/cost-tracking/requests) — the **Run** column and filters


# Spend (https://docs.corevalue.dev/features/cost-tracking/spend)





**Spend** totals what the gateway spent in the selected range and splits it by one dimension at a time — who spent it, what for, and on which model. Teams and keys are shown against their budget or cap.

<img alt="The Spend page, Team tab" src="__img0" />

## Before you start [#before-you-start]

| Requirement      | Detail                                                                                                                                      |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**        | Top bar → **Spend** (`/spend`). The tab is in the URL, for example `/spend?by=customer`.                                                    |
| **Role**         | Every role can open it (`traffic.read`).                                                                                                    |
| **What you see** | Only the spend your [request visibility](/features/access/request-visibility) allows, narrowed by the **Team scope** picker in the top bar. |

<Callout type="info">
  Spend is a rolling window, not a ledger. For the month of record, use [Reports › Chargeback](/features/reports/chargeback).
</Callout>

## The tabs [#the-tabs]

Each tab groups spend by one dimension. Requests that carry no value for it are grouped as **Unattributed**.

| Tab                      | Groups spend by                                | Filled by                                                                                                                                          |
| ------------------------ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Team**                 | The team the key belongs to                    | Assigning keys to [teams](/features/organization/teams). Never the `Cova-Team-Id` header.                                                          |
| **Customer**             | Your customer                                  | `Cova-Customer-Id`. IDs are matched without regard to case, as [customer budgets](/features/budgets/budgets-and-limits#set-a-customer-budget) are. |
| **Feature**              | The feature or surface that made the request   | `Cova-Feature-Id`                                                                                                                                  |
| **End user**             | The person using your product                  | `Cova-User-Id`. Not a member of your organisation.                                                                                                 |
| **Run**                  | One agent run or workflow                      | `Cova-Session-Id`, named by `Cova-Session-Name`                                                                                                    |
| **Key**                  | The API key or virtual key used                | Automatic                                                                                                                                          |
| **Agent**                | The agent the request's key is bound to        | Automatic, once the agent is registered under **Gateway › Agents** and its keys are bound                                                          |
| **Model** / **Provider** | The model and provider that served the request | Automatic                                                                                                                                          |
| **Property**             | The values of one property you choose          | A `Cova-Property-*` header. Pick it from **Property** under the tabs.                                                                              |

How to send these headers is on [Tag your traffic](/features/cost-attribution).

## The table [#the-table]

Above the table, a line gives the total for the range and for the window of the same length before it.

| Column                                         | What it shows                                                                                                                                                                                             |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| *(dimension)*                                  | The team, customer, key or other value. Click the arrow beside it to expand the mix.                                                                                                                      |
| **Share of total**                             | Its share of the range's spend.                                                                                                                                                                           |
| **By model** / **By provider** / **By step**   | How its spend splits, as a bar. Choose the split with **Mix by**. **Step** is offered on the **Run** tab only, and splits a run by `Cova-Session-Path`. Not shown on the **Model** and **Provider** tabs. |
| **Cost**                                       | Spend in the range.                                                                                                                                                                                       |
| **vs previous**                                | Change against the window before. **new** means it had no spend then.                                                                                                                                     |
| **Requests**                                   | Requests in the range.                                                                                                                                                                                    |
| **Budget · this month** / **Cap · this month** | **Team** and **Key** tabs only. The team's budget or the key's cap, and how much of it this calendar month has used — whatever range you picked.                                                          |

The link at the end of a row opens the team's **Budget**, the key's **Cap**, or the run itself (**Open**).

Rows load 100 at a time; click **Load more** for the next page.

## Common tasks [#common-tasks]

### Find your most expensive customers [#find-your-most-expensive-customers]

1. Click the **Customer** tab.
2. Pick a range in the page header.
3. Rows are sorted by cost. Click the arrow on a row to see which models its spend went to.

To cap a customer, set a [customer budget](/features/budgets/budgets-and-limits#set-a-customer-budget).

### Find the step of an agent run that cost the most [#find-the-step-of-an-agent-run-that-cost-the-most]

1. Click the **Run** tab and set **Mix by** to **Step** (it opens on **Step** unless you picked another split earlier).
2. Click the arrow on the run to list its steps, with the cost, share and requests of each.
3. Click **Open** to follow the run request by request on [Runs](/features/cost-tracking/runs).

### See what each agent spends [#see-what-each-agent-spends]

1. Register each agent under **Gateway › Agents** (one with no endpoint is enough) and bind it to the keys it calls with.
2. Click the **Agent** tab.

The agent is read from the key when you look, not stored on each request: binding a key attributes its earlier requests too, and moving a key to another agent moves its history with it. A request made with a proxy key counts toward the agent that key is bound to. MCP tool calls are not priced yet, so they add nothing to an agent's total.

### Narrow to one slice and download it [#narrow-to-one-slice-and-download-it]

1. Click **Filter** and add a condition — for example **Model** equals `gpt-4o`. The filter is the same one [Requests](/features/cost-tracking/requests#filter-requests) uses.
2. Click **Download**. You get the rows shown, every column, as a CSV file.

## Related [#related]

* [Tag your traffic](/features/cost-attribution) — fill the Customer, Feature, End user, Run and Property tabs
* [Dashboard](/features/cost-tracking/dashboard) — the top eight of each tab, beside the rest of the picture
* [Chargeback](/features/reports/chargeback) — the closed month, per team
* [Unit economics](/features/reports/unit-economics) — cost per customer against what you charge


# Block models or providers (https://docs.corevalue.dev/features/access/block-models)







You stop a model or provider from being used by limiting the keys that call it. Each CoreValue key — API key or virtual key — can carry a list of the **Models** and **Providers** it may use. A request for anything else is refused at the gateway, before it reaches the provider and before it costs anything.

<Callout type="info">
  Limits are set per key, as an allow list. There is no organisation-wide block list in the console. To keep a model out of use everywhere, give every key a **Models** list without it, and issue new keys with one.
</Callout>

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                |
| ----------- | ----------------------------------------------------------------------------------------------------- |
| **Plan**    | Every plan.                                                                                           |
| **Role**    | Owner or Admin (the `keys.write` permission). Members see the **Scopes** column but cannot change it. |
| **Where**   | Top bar → **Governance** → **Keys** (`/governance?tab=keys`).                                         |

## Limit a key [#limit-a-key]

<img alt="Keys on the Governance page, with the Scopes column" src="__img0" />

<Steps>
  <Step>
    ### Open the key's scopes [#open-the-keys-scopes]

    Go to **Governance** → **Keys** and click **Scopes** on the key's row.
  </Step>

  <Step>
    ### Add models and providers [#add-models-and-providers]

        <img alt="The Scopes dialog" src="__img1" />

    | Field         | What to enter                                                                                                                                                                                                                                        |
    | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Models**    | Model names from CoreValue's registry, for example `gpt-4o-mini` or `claude-sonnet-4-5-20250929`. One entry covers every spelling of the model: its Bedrock id, a prefixed id such as `anthropic/claude-sonnet-4-5-20250929`, and URL-encoded forms. |
    | **Providers** | The providers a request may be sent to, for example `openai` or `bedrock`.                                                                                                                                                                           |

    An empty field shows **Any** and allows everything. Each field takes up to 100 entries. The same dialog also limits the key to certain **Customers**; see [Key scopes](/features/access/key-scopes).
  </Step>

  <Step>
    ### Save [#save]

    Click **Save**. **Clear all scopes** removes every limit from the key.
  </Step>
</Steps>

## What the caller receives [#what-the-caller-receives]

A request outside the key's lists gets HTTP `403` in the error format of the API it called. For an OpenAI-format request:

<Tabs items="[&#x22;Model not allowed&#x22;, &#x22;Provider not allowed&#x22;]">
  <Tab>
    ```json
    HTTP/1.1 403 Forbidden

    {
      "error": {
        "message": "This key may not use model \"gpt-4o\".",
        "type": "permission_error",
        "code": "model_not_allowed"
      }
    }
    ```
  </Tab>

  <Tab>
    ```json
    HTTP/1.1 403 Forbidden

    {
      "error": {
        "message": "This key may not use provider \"groq\".",
        "type": "permission_error",
        "code": "provider_not_allowed"
      }
    }
    ```
  </Tab>
</Tabs>

Anthropic-format requests get `permission_error`, and Gemini-format requests `PERMISSION_DENIED`. A key with a **Models** list that sends a request naming no model is refused too.

The refusal is recorded on **Requests** at $0, so you can see who tried. It is checked before rate limits and budgets, so a refused request uses none of them.

## Which model and provider are checked [#which-model-and-provider-are-checked]

* **Models** is checked against the model the request asks for, before anything changes it.
* **Providers** is checked against the provider the request is sent to: the prefix on `/gateway/v1`, the provider whose endpoint was called, or the provider [routing by model name](/features/routing/model-routing) chose.

## MCP tools and A2A agents [#mcp-tools-and-a2a-agents]

Model and provider limits apply to LLM requests. To decide who may call an MCP tool or an A2A agent, use [Access rules](/features/access/mcp-tool-policies); to hide a tool from everyone, limit the server's **Tools** on [Add MCP servers](/features/mcp/servers).

## Related [#related]

* [Key scopes](/features/access/key-scopes) — models, providers and customers on one key
* [Budgets & Limits](/features/budgets/budgets-and-limits) — stop spend, rather than a model
* [Supported providers and models](/additional/supported-models) — the names to enter


# Guardrails and PII masking (https://docs.corevalue.dev/features/access/guardrails)



Guardrails scan traffic for the personal-data patterns your organisation selects in **Governance › Policies**, under PII Scanning, and act on what they find before it reaches the provider or the tool.

## What is scanned [#what-is-scanned]

* **LLM requests:** the request body, before it is sent to the provider. LLM responses are not scanned by guardrails.
* **MCP tool calls:** the tool's arguments, before the tool sees them, and the tool's result, before the caller sees it.

## Actions [#actions]

For each pattern, choose what happens on a match:

* **Alert, and allow** (the default): the request is sent as it is, and the detection is recorded.
* **Redact, then send:** the matched values are replaced before the request is sent, or before a tool result is returned. The redacted version is what is stored.
* **Refuse the request:** the request is refused with HTTP 400 `pii_policy_violation` (an MCP tool call gets JSON-RPC error `-32602`), naming the kind of data matched, never the value. For a tool result, the tool has already run; its output is withheld and the caller gets an error in its place.

When several patterns match, the strongest action applies: refuse, then redact, then alert.

## When a request cannot be scanned [#when-a-request-cannot-be-scanned]

Choose a fail mode for when the policy cannot be read and no recent copy of it is available, or a body is larger than the 1 MB that can be scanned:

* **Fail open** (the default): the request is sent unscanned, and recorded as not scanned.
* **Fail closed:** the request is refused. When the policy cannot be read, an LLM request or a call to a server's own MCP endpoint gets HTTP 503, which clients that retry will try again; a call on the unified MCP endpoint gets JSON-RPC error `-32603`. A request body too large to scan gets HTTP 413, and a tool result too large to scan is withheld.

## Reviewing detections [#reviewing-detections]

Each detection is recorded on the request or tool call with the pattern that matched and the action taken, without the matched value. Changes to the policy reach the gateway within a minute.

Masking of stored request and response bodies is a separate setting, applied when bodies are stored; it does not change what the provider or the tool receives.


# Key scopes (https://docs.corevalue.dev/features/access/key-scopes)



A scope limits what a CoreValue key can be used for. Scopes are checked at the
gateway before rate limits and budgets, so a caller cannot widen a key's access
by changing its request headers.

## Scope a key [#scope-a-key]

1. Go to **Governance → Keys** and click **Scopes** on the key. This works for
   API keys and virtual keys alike.
2. Add entries to any of **Customers**, **Models** and **Providers**. An empty
   field means *Any*. Customers are suggested from this month's traffic.
3. Click **Save**. **Clear all scopes** removes every limit.

Each field takes up to 100 entries. Customer IDs are matched without regard to
case.

## What a scoped key does [#what-a-scoped-key-does]

| Scope                      | Request                    | Result                                                                    |
| -------------------------- | -------------------------- | ------------------------------------------------------------------------- |
| **Customers**: exactly one | No `Cova-Customer-Id`      | Counted for that customer, including against its budget.                  |
| **Customers**: several     | No `Cova-Customer-Id`      | Refused, `400 customer_header_required`: the key must say which customer. |
| **Customers**: any number  | A customer not in the list | Refused, `403 customer_not_allowed`.                                      |
| **Models**                 | A model not in the list    | Refused, `403 model_not_allowed`.                                         |
| **Providers**              | A provider not in the list | Refused, `403 provider_not_allowed`.                                      |

Refusals use the error format of the API you called (`permission_error` or
`invalid_request_error`; `PERMISSION_DENIED` or `INVALID_ARGUMENT` for Google),
and are recorded in the request log at $0.

## Good practice [#good-practice]

Give each application, environment and, if you resell AI, each customer its own
key. A key scoped to one customer binds every request to that customer without
any header; see [Per-customer budgets](/guides/per-customer-budgets).

Changing scopes needs the `keys.write` permission.


# Access rules for tools and agents (https://docs.corevalue.dev/features/access/mcp-tool-policies)



Access rules decide which callers may use which MCP tools and A2A agents. Owners and admins manage them in **Governance › Policies › Access rules**. Every caller is identified by its CoreValue key, never by a header it sends.

Access rules are available on plans that include them. See [Billing, plans and credits](/features/organization/billing).

## A rule [#a-rule]

Each rule has three parts:

* **Effect:** deny or allow.
* **Who:** a team, a member, an API key, an A2A agent (the keys it calls with), or anyone.
* **What:** one MCP tool, every tool on an MCP server, or an A2A agent. For one tool, give the tool's own name, as the server lists it.

For an A2A agent, its own **Who may call it** setting in [Gateway › Agents](/features/agents/register) is checked first; access rules apply to the callers it lets through.

## Audit, then enforce [#audit-then-enforce]

A new rule starts in **audit**. A call it would deny is still served, and is recorded as *would-deny*, naming the rule. The rule list shows, for each rule, how many calls it refused and how many it would have refused over the last seven days. When the record shows the rule does what you meant, switch it to **enforce**. Only an enforced deny refuses a call. You can switch a rule back to audit at any time.

The same holds for allow rules: an allow rule in audit grants nothing until it is enforced.

## When rules disagree [#when-rules-disagree]

A deny overrides an allow. Several rules can match one call; if any enforced rule denies it, the call is refused, whatever other rules allow it. The order the rules were written in never changes the outcome.

## Default deny [#default-deny]

With no rules, every caller in the organisation reaches every server, tool and agent it can see. &#x2A;*Turn on default deny (audit)** changes what happens to a call no rule matches: it is served, and recorded as would-deny unless an allow rule covers it. **Enforce default deny** then refuses those calls.

Default deny covers every MCP tool call and every A2A call, platform tools included. Before enforcing it, add allow rules for the callers who need access, enforce them, and check that the would-deny count has stopped growing.

## A2A loop limit [#a2a-loop-limit]

The loop limit caps how many times one agent may call another within one task. Only message sends count, not polls. It also starts in audit, recording calls past the limit as would-deny, and refuses them only when enforced. An agent can set its own limit in **Gateway › Agents**, which applies instead of the organisation's. The limit counts only calls between agents that pass the task trace on.

## What a refused caller sees [#what-a-refused-caller-sees]

* **MCP tool call.** An enforced deny is answered as if the tool did not exist: JSON-RPC error `-32602 Unknown tool`, so a client cannot tell a denied tool from an absent one. On a server's own endpoint, a batch that contains one denied tool call is refused whole.
* **Tool lists.** On the unified endpoint, `/mcp`, a tool an enforced rule denies is left out of `tools/list`. On a server's own endpoint, `/mcp/<server-id>`, the server's `tools/list` is passed through as it is, so a denied tool still appears there; calling it is refused.
* **A2A call.** HTTP 403 with a JSON-RPC error whose `data.reason` is `access_rule`. The agent card is refused to that caller too. See [Call an agent](/features/agents/call).

Every decision, refused or would-deny, is recorded on the call's row with the rule that made it, in tool-call activity and in agent traffic.

## When changes apply [#when-changes-apply]

Changes to rules, the default and the loop limit reach the gateway within a minute.


# Who sees whose requests (https://docs.corevalue.dev/features/access/request-visibility)





**Request visibility** decides whose traffic each person in your organisation can see in the console: requests, their details, and the spend and usage views built from them. Every organisation starts with everyone seeing everything. You can narrow it to teams or to individuals.

<img alt="The Request visibility setting on Governance › Access" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                               |
| ----------- | ---------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Governance** → **Access**, the **Request visibility** section between Members and Teams. |
| **Role**    | Owner or Admin to narrow it (`org.members.write`). &#x2A;*Only the owner can widen it.**             |
| **Plan**    | Every plan.                                                                                          |

## The three settings [#the-three-settings]

| Setting                          | Who sees what                                                                                          |
| -------------------------------- | ------------------------------------------------------------------------------------------------------ |
| **Everyone in the organization** | Every member sees all of the organisation's traffic. The default.                                      |
| **Scoped to teams**              | Members and admins see traffic from the keys of the [teams](/features/organization/teams) they are on. |
| **Scoped to individuals**        | Members and admins see traffic from the keys they own.                                                 |

The owner always sees everything. Admins are scoped like members: an admin on no team sees no traffic under **Scoped to teams**.

A request belongs to the team its key was on when the request was made. Moving a key to another team changes who sees its future requests; past requests stay with the old team. The `Cova-Team-Id` header has no effect on visibility.

<Callout type="warn">
  **Narrowing applies to past requests too.** As soon as you save, people lose sight of traffic outside their scope, including history they could see before.
</Callout>

## Narrow visibility [#narrow-visibility]

1. Go to **Governance** → **Access**.
2. Under **Request visibility**, choose **Scoped to teams** or **Scoped to individuals**.
3. Read any warning that appears (below), then click **Save**.

### Keys with no team [#keys-with-no-team]

A narrowed setting needs every key to have a team, so that no traffic is left belonging to nobody. Saving is refused while any key has no team, and the page lists those keys. Fix each one on [Governance › Keys](/features/organization/api-keys): put it on a team, or mark it **Org-level key** if it is a shared service key that only owners should see.

### Keys and webhooks that keep wide access [#keys-and-webhooks-that-keep-wide-access]

Two things keep organisation-wide access whatever this setting says, and the page lists them before you save:

* **Org-level keys** — they can read every team's traffic through the API.
* **Webhooks that send request data** out of CoreValue.

The owner confirms them by ticking **I've reviewed these, and they should keep organization-wide access**. An admin cannot narrow visibility while any of these exist: put the keys on teams and turn off request data on the webhooks, or ask the owner.

## While visibility is narrowed [#while-visibility-is-narrowed]

* New keys must be put on a team or marked org-level.
* Only the owner can add people to a team, or widen visibility again.
* The top bar's team picker offers only your own teams, unless you are the owner.
* A few organisation-wide views that cannot be split by key are shown to the owner only.
* A member with nothing in scope sees a note on each page — *You aren't on a team yet* or *You don't own any keys yet* — explaining why there is nothing to show.

## Keys reading through the API [#keys-reading-through-the-api]

A CoreValue key that reads data from the CoreValue API follows the same setting. An org-level key reads everything. Under **Scoped to teams**, a key on a team reads its team's traffic. Otherwise a key reads only its own traffic — never its owner's.

## Related [#related]

* [Teams](/features/organization/teams)
* [Members](/features/organization/members)
* [API keys and virtual keys](/features/organization/api-keys)
* [Webhooks](/features/alerts/webhooks)


# API keys and virtual keys (https://docs.corevalue.dev/features/organization/api-keys)





Your applications send a **CoreValue key** to the gateway in place of a provider key. There are two kinds, and both are used the same way: put the key where your SDK expects the provider key, and point the SDK at the gateway. See [Authentication](/api-reference/authentication).

| Kind            | Starts with      | Use it when                                                                                                  |
| --------------- | ---------------- | ------------------------------------------------------------------------------------------------------------ |
| **API key**     | `sk-cova-`       | A person or application needs a key. Its spend counts towards its team's budget and the organisation budget. |
| **Virtual key** | `sk-cova-proxy-` | A service should also have **its own limits**: a monthly cost cap and request-count limits.                  |

<img alt="The Keys tab" src="__img0" />

## Before you start [#before-you-start]

| Requirement       | Detail                                                                                                                    |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Where**         | Top bar → **Governance** → **Keys** (`/governance?tab=keys`).                                                             |
| **Role**          | Owner or Admin to create, edit, scope or delete keys (`keys.write`). Members see the table with the buttons disabled.     |
| **Provider keys** | A virtual key asks you to pick a provider key, so add one on [Provider keys](/features/organization/provider-keys) first. |

## The Keys table [#the-keys-table]

Both kinds of key are listed together, with their **Type**, **Owner**, **Team**, **Provider key**, **Cap · this month**, **Scopes** and **Created** date. An API key's cap column reads *Team and org budgets*: it has no cap of its own.

## Create an API key [#create-an-api-key]

1. Click **Generate API key**.
2. Fill in the **Create Key** dialog and click **Create Key**.
3. Copy the key from **Your CoreValue Key**. It is shown **once**; you cannot see it again.

| Field             | What it does                                                                                                                                             |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Key Name**      | A name you will recognise in the table and the request log. Required.                                                                                    |
| **Permissions**   | **Write** is always on. **Read** (on by default) also allows `GET` calls to the CoreValue API. Neither affects calling models through the gateway.       |
| **Owner**         | The member the key belongs to. Defaults to you. Their [member budget](/features/budgets/budgets-and-limits#set-a-member-budget) counts this key's spend. |
| **Team**          | The team whose budget and chargeback the key counts towards. Required when request visibility is scoped to teams or individuals.                         |
| **Org-level key** | Instead of a team: a shared service key whose traffic only owners can see.                                                                               |

## Create a virtual key [#create-a-virtual-key]

1. Click **Create virtual key**.
2. Fill in the dialog and click **Create virtual key**.
3. Copy the key from **Your virtual key**. It is shown **once**.

| Field                 | What it does                                                                                                                                                                                                                           |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Virtual key name**  | Required.                                                                                                                                                                                                                              |
| **Owner (optional)**  | Leave as *Unassigned — shared service key* unless the key belongs to one person.                                                                                                                                                       |
| **Team**              | As for an API key.                                                                                                                                                                                                                     |
| **Org-level key**     | As for an API key.                                                                                                                                                                                                                     |
| **Provider Key Name** | One of your stored provider keys. Recorded on the key and shown in the table. The gateway still picks the provider key from the model you call; to limit which providers a key may use, set its [scopes](/features/access/key-scopes). |
| **Limits**            | Click **Add limit** for each one. **Cost** is US dollars per calendar month for this key. **Count** is a number of requests per rolling window. Either returns HTTP `429` once exceeded.                                               |

You can also change a virtual key's caps on [Budgets & Limits › Keys](/features/budgets/budgets-and-limits#cap-a-virtual-key).

## Edit, scope or delete a key [#edit-scope-or-delete-a-key]

Each row has three buttons:

* **Edit** — rename an API key, or change either kind's **Owner** and **Team**. Changing the owner or team does not rotate the key or interrupt anything using it. Moving a key changes where its future spend and traffic go; past requests stay with the team they were made under.
* **Scopes** — limit the key to certain customers, models or providers. See [Key scopes](/features/access/key-scopes).
* **Delete** — the key stops working immediately. Its history stays in the request log. This cannot be undone.

Give each application and environment its own key, so you can cap, scope and revoke one without touching the others. When someone leaves, reassign or delete the keys they own.

## Related [#related]

* [Key scopes](/features/access/key-scopes)
* [Teams](/features/organization/teams)
* [Budgets & Limits](/features/budgets/budgets-and-limits)
* [Quickstart](/getting-started/quickstart)


# Audit log (https://docs.corevalue.dev/features/organization/audit-log)





The **audit log** records the changes made in your organisation: who made each one, when, what it changed, and whether it was allowed. Use it to answer "who changed this budget?" or "when was this key deleted?", and as the shared record for a security or operational review.

<img alt="The Audit tab" src="__img0" />

## Before you start [#before-you-start]

| Requirement    | Detail                                                                                                                                                                                                      |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**      | Top bar → **Governance** → **Audit** (`/governance?tab=audit`).                                                                                                                                             |
| **Permission** | `audit.read`. Owners and admins hold it. A custom role holds it only if you grant it; `reports.read` does not include it. Without it, the tab shows a lock. See [Roles and permissions](/references/roles). |
| **Plan**       | Every plan.                                                                                                                                                                                                 |

## What is recorded [#what-is-recorded]

There are two kinds of row.

| Kind               | Example                                                | What it tells you                                                                                                              |
| ------------------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| **Named event**    | `member.role_changed`, `budget.updated`, `key.deleted` | The change itself, with the fields that changed, before and after.                                                             |
| **Route decision** | `route.…`                                              | One attempt to use a protected part of the console or API: who tried, the route, and whether it was allowed, denied or failed. |

Named events cover members and roles, keys and provider keys, budgets, caps, alerts and downgrade mappings, rate and velocity limits, prices, gateway configuration, organisation settings, and MCP connections. The **Action** box suggests every event name as you type.

Keys and secrets are never recorded; anything that looks like one is shown as `[redacted]`. Reading the audit log is not itself recorded.

## Read the log [#read-the-log]

Each row shows **When**, **Actor**, **Action**, **Route**, **Outcome**, **Target** and **Details**. Newest rows come first; use **Previous** and **Next** to page through.

Choose a view at the top:

| View                | Shows                                                                           |
| ------------------- | ------------------------------------------------------------------------------- |
| **Events**          | Named events, plus every route decision that was refused or failed. Start here. |
| **Route decisions** | Every route decision, allowed or not.                                           |
| **All**             | Both. A successful change appears twice: once as its route, once as its event.  |

## Find something [#find-something]

1. Narrow the **Outcome**: **All**, **Allowed**, **Denied**, **Failed** or **Attempted**.
2. Fill in any of **Actor** (an email, user ID or key ID), **Action** (for example `member.role_changed`), **From** and **To**.
3. Click **Apply**. **Clear** resets every filter.

## Download as CSV [#download-as-csv]

Click **Download CSV**. It downloads every row that matches the current filters, newest first, up to 10,000 rows. Narrow the dates to export a longer history in parts.

## Related [#related]

* [Roles and custom roles](/features/organization/roles) — grant `audit.read` to a reviewer without making them an admin
* [Members](/features/organization/members)
* [API keys and virtual keys](/features/organization/api-keys)


# Billing, plans and credits (https://docs.corevalue.dev/features/organization/billing)





**Billing** shows which plan your organisation is on and what it has used this billing period. From there you can upgrade, manage the subscription, or contact us about Enterprise. **Credits** shows your credit balance and its history.

Model usage through your own [provider keys](/features/organization/provider-keys) is billed by the provider, directly to you. Your CoreValue plan is separate.

<img alt="The Billing page" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                                                                                                                                                       |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | The organisation menu at the right of the top bar → **Settings** → **Billing** (`/settings/billing`) or **Credits** (`/settings/credits`).                                                                                                                                   |
| **Role**    | Anyone can open Billing. Owner or Admin to upgrade (`billing.write`). Owner only to manage or cancel the subscription and remove a payment method (`ownership`). Invoices and spend by model on Credits need `reports.read`. See [Roles and permissions](/references/roles). |

## Plans [#plans]

There are three plans: **Free**, **Growth** and **Enterprise**. Some features in these docs name the plan they need — for example [custom roles](/features/organization/roles) are Enterprise, and adding members needs a paid plan. For current prices and what each plan includes, see the pricing page on corevalue.dev.

## What the Billing page shows [#what-the-billing-page-shows]

The page depends on your plan. Your plan is marked **Current plan**.

| Plan           | What you see                                                                   | What you can do                                                                                                   |
| -------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| **Free**       | The current billing period and **Requests used** against the plan's allowance. | **Upgrade to Growth**, or **Contact Sales** for Enterprise.                                                       |
| **Growth**     | The current billing period and a chart of this period's usage.                 | **Manage Subscription**, **View Upcoming Invoice**, and **Reactivate Subscription** if a cancellation is pending. |
| **Enterprise** | A summary of your Enterprise plan.                                             | **Contact Enterprise Support** about limits, features or changes.                                                 |

## Upgrade to Growth [#upgrade-to-growth]

1. On **Billing**, click **Upgrade to Growth**.
2. In the **Upgrade to Growth** dialog, click **Upgrade to Growth**. A secure checkout page opens in a new tab.
3. Complete the payment there. Your organisation moves to Growth when the checkout completes.

## Manage or cancel the subscription [#manage-or-cancel-the-subscription]

Only the owner can do this.

1. On **Billing**, click **Manage Subscription**. The subscription page opens in a new tab.
2. Manage the payment method and the subscription there, including cancelling it.

A cancelled subscription stays active until the end of the billing period. Until then, **Reactivate Subscription** on the Billing page undoes the cancellation.

**View Upcoming Invoice** shows what the next invoice will contain so far: its lines, subtotal, tax and total.

## Credits [#credits]

**Settings** → **Credits** has these tabs:

| Tab           | Shows                                                                                                                                                                                                  |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Overview**  | **Current Balance** and **Recent Transactions**.                                                                                                                                                       |
| **Usage**     | Spend paid for with credits, by model and provider, for a date range. Spend through your own provider keys is not included; see [Spend](/features/cost-tracking/spend) for that. Needs `reports.read`. |
| **Invoices**  | Appears when your organisation has invoices. Needs `reports.read`.                                                                                                                                     |
| **Discounts** | Appears when discounts apply to your organisation.                                                                                                                                                     |

<Callout type="info">
  **Buying credits is not available yet.** The console does not offer credit purchases today, and only CoreValue can add credits to an organisation. Any balance and its history still show on the Credits page. Contact us if you would like to discuss using credits.
</Callout>

## Related [#related]

* [Provider keys](/features/organization/provider-keys)
* [Roles and permissions](/references/roles)
* [Members](/features/organization/members)


# Invites (Preview) (https://docs.corevalue.dev/features/organization/invites)





<Callout type="info">
  **Preview — not yet available.** Email invitations are built but switched off on CoreValue's service for now. Until they are on, you can add only people who already have a confirmed CoreValue account. Contact us if you need a teammate added.
</Callout>

An invitation lets you add someone to your organisation by email, before they have a CoreValue account. This page describes how it works, so you know what to expect when it is turned on.

<img alt="The Invite a member dialog" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Governance** → **Access** → **Invite member**, or the organisation menu at the right of the top bar → **Invite members**. |
| **Role**    | Owner or Admin (`org.members.write`).                                                                                                 |
| **Plan**    | Any plan except Free. On Free, the button offers an upgrade instead.                                                                  |

## Invite someone [#invite-someone]

1. Click **Invite member**.
2. Enter their **Email**.
3. Choose a **Role**: **Member**, **Admin**, or one of your [custom roles](/features/organization/roles) on Enterprise. Owner is never offered; ownership moves only by [transfer](/features/organization/members#transfer-ownership).
4. Click **Send invitation**.

What happens next depends on the address:

| The address belongs to                                   | What happens                                                                                                      |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Someone with a confirmed CoreValue account               | They are added to the organisation straight away. No email is sent. This works today.                             |
| Nobody yet, or an account that never confirmed its email | They are added to the list and sent one email. The dialog shows **Invitation sent**. This is the part in preview. |

## What the invitee receives [#what-the-invitee-receives]

One email with a link. The link confirms their email address and lets them choose a password; after that they can sign in to your organisation with the role you chose.

The link works for **seven days**. If it expires or the email is lost, invite the same address again to send a new link.

## Cancel an invitation [#cancel-an-invitation]

The invited person appears in the [members list](/features/organization/members) as soon as you invite them. To withdraw the invitation, click **Remove** on their row.

## If something goes wrong [#if-something-goes-wrong]

* **"Invitations are switched off on this deployment"** — email invitations are not on yet. Contact us to have the person added.
* **The address is already a member** — nothing changes; the dialog closes.

Every invitation is recorded in the [audit log](/features/organization/audit-log) as `member.invited`.


# Members (https://docs.corevalue.dev/features/organization/members)





A **member** is a person who can sign in to your organisation's console. Each member has one role, which decides what they can do. The members list is where you add people, change their role and remove them.

<img alt="The Members list on Governance › Access" src="__img0" />

## Before you start [#before-you-start]

| Requirement    | Detail                                                                                                                                                 |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Where**      | Top bar → **Governance** → **Access** (`/governance?tab=access`). **Members** is the first section; [Teams](/features/organization/teams) is below it. |
| **Role**       | Owner or Admin to add, change or remove members (the `org.members.write` permission). Everyone else sees the list with the controls disabled.          |
| **Owner only** | Transferring ownership.                                                                                                                                |

## What the list shows [#what-the-list-shows]

Each row shows the member's email, the date they were added, and their role. Your own row is marked **Current User**. The owner's row carries an **Owner** badge, and its role cannot be changed.

| Role            | What it can do                                                                                                                                               |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Owner**       | Everything, including the owner-only actions: transfer the organisation, delete it, cancel the subscription and remove a payment method. There is one owner. |
| **Admin**       | Everything except the owner-only actions.                                                                                                                    |
| **Member**      | Reads requests and spend, and can annotate them. Cannot change settings or create keys.                                                                      |
| **Custom role** | Enterprise only. A member who also holds the permissions you chose. See [Roles and custom roles](/features/organization/roles).                              |

The full list of what each role holds is on [Roles and permissions](/references/roles).

## Add a member [#add-a-member]

Click **Invite member**. Enter their **Email**, choose a **Role**, and click **Send invitation**.

* If the address belongs to someone who already has a confirmed CoreValue account, they are added straight away and appear in the list. No email is sent.
* Otherwise CoreValue would email them an invitation. Email invitations are in preview and not yet available — see [Invites](/features/organization/invites).

You can open the same dialog from the organisation menu at the right of the top bar → **Invite members**. On the Free plan, adding members is not included, and the button offers an upgrade instead.

## Change a member's role [#change-a-members-role]

1. Find the member in the list.
2. Pick the new role from the selector on their row: **Admin**, **Member**, or one of your custom roles.
3. The change saves straight away.

You cannot change your own role, and an admin cannot make anyone an owner.

## Remove a member [#remove-a-member]

Click **Remove** on their row, then **Remove** in the confirmation. They lose access to the organisation at once. Keys they own are not deleted and keep working, so if the person is leaving, reassign or delete their keys on [Governance › Keys](/features/organization/api-keys).

To leave an organisation yourself, click **Leave** on your own row. The owner cannot leave or be removed — transfer ownership first.

## Transfer ownership [#transfer-ownership]

Only the owner can do this.

1. On the new owner's row, pick **Owner** from the role selector.
2. Click **Transfer** in the **Transfer Ownership** dialog.

The new owner gets the **Owner** badge, and you become an admin.

## Related [#related]

* [Teams](/features/organization/teams) — group members and keys
* [Roles and custom roles](/features/organization/roles)
* [Who sees whose requests](/features/access/request-visibility)
* [Audit log](/features/organization/audit-log) — role changes and removals are recorded there


# Provider keys (https://docs.corevalue.dev/features/organization/provider-keys)





A **provider key** is your own key from OpenAI, Anthropic, Google or another provider. You add it to CoreValue once. When a request reaches the gateway, CoreValue uses the provider key for that model's provider to make the call, and the provider bills you directly. This is often called **BYOK** — bring your own key.

Your provider keys are stored by CoreValue and never sent to your applications. Applications only ever hold a [CoreValue key](/features/organization/api-keys).

<img alt="The Provider keys tab" src="__img0" />

## Before you start [#before-you-start]

| Requirement  | Detail                                                                                                  |
| ------------ | ------------------------------------------------------------------------------------------------------- |
| **Where**    | Top bar → **Gateway** → **Provider keys** (`/gateway?tab=provider-keys`).                               |
| **Role**     | Owner or Admin (`provider_keys.write`). Anyone else sees **Admin Access Required** instead of the list. |
| **You need** | A key from the provider's own console.                                                                  |

## Add a provider key [#add-a-provider-key]

1. Find the provider in the list. Use &#x2A;*Search providers...** or the **Sort** menu to narrow it down. Each row shows *No keys configured* or how many keys are configured.
2. Click the provider's row to open it.
3. Paste the key into the key field. For Vertex AI, paste the **Service Account JSON** instead.
4. Leave &#x2A;*Enable for AI Gateway (BYOK)** ticked, so the gateway uses this key.
5. Fill in any extra fields the provider needs (below), then click **Add**.

The key is used from the next request.

### Extra fields for some providers [#extra-fields-for-some-providers]

| Provider         | Fields                                                                                                 |
| ---------------- | ------------------------------------------------------------------------------------------------------ |
| **OpenAI**       | **Endpoint Region**: the default endpoint, or *US Data Residency*.                                     |
| **Azure OpenAI** | **Base URI**, **API Version**, **Deployment Name**, and the model name CoreValue should record for it. |
| **AWS Bedrock**  | **Region** and **Cross Region**.                                                                       |
| **Vertex AI**    | **Region** and **Cross Region**, with the service account JSON.                                        |

## Replace, turn off or delete a key [#replace-turn-off-or-delete-a-key]

Open the provider's row. On a stored key:

* **Edit**, then enter the new key and click **Save**. Confirm with **Update Key**. The old value is replaced.
* Untick &#x2A;*Enable for AI Gateway (BYOK)** while editing, and save, to keep the key stored but stop the gateway using it.
* **Delete**, then **Delete** in the **Delete Provider Key** dialog. This cannot be undone.

Changes apply from the next request. If no enabled key is left for a provider, requests to that provider's models fail until you add one.

The eye icon shows the stored value to people who can manage provider keys, and the copy icon copies it.

## More than one key for a provider [#more-than-one-key-for-a-provider]

Some providers let you store several keys: click **Add new key** on the provider's row and give each one a name. Today the gateway uses **only the first enabled key** for that provider. Spreading requests across keys is not built yet; see [Several keys for one provider](/features/routing/key-pool).

## Related [#related]

* [API keys and virtual keys](/features/organization/api-keys)
* [Call other providers](/features/routing/other-providers)
* [Keep AI working when a provider fails](/guides/provider-failover)
* [Audit log](/features/organization/audit-log) — adding, changing and deleting provider keys is recorded


# Roles and custom roles (https://docs.corevalue.dev/features/organization/roles)





A **role** is a set of permissions. Every member has one. Three roles are built in and mean the same thing in every organisation. On the Enterprise plan you can also define your own.

<img alt="The Roles tab" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                     |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **Where**   | Top bar → **Governance** → **Roles** (`/governance?tab=roles`).                                                                            |
| **Role**    | Anyone can view the matrix. Owner or Admin to create, edit or archive a custom role (`org.members.write`).                                 |
| **Plan**    | Built-in roles: every plan. Custom roles: **Enterprise**. On other plans the tab shows the matrix read-only, and **New role** is disabled. |

## Built-in roles [#built-in-roles]

| Role       | Holds                                                                                                                                                                                                                                          |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Owner**  | Every permission, including `ownership`: transfer the organisation, delete it, cancel the subscription and remove a payment method. There is one owner; ownership moves only by [transfer](/features/organization/members#transfer-ownership). |
| **Admin**  | Every permission except `ownership`.                                                                                                                                                                                                           |
| **Member** | `org.read`, `traffic.read` and `traffic.annotate`: open the console, read requests and spend, and add feedback, scores and tags.                                                                                                               |

The built-in roles cannot be edited. Every permission, and the API routes each one unlocks, is listed on [Roles and permissions](/references/roles).

## Read the matrix [#read-the-matrix]

The tab is a table: one row per permission, one column per role. Each column header shows whether the role is **built-in** or **custom**, and how many members hold it. A cell says **Yes** when the role holds that permission. `org.read` is marked **implicit** — every role holds it — and `ownership` is marked **owner only**.

## Create a custom role [#create-a-custom-role]

A custom role is the Member role plus the permissions you add. Use it to give someone exactly what they need — for example a finance viewer who can read reports but not touch keys.

1. Click **New role**.
2. Enter a **Name** and, optionally, a **Description**.
3. Under **Permissions**, tick what the role should hold. They are grouped as **See**, **Configure**, **Credentials and integrations**, and **People and billing**.
4. Click **Create role**.

You can only grant permissions you hold yourself. No custom role can hold `ownership`.

## Give a member a custom role [#give-a-member-a-custom-role]

* For someone already in the organisation, pick the role from the selector on their row in [Members](/features/organization/members#change-a-members-role).
* When adding someone, choose it as their **Role** in the **Invite a member** dialog.

## Edit or archive a custom role [#edit-or-archive-a-custom-role]

Use **Edit** or **Archive** under the role's name in the matrix. A change to a role applies to everyone who holds it. A role cannot be archived while any member still holds it: give those members another role first. An archived role leaves the matrix and can no longer be assigned.

If your organisation moves off Enterprise, custom roles already held keep working, but you cannot create or edit them.

## API keys have no role [#api-keys-have-no-role]

A CoreValue key never inherits the role of the person who created it. A key holds what a member holds and nothing more. Which customers, models and providers a key may use is set separately, with [key scopes](/features/access/key-scopes).

## Related [#related]

* [Roles and permissions](/references/roles) — the full permission catalogue
* [Members](/features/organization/members)
* [Audit log](/features/organization/audit-log) — role changes are recorded there


# Single Sign-On (OIDC) (https://docs.corevalue.dev/features/organization/single-sign-on)



Single sign-on lets people at your email domain sign in to CoreValue through
your own identity provider (IdP) instead of a password. Your IdP decides who
can sign in, applies its own multi-factor policy, and records the sign-in in
its own logs.

Single sign-on is available on the **Enterprise** plan. It uses **OpenID
Connect**, so it works with identity providers that support that standard, such
as Okta and Microsoft Entra ID. SAML is available on request.

## What you need [#what-you-need]

* An organization **owner** or **admin** to configure it. Only the **owner**
  can require it.
* An OIDC app registration at your IdP (a "web application" with the
  authorization code flow).
* Access to your domain's DNS, to add one TXT record.

## Set it up [#set-it-up]

1. In CoreValue, open **Settings → Single sign-on**.
2. Enter your IdP's **issuer URL**, the app's **client ID** and **client
   secret**, and your **email domain** (for example `example.com`). Save.
   CoreValue reads the issuer's discovery document
   (`/.well-known/openid-configuration`) and refuses an issuer that does not
   match it exactly.
3. Copy the **Redirect URI** the page shows into your IdP app registration as
   the sign-in redirect URI. If your IdP asks for an **initiate login URI**
   (the app tile), use the one shown beside it.
4. Add the TXT record the page shows to your DNS:
   `_corevalue-sso.<your domain>` with the value
   `corevalue-sso-verification=<token>`. Then press **Verify domain**.
   Until the domain is verified, nobody can sign in through the provider.
5. Press **Test sign-in** and sign in through your IdP.

The issuer must match the `iss` value in the tokens your IdP issues,
character for character. For Microsoft Entra ID it has the form
`https://login.microsoftonline.com/<tenant-id>/v2.0`. For Okta it is your Okta
domain, or your authorization server's URL such as
`https://<your-org>.okta.com/oauth2/default`.

## How people sign in [#how-people-sign-in]

People choose **Continue with SSO** on the sign-in page and enter their work
email. CoreValue sends them to your IdP and back.

* **New people** get a CoreValue account the first time they sign in, as a
  **member** of your organization. An owner or admin can change their role on
  the Members page.
* **People who already have an account** with the same email are linked to
  it, if your IdP confirms the address (the `email_verified` claim). If it
  does not, the sign-in is refused rather than linked.
* An IdP sign-in for an address **outside your verified domain** is refused.

Each new account also gets a personal organization, as every sign-up does;
people land in yours after signing in through your IdP.

## Requiring single sign-on [#requiring-single-sign-on]

The owner can turn on **Require single sign-on**. After that, people at your
domain cannot sign in, sign up or reset a password with email and password, or
sign in with Google or GitHub; they must use your IdP.

* The switch cannot be turned on until the owner has signed in through your
  IdP at least once.
* **The organization owner is never blocked**, so a broken IdP cannot lock
  the organization out. Keep the owner's password: if the owner has none, the
  console shows a warning beside the switch (it does not stop you), because
  the owner then signs in only through your IdP, and CoreValue's operator
  break-glass is the way back in if the IdP is unreachable.
* If your IdP locks you out, CoreValue support can turn the requirement off.
  That action is recorded in our audit log and in your organization's.
* People who are already signed in stay signed in until their session
  expires, unless the owner ticks **Also sign out members at this domain who
  are signed in now** when turning the requirement on. Then their sessions end
  at once (everyone at the domain who is a member, except the owner and the
  person turning it on), they sign back in through your IdP, and the audit log
  records how many sessions ended. If that step fails, enforcement stays on
  and the owner is told; the owner can sign members out again at any time
  while the requirement is on, with **Sign out members at this domain who are
  signed in now**, which is also how to force a sign-out after an incident.
* While the requirement is on, the owner cannot be removed, demoted or leave,
  and ownership cannot be handed to someone at your domain who has no
  password. Turn the requirement off first.

Turning the requirement off restores password sign-in; nothing is deleted.

## Security [#security]

* Your client secret is encrypted before it is stored, with a key held
  outside the database, and is never shown again after you save it.
* Every change — saving the provider, verifying the domain, turning the
  requirement on or off, removing it — appears in your organization's audit
  log. The secret never does.
* CoreValue asks your IdP for `openid email profile` only; it does not
  request offline access or refresh tokens.

## What is not included yet [#what-is-not-included-yet]

* SAML (available on request).
* Mapping IdP groups to CoreValue roles.
* SCIM provisioning: removing someone at your IdP does not remove them from
  CoreValue. Remove them on the Members page.


# Teams (https://docs.corevalue.dev/features/organization/teams)





A **team** is a group of keys and the people responsible for them — usually a product team or a service. A team does three things:

* **Spend.** Every request is counted against the team of the key that made it. That is what chargeback and the top bar's team picker use.
* **Budget.** A team can have a monthly budget, enforced on all of its keys together.
* **Visibility.** When request visibility is scoped to teams, a team's members see the traffic of the team's keys. See [Who sees whose requests](/features/access/request-visibility).

<img alt="The Teams section on Governance › Access" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                                                       |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Governance** → **Access**, below **Members** and **Request visibility**. The same table, without the visibility control, is on **Budgets & Limits** → **Teams**. |
| **Role**    | Owner or Admin to create, edit or delete teams and change who is in them (`org.members.write`). Everyone can see the table.                                                  |
| **Plan**    | Every plan.                                                                                                                                                                  |

## Create a team [#create-a-team]

1. Click **Create team**.
2. Fill in the dialog and click **Create**.

| Field                         | What it does                                                                                   | Default                    |
| ----------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------- |
| **Name**                      | Shown across the console and in the top bar's team picker. Must be unique in the organisation. | —                          |
| **Monthly budget (optional)** | The team's ceiling for the calendar month, counted across all its keys. Must be more than $0.  | No budget                  |
| **At the budget**             | **Alert**, **Downgrade** or **Block**, or inherit the organisation's action.                   | Inherit the organisation's |

The table shows each team's **Members**, **Keys**, **Monthly budget** used this month, and what happens **At the budget**. How team budgets are enforced is on [Budgets & Limits](/features/budgets/budgets-and-limits#set-a-team-budget).

To edit a team, click the edit icon at the end of its row.

## Add people to a team [#add-people-to-a-team]

1. Click the member count on the team's row (for example **3 members**).
2. In the panel, choose someone from &#x2A;*Add a member...** and click **Add**.
3. To take someone off the team, click **Remove** beside their name.

Only people already in the organisation can be added. While request visibility is scoped to teams or individuals, only the owner can add people to a team, because doing so changes what they can see.

## Put keys on a team [#put-keys-on-a-team]

A key's team is set on the key, not on the team:

* When you create a key on **Governance** → **Keys**, choose its **Team**.
* For an existing key, click **Edit** on its row and change **Team**.

See [API keys and virtual keys](/features/organization/api-keys). Moving a key changes where its future spend is counted; requests it already made stay with the team they were made under.

<Callout type="info">
  The `Cova-Team-Id` request header is recorded for your own filtering only. Team budgets, chargeback and visibility always use the key's team, so a request cannot move its spend to another team by sending a different header.
</Callout>

## Delete a team [#delete-a-team]

Click the delete icon on its row, then **Delete team**. A team that still has keys cannot be deleted: move its keys to another team first. Keys are never deleted with a team. The team's members are taken off it; their accounts are not affected.

## Related [#related]

* [Team chargeback](/guides/team-chargeback) — monthly spend per team for finance
* [Budgets & Limits](/features/budgets/budgets-and-limits)
* [Members](/features/organization/members)


# Chargeback (https://docs.corevalue.dev/features/reports/chargeback)





**Chargeback** splits one calendar month's spend by team, customer and model. While the month is open it is *showback*: what each team and customer consumed, read live. Once the month is **closed**, its figures are frozen, so an invoice raised from it does not move when a late request lands.

For a step-by-step setup (teams, keys, budgets, then this report), see [Team chargeback](/guides/team-chargeback).

<img alt="Reports › Chargeback grouped by team" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                                                  |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Reports** → **Chargeback** (`/reports?tab=chargeback`).                                                                                                     |
| **Role**    | Viewing and downloading need `reports.read`. **Close month** needs `spend_controls.write`. Owners and admins have both. See [Roles and permissions](/references/roles). |
| **Plan**    | Every plan. Revenue and margin need [customer prices](/features/reports/customer-pricing), which are Growth and Enterprise.                                             |

## How spend is split [#how-spend-is-split]

Each row is one team × customer × model combination for the month.

* **Team** is the team of the key that made the request, never the `Cova-Team-Id&#x60; header. A key with no team counts as *— unattributed*.
* **Customer** is the `Cova-Customer-Id&#x60; your application sends, matched without regard to case. Requests without it count as *— unattributed*.
* **Model** is the model that served the request.

Unattributed spend is listed, not dropped, so the rows always add up to the month's total. If they ever do not, the page says &#x2A;The rows do not add up to the month's total; do not invoice from this.*

Months are UTC calendar months.

<Callout type="info">
  Chargeback covers LLM requests. The cost of MCP tool calls and agent calls is not included in its totals yet.
</Callout>

## Read the report [#read-the-report]

| Control          | What it does                                                                                                                                              |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Month**        | Any of the last twelve months. The page opens on last month if it had spend, otherwise on this month.                                                     |
| **Group by**     | **Team**, **Customer**, **Model** or **Flat**. An open month starts grouped by team, a closed month by customer. Click a group's name to expand its rows. |
| **Download CSV** | Saves the month's rows. See [Export for finance](#export-for-finance).                                                                                    |
| **Close month**  | Freezes the month. See [Close a month](#close-a-month).                                                                                                   |

The heading reads *Showback — September 2026* while the month is open, with the badge &#x2A;Showback — what each team and customer consumed. Nothing is billed.* A closed month reads *Chargeback — September 2026* and notes when it was closed, and whether by a member or by the monthly job.

Above the table, **Cost** is the month's total, with **Budget** and **Variance** when an organisation budget is set. Variance is shown for the current month only.

| Column       | What it means                                                                                                                                                |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Cost**     | What the requests cost.                                                                                                                                      |
| **Requests** | How many requests.                                                                                                                                           |
| **Revenue**  | The customer's declared price for the month, shared across its rows in proportion to cost. A dash means the customer has no price.                           |
| **Margin**   | (Revenue − Cost) ÷ Revenue. A group margin marked `*` covers only the priced rows in the group; unpriced cost is left out of it rather than counted as free. |

Revenue is what you declared a customer pays, not an invoice. For how it is calculated, see [Customer pricing and margin](/features/reports/customer-pricing#how-revenue-and-margin-are-calculated).

On an open month, a line above the table names any customer whose margin is below the floor on their own price.

## Close a month [#close-a-month]

1. Choose the **Month**.
2. Check the rows, especially *— unattributed* spend.
3. Click **Close month*&#x2A;. You will see &#x2A;"Month closed"*.

Closing records the month's cost, requests, revenue and margin as they stand. [Value](/features/reports/value) and [Unit economics](/features/reports/unit-economics) then use the closed cost for that month too, so every report agrees with what you invoiced.

You do not have to close months by hand: a monthly job closes the previous month on the 3rd. If you correct something after a month is closed, click **Close again** to record the figures afresh; the page shows who closed it and when.

## Export for finance [#export-for-finance]

**Download CSV** saves the month's rows as `chargeback-YYYY-MM.csv`, or `chargeback-YYYY-MM-provisional.csv` while the month is open. The file has one row per team × customer × model, with a total row:

`Team`, `Customer`, `Model`, `Cost (USD)`, `Requests`, `Prompt tokens`, `Completion tokens`, `Revenue (declared price, USD)`, `Margin (%)`

Subtotals on screen are each rounded to the cent, so added by eye they can differ from the total by a cent. The total, and the file, are exact.

## Related [#related]

* [Team chargeback](/guides/team-chargeback)
* [Per-customer budgets](/guides/per-customer-budgets)
* [Teams](/features/organization/teams)


# Customer pricing and margin (https://docs.corevalue.dev/features/reports/customer-pricing)







If you resell AI to your own customers, record what each one pays you. CoreValue then shows revenue and margin next to each customer's cost, warns you when a margin gets thin, and can stop serving a customer before they cost more than you are willing to carry.

Customer pricing never changes what a provider charges you. It only records your side of the deal.

<img alt="Budgets & Limits › Customers with prices and margins" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                                                       |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Budgets & Limits** → **Customers** (`/budgets?tab=customers`).                                                                                                   |
| **Role**    | Viewing the tab needs `reports.read`. Setting or removing a price needs `spend_controls.write`. Owners and admins have both. See [Roles and permissions](/references/roles). |
| **Plan**    | Growth or Enterprise. On another plan the price fields are read-only; a price set earlier still applies and can be removed.                                                  |
| **Traffic** | Requests must carry `Cova-Customer-Id`. See [Per-customer budgets](/guides/per-customer-budgets#1-bind-requests-to-customers).                                               |

## Set a price [#set-a-price]

<Steps>
  <Step>
    ### Open the price dialog [#open-the-price-dialog]

    * For one customer, click **Price** on the customer's row.
    * For every customer without a price of their own, click **Default price** at the top of the tab.

    The table lists customers that have a budget or a price of their own. To price a customer who has neither, give them a budget first with **Add limit**, or cover them with the default price.
  </Step>

  <Step>
    ### Fill in the price [#fill-in-the-price]

    | Field                   | What it means                                                                                                                   |
    | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
    | **Monthly fee (USD)**   | A fixed amount the customer pays each month.                                                                                    |
    | **Markup on usage (%)** | What you charge on top of their AI cost. `0` means you resell usage at cost; leaving it blank means usage is not resold at all. |
    | **Margin floor (%)**    | Optional. The lowest margin you accept, from 0 up to (not including) 100.                                                       |
    | **At the floor**        | Appears once a floor is set. **Alert me, keep serving**, or **Refuse this customer's requests**.                                |

    A price needs a fee, a markup, or both. With a markup and no fee the margin never changes, so the dialog refuses a floor above it, because every request would breach it.
  </Step>

  <Step>
    ### Save [#save]

    Click **Save price**. To take a price away, open the dialog and click **Remove price**; a customer with no price of their own then falls back to the default price, if there is one.
  </Step>
</Steps>

## How revenue and margin are calculated [#how-revenue-and-margin-are-calculated]

For a customer whose AI cost this month is *c*:

| Price                               | Revenue                   | Margin                    |
| ----------------------------------- | ------------------------- | ------------------------- |
| Fee *F*, no markup                  | *F*                       | (*F* − *c*) ÷ *F*         |
| Markup *m*% (with or without a fee) | *F* + *c* × (1 + *m*/100) | (Revenue − *c*) ÷ Revenue |

With a fee alone, revenue is fixed while cost grows, so the margin can go below zero. With a markup, the margin falls towards *m* ÷ (100 + *m*) as cost grows but never below it: 20% for a 25% markup.

On the Customers tab:

| Column                   | What it shows                                                                                        |
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
| **Price**                | The fee per month and the markup. &#x2A;(default)* means the customer is on the default price.       |
| **Revenue · this month** | Revenue for this calendar month so far.                                                              |
| **Margin**               | This month's margin, with the floor beside it. Amber within 10 points above the floor, red below it. |

The same revenue and margin appear in [Chargeback](/features/reports/chargeback), [Unit economics](/features/reports/unit-economics) and [Value](/features/reports/value), shared across a customer's rows in proportion to cost.

## What a margin floor does [#what-a-margin-floor-does]

**Alert me, keep serving.** CoreValue checks customer margins every 15 minutes. It sends a warning when a customer's margin is within 10 points above the floor, and an alert when it falls below the floor. Each is sent at most once per customer per month; a customer already below the floor at the first check gets only the alert. Alerts go by email to your [budget alert recipients](/features/budgets/budget-alerts) and to Slack channels set up for budget alerts. They are not sent to webhooks.

**Refuse this customer's requests.** The floor becomes a ceiling on what the customer may cost you this month: the cost at which their margin would reach the floor. The dialog shows it as *Requests stop at* an amount *of cost this month*. Once the customer's cost reaches it, the gateway refuses further requests that carry their `Cova-Customer-Id`, as it would for a [customer budget](/features/budgets/budgets-and-limits#set-a-customer-budget) set to refuse, until the month ends. If the customer also has a refusing budget at the same or a lower amount, that budget applies instead. The same margin alerts are sent as well.

A floor the markup alone keeps the margin above (a floor at or below *m* ÷ (100 + *m*)) has no ceiling and refuses nothing.

<Callout type="warn">
  Like customer budgets, this is enforced on the customer ID your application sends. To bound a key whatever it sends, [cap the key](/features/budgets/budgets-and-limits#cap-a-virtual-key).
</Callout>

<img alt="The price dialog with a refusing margin floor" src="__img1" />

## Related [#related]

* [Per-customer budgets](/guides/per-customer-budgets)
* [Unit economics](/features/reports/unit-economics)
* [Budget alerts](/features/budgets/budget-alerts)


# Executive report (https://docs.corevalue.dev/features/reports/executive)





The **Executive Report** is the first tab of **Reports**. It puts the period's spend, request volume, efficiency and cache savings on one page, with this month's budget position and the five most expensive models, so you can hand leadership a summary without building one.

<img alt="The Executive Report tab" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                                                                                                          |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Reports** → **Executive Report** (`/reports?tab=executive`).                                                                                                                                                        |
| **Role**    | Anyone who can read traffic (`traffic.read`) sees the figures. The **Budget Utilization** block also needs `reports.read`, which owners and admins hold; members do not see it. See [Roles and permissions](/references/roles). |
| **Plan**    | Every plan.                                                                                                                                                                                                                     |

## Choose the period [#choose-the-period]

Use the period menu at the top right:

| Option            | Covers                                                     |
| ----------------- | ---------------------------------------------------------- |
| **Last 7 Days**   | The seven days up to now.                                  |
| **Last Month**    | One month back from today, up to now. This is the default. |
| **Last 3 Months** | Three months back from today, up to now.                   |

These are rolling windows ending now, not calendar months. Every figure on the page uses the period you choose, except **Budget Utilization**, which is always the current calendar month.

## What the report shows [#what-the-report-shows]

### Headline figures [#headline-figures]

| Figure               | What it means                                                                                                                                                       |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Total Spend**      | What requests cost in the period. The arrow and percentage compare it with the period of the same length just before; the note under it gives that previous figure. |
| **Total Requests**   | Requests made through the gateway in the period.                                                                                                                    |
| **Active Users**     | Distinct end users (`Cova-User-Id` values) seen in the period.                                                                                                      |
| **Avg Latency**      | Average request latency, in milliseconds.                                                                                                                           |
| **Cost / Token**     | Spend divided by tokens.                                                                                                                                            |
| **Cost / Request**   | Spend divided by requests.                                                                                                                                          |
| **Cache Cost Saved** | What requests answered from the [cache](/features/routing/caching) would otherwise have cost.                                                                       |
| **Cache Time Saved** | The response time those cached answers saved.                                                                                                                       |

The two cache figures read *Owners only* when your organisation restricts [request visibility](/features/access/request-visibility) and you are not the owner.

### Finding [#finding]

When spend moved against the previous period and one model makes up at least 40% of the top models' cost, a line under the figures names it, with its share of cost, its share of requests, and the change in spend. With no dominant model, no line appears.

### Budget Utilization [#budget-utilization]

The current UTC calendar month's spend against the organisation's **Monthly Budget**, as a bar and a percentage, with a note on how far through the month you are. Spend above the budget shows *Over budget by* the difference. The block appears only when an organisation budget is set; see [Budgets & Limits](/features/budgets/budgets-and-limits#set-the-organisation-budget).

The month's spend here is the same figure [Chargeback](/features/reports/chargeback) shows, so the two pages agree.

### Top 5 Models by Cost [#top-5-models-by-cost]

The five models with the highest cost in the period, with their **Requests**, **Total Tokens** and **Cost**.

### Spend Trend (Period over Period) [#spend-trend-period-over-period]

**Current Period**, **Previous Period** and the **Change** between them, with a sentence saying whether spend rose, fell or stayed flat.

## Share the report [#share-the-report]

Click **Export PDF**. Your browser's print dialog opens on a print layout headed with your organisation's name and today's date; choose *Save as PDF* or a printer.

<Callout type="info">
  The Executive Report is a summary, not a ledger. For the figures an invoice should quote, use [Chargeback](/features/reports/chargeback). To weigh spend against what the work was worth, use [Value](/features/reports/value).
</Callout>

## Related [#related]

* [Spend](/features/cost-tracking/spend): the same spend broken down by team, customer, model and more
* [Chargeback](/features/reports/chargeback)
* [Scheduled reports](/features/reports/scheduled)


# Scheduled reports (https://docs.corevalue.dev/features/reports/scheduled)





**Scheduled reports** has one setting: a weekly summary report, every **Monday** at **10am UTC**, sent to the people and Slack channels you choose. There is no choice of report type, period or schedule.

<Callout type="warn">
  CoreValue does not send the weekly summary yet. You can save recipients and channels on this tab, but no report is delivered today. Until it is, use **Export PDF** on the [Executive report](/features/reports/executive) or **Download CSV** on [Chargeback](/features/reports/chargeback) to share figures.
</Callout>

<img alt="Reports › Scheduled reports with emails selected" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                         |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Reports** → **Scheduled reports** (`/reports?tab=scheduled`).                                                                      |
| **Role**    | `egress.write`, which owners and admins hold: the setting sends data out of your organisation. See [Roles and permissions](/references/roles). |
| **Plan**    | Every plan.                                                                                                                                    |

## Set up the weekly summary [#set-up-the-weekly-summary]

<Steps>
  <Step>
    ### Turn it on [#turn-it-on]

    Switch on the toggle next to **Reports**. The recipient options appear.
  </Step>

  <Step>
    ### Choose recipients [#choose-recipients]

    | Option             | What it does                                                                                                                                                                                                                                                      |
    | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Emails**         | On by default. Click **Select emails to send alerts to** and pick members of your organisation. Only members can be chosen; you cannot type an outside address.                                                                                                   |
    | **Slack Channels** | Switch on, then click **Select slack channels to send alerts to** and pick channels. If Slack is not connected yet, a **Connect Slack** button appears instead; see [Slack](/features/alerts/slack). For a private channel, invite the CoreValue app to it first. |

    Choose at least one email or one channel.
  </Step>

  <Step>
    ### Save [#save]

    Click **Save*&#x2A;. You will see &#x2A;"Successfully enabled report"&#x2A; the first time, and &#x2A;"Successfully configured report"* after that.
  </Step>
</Steps>

To stop the summary, switch the toggle off and click **Save**. Your recipients are kept for when you switch it back on.

**Cancel** clears the selections on screen without saving.

## Related [#related]

* [Executive report](/features/reports/executive)
* [Slack](/features/alerts/slack)
* [Budget alerts](/features/budgets/budget-alerts): email and Slack alerts that are sent today


# Unit economics (https://docs.corevalue.dev/features/reports/unit-economics)





**Unit economics** divides a month's cost by a unit of work you choose (requests, runs or end users) and shows how that rate has moved over six months. Use it to answer questions like "what does a conversation cost for this customer, and is it going up?", and to see margin beside it.

<img alt="Reports › Unit economics grouped by customer" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                          |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Reports** → **Unit economics** (`/reports?tab=unit-economics`).                                                     |
| **Role**    | `reports.read`, which owners and admins hold. See [Roles and permissions](/references/roles).                                   |
| **Plan**    | Every plan. The **Margin** column needs [customer prices](/features/reports/customer-pricing), which are Growth and Enterprise. |

## Choose the view [#choose-the-view]

| Control      | Options                                                                                                                                                                      |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Group by** | **Team**: the team of the key that made the request. **Customer**: the `Cova-Customer-Id` your application sends. **Feature**: the `Cova-Feature-Id` your application sends. |
| **Unit**     | **Per request**, **Per run** or **Per end user**.                                                                                                                            |

A **run** is one value of `Cova-Session-Id`, so a conversation or agent run counts once however many requests it makes. An **end user** is one value of `Cova-User-Id`. Both count only requests that carry the header; see [Cost attribution](/features/cost-attribution).

The page opens on **Per end user** when your requests carry `Cova-User-Id`, otherwise **Per run** when they carry `Cova-Session-Id`, otherwise **Per request**. Your own choice then sticks.

## Read the table [#read-the-table]

The line above the table gives the latest month and its total cost, and says whether the month is closed.

| Column                                  | What it means                                                                                                                         |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **Cost**                                | What the group spent in the month.                                                                                                    |
| **Requests**, **Runs** or **End users** | How many of the chosen unit the group had.                                                                                            |
| **Cost per request / run / end user**   | Cost ÷ that count. A dash means there was nothing to divide by, not that the work was free.                                           |
| **6-month trend**                       | The unit cost for each of the last six months.                                                                                        |
| **Change**                              | From the first to the last month in the trend that has a unit cost. Red when the unit cost went up.                                   |
| **Margin**                              | (Revenue − Cost) ÷ Revenue, where revenue is the customer's declared price shared across rows by cost. A dash means no price applies. |

Spend with no team or no customer appears as *— unattributed*.

## Open and closed months [#open-and-closed-months]

A month that [Chargeback](/features/reports/chargeback) has closed shows *Closed — cost and revenue are what chargeback settled on*. Cost and revenue for Team and Customer are then the closed figures, so this page agrees with what you invoiced.

Run and end-user counts were never stored at close, so they are always read live. When you look at a closed month **Per run** or **Per end user**, a note says the rate can still move while the cost it divides cannot.

An open month shows *Provisional — read live, and can still change*. **Feature** is never closed, so every month in that view is read live.

## Common tasks [#common-tasks]

* **Spot a customer getting more expensive to serve.** Group by **Customer**, choose **Per end user** or **Per run**, and look for red in **Change**.
* **Check a margin.** Group by **Customer** and read **Margin**. The floor, and what happens when a customer crosses it, are set on [Budgets & Limits › Customers](/features/reports/customer-pricing).
* **Compare features.** Group by **Feature** with **Per request** to see which parts of your product cost most per call.

<Callout type="info">
  Unit economics does not say what a month totalled. [Chargeback](/features/reports/chargeback) is the month of record.
</Callout>

## Related [#related]

* [Customer pricing and margin](/features/reports/customer-pricing)
* [Value](/features/reports/value)
* [Runs](/features/cost-tracking/runs)


# Value (https://docs.corevalue.dev/features/reports/value)





**Value** compares what requests cost with what the work they did is worth. Your application records a measure of the work on each request, such as minutes saved, and you declare what one unit of it is worth in dollars. Both sides are then money, so the ratio reads plainly: **1.0x means break-even**.

CoreValue does the arithmetic from your inputs. It cannot tell you what an hour saved is worth to your business; agree the rate with the people who own the workflow.

<img alt="The Value tab with the rate editor open" src="__img0" />

## Before you start [#before-you-start]

| Requirement | Detail                                                                                                                                                                |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Where**   | Top bar → **Reports** → **Value** (`/reports?tab=value`).                                                                                                             |
| **Role**    | Viewing needs `reports.read`. Declaring or changing a rate needs `spend_controls.write`. Owners and admins have both. See [Roles and permissions](/references/roles). |
| **Plan**    | Declaring a rate needs the Growth or Enterprise plan. A rate set earlier keeps working on any plan.                                                                   |

## 1. Record the measure on each request [#1-record-the-measure-on-each-request]

Send the measure as a [custom property](/api-reference/headers) on each request, as a plain number:

```bash
-H "Cova-Property-Time-Saved-Mins: 12"
```

The gateway lowercases property names and turns hyphens into underscores, so this header is recorded as `time_saved_mins`. Use that recorded name when you declare the rate.

A value that is not a number, such as `high` or `2 hours`, is not counted as zero. The request is left out of the ratio, its cost with it, and the page tells you how many requests were skipped.

## 2. Declare a rate [#2-declare-a-rate]

1. Click **Declare a rate** (or **Change rate** once one exists).

2. Fill in the form:

   | Field                    | What it means                                              |
   | ------------------------ | ---------------------------------------------------------- |
   | **Property**             | The recorded property name, for example `time_saved_mins`. |
   | **USD per unit**         | What one unit is worth. Must be greater than zero.         |
   | **Unit name (optional)** | How the unit reads on the page, for example `minutes`.     |

3. Click **Save*&#x2A;. You will see &#x2A;"Rate saved"*.

With more than one rate declared, a **Value property** menu appears so you can choose which one the page uses.

## Read the report [#read-the-report]

Choose **Group by**: **Team**, **Customer** or **Feature**. Team is the key's team, Customer is the `Cova-Customer-Id` your application sends, and Feature is `Cova-Feature-Id`. The page shows the latest month, with a six-month trend per row.

**Custom property** also appears in the menu, but grouping by a property does not yet show results in the console.

### Summary [#summary]

**Cost**, then **Budget** and **Variance** when an organisation budget is set, then **Value** (the units counted, at your rate) and **ROI**. ROI is uncoloured when it rests on less than half of the spend.

### Table [#table]

| Column                   | What it means                                                                                                                           |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| **Cost**                 | Everything the group spent in the month, whether or not its requests carried the property.                                              |
| **Budget**, **Variance** | The group's budget and how far cost is over or under it. Shown for Team and Customer only.                                              |
| **Measured**             | The cost of requests that carried a number for the property.                                                                            |
| **Coverage**             | Measured as a share of Cost.                                                                                                            |
| **Value**                | Units counted × USD per unit.                                                                                                           |
| **ROI**                  | Value ÷ Measured. Green at 1.0x or above, red below. Marked `*` and left uncoloured when coverage is under half.                        |
| **6 months**             | ROI per month.                                                                                                                          |
| **Revenue**              | What the group's customers pay you, from [customer pricing](/features/reports/customer-pricing). Shown beside value, never added to it. |

A dash means there was nothing to compare, not that the work was worthless. Spend with no team or no customer is listed as *— unattributed*.

### Provisional and closed months [#provisional-and-closed-months]

A month that [Chargeback](/features/reports/chargeback) has not closed is marked *Provisional — read live, and can still change*. Once it closes, the Team and Customer cost is the closed figure. Value and ROI are always read live, because a closed month never stored them. Feature is never closed, so its months are always read live.

<Callout type="info">
  Value does not say what anyone owes. [Chargeback](/features/reports/chargeback) is the month of record.
</Callout>

## Related [#related]

* [Unit economics](/features/reports/unit-economics): what one request, run or end user costs
* [Cost attribution](/features/cost-attribution): tagging requests with customers, users and properties


# Call an agent (https://docs.corevalue.dev/features/agents/call)



## Endpoint [#endpoint]

Each registered agent has its own address at the gateway:

```
https://gateway.corevalue.dev/a2a/<agent-id>
```

Its agent card is at:

```
https://gateway.corevalue.dev/a2a/<agent-id>/.well-known/agent-card.json
```

The card is the agent's own, rewritten so that every endpoint in it points at the gateway. Reading it needs the same key as a call, and the caller must be one the agent allows.

## Authentication [#authentication]

Send a CoreValue API key as `Authorization: Bearer <CoreValue API key>`. Keys in the URL are refused. The agent never sees your CoreValue key: CoreValue sends the agent the credential stored for it, if any.

## Protocol versions [#protocol-versions]

A2A 0.3 and 1.0 clients both work, whichever version the agent speaks: the gateway translates the request and the answer, streamed answers included. The caller's version is read from its `A2A-Version` header, or from the method's name when it sends none. A field that one version cannot carry is refused with an error that names it, never dropped.

```bash
curl https://gateway.corevalue.dev/a2a/billing \
  -H "Authorization: Bearer $COVA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "A2A-Version: 1.0" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "SendMessage",
    "params": {
      "message": {
        "messageId": "4f8c2a1e-6b1d-4c3e-9a57-0d2e8b7f1c90",
        "role": "ROLE_USER",
        "parts": [{ "text": "What is the balance on invoice 1042?" }]
      }
    }
  }'
```

## Task trace [#task-trace]

Every answer carries two response headers:

* `Cova-Task-Trace`: the task trace this call joined, as `<trace-id>;parent=<call-id>`.
* `Cova-Call-Id`: this call's id.

CoreValue also sends `Cova-Task-Trace` to the agent. An agent that passes it on in its own LLM, MCP and A2A calls lets CoreValue tie that spend back to the task and the agent that asked for it. A call without the header starts a new trace, unless the agent requires one.

## Refusals [#refusals]

| Status                           | When                                                                                                          |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| 403 `A2aGatewayNotEntitled`      | Your organisation's plan does not include the agent gateway.                                                  |
| 404 `UnknownA2aAgent`            | No agent is registered with that ID.                                                                          |
| 403, reason `caller_not_allowed` | The caller is not one the agent's **Who may call it** setting allows.                                         |
| 403, reason `access_rule`        | An enforced [access rule](/features/access/mcp-tool-policies) refuses the caller.                             |
| 403, reason `trace_required`     | The agent requires a task trace and the call has none.                                                        |
| 403, reason `loop_limit`         | One agent has called this one more times within a task than the loop limit allows, and the limit is enforced. |

A refusal on a call is a JSON-RPC error whose `data.reason` names the cause. A caller that may not reach the agent is refused the agent card too, with 403 `A2aCallerNotAllowed`. Every refused call is still recorded, so owners can tell a rejected request from an agent failure.

Changes to an agent's settings and to access rules reach the gateway within a minute.

## Cost [#cost]

A2A calls are currently unpriced: the call's row carries no cost. The LLM and MCP spend an agent makes is priced as usual on its own rows.


# How the agent gateway works (https://docs.corevalue.dev/features/agents/overview)



The agent gateway puts agent-to-agent (A2A) traffic behind CoreValue. Your organisation registers the agents it uses. Callers reach each one at a CoreValue address with a CoreValue API key, and CoreValue forwards the call to the agent, with the credential you stored for it when the agent needs one.

A registered agent has:

* an **agent ID**, which is its path at the gateway;
* the agent's own URL, where CoreValue reads its agent card;
* the A2A version it speaks, 0.3 or 1.0;
* who may call it;
* optionally, a credential CoreValue sends to the agent;
* the CoreValue keys the agent itself calls with, so that its own LLM, MCP and A2A traffic is attributed to it.

An agent that is never called by another agent can be registered with no endpoint: only its ID, name and keys. See [Register an agent](/features/agents/register#an-agent-with-no-endpoint).

## What CoreValue records [#what-corevalue-records]

Each call records the caller and the agent called, the method, the task and its state, the decision (allowed, refused, or served but recorded as one an audit rule would refuse), the timing and the size. A refused call is recorded too, so you can tell a refusal from an agent failure.

A2A calls are not priced yet, so the call itself carries no cost. The LLM and MCP spend an agent makes while it works on a task is recorded on its own rows, and is tied back to the task when the agent passes the task trace on. See [Call an agent](/features/agents/call).

## Access [#access]

Any member can view the agent registry and the agent traffic visible to them. Owners and admins register, change, test and remove agents, and bind keys to them. To decide which callers may reach which agents across the organisation, use [access rules](/features/access/mcp-tool-policies).

The agent gateway is available on plans that include it. Agents with no endpoint are available on every plan. See [Billing, plans and credits](/features/organization/billing).


# Register an agent (https://docs.corevalue.dev/features/agents/register)



Open **Gateway › Agents** and choose **Add agent**. Owners and admins can add agents; other members see the registry but cannot change it.

* **Agent ID.** The agent's path at the gateway, `/a2a/<agent-id>`. It cannot be changed later.
* **Where it runs.** An A2A server, a Bedrock AgentCore runtime, or **No endpoint**.
* **URL.** For an A2A server, the agent's base URL or its card's URL; the card is read from `/.well-known/agent-card.json`. For AgentCore, the runtime ARN. CoreValue reaches the agent only over HTTPS at a public address, and checks this on every request.
* **A2A version it speaks.** 0.3 or 1.0. Callers can use either; CoreValue translates.
* **Who may call it.** Anyone in the organisation, or only the agents, teams and members you choose. This is decided from the caller's key, never from a header.
* **Headers passed through from callers.** None by default. `Authorization`, cookies and `Cova-*` headers are never passed.
* **Require a task trace.** Refuse calls that do not carry a `Cova-Task-Trace` header, so every call can be tied to its task.
* **Loop limit.** How many times one agent may call this one within a task: the organisation's limit, no limit, or its own.
* **Credential the gateway sends.** Optional. For an A2A server, a secret sent as `Authorization: Bearer <secret>` unless you name another header; for AgentCore, IAM (SigV4) keys or an OAuth bearer token. It is stored encrypted, and never returned by the API or shown in the console.

## An agent with no endpoint [#an-agent-with-no-endpoint]

Most agents call models and tools but are never called by another agent. Register one with **No endpoint** to give it an identity: bind it to the keys it calls with, and its LLM and MCP traffic is attributed to it and access rules can name it. It takes only an agent ID and a name. CoreValue never calls it: an A2A call addressed to it is answered as if no such agent existed (404). Agents with no endpoint are available on every plan; reaching agents through the gateway needs a plan that includes the agent gateway.

## After saving [#after-saving]

After saving an A2A server, use **Test** to read the agent's published card and check its protocol version, whether it streams, its skills and its own authentication scheme.

Use **Keys** to bind the CoreValue keys the agent itself calls with. LLM, MCP and A2A traffic made with those keys is then attributed to the agent, and access rules that name the agent apply to it. A key can belong to one agent at a time.

Changes reach the gateway within a minute.


# Connect MCP clients (https://docs.corevalue.dev/features/mcp/connect-clients)



An MCP client reaches your organisation's MCP servers through one CoreValue
URL. It proves who it is in one of two ways:

* **An API key in a header.** For clients that let you set a header, such as
  Claude Code and Cursor, and for your own agents.
* **Signing in.** For clients that connect with OAuth and cannot send a header
  you choose, such as claude.ai. A member signs in to CoreValue from the
  client and approves the connection. No key is pasted anywhere.

Both reach the same servers and tools, and both are logged as tool calls. The
difference is who a call is attributed to: the key and its team, or the member
who signed in.

## Which URL to use [#which-url-to-use]

Every server registered on **Gateway › MCP servers** has a **Server ID**.
Clients can connect to:

* `https://gateway.corevalue.dev/mcp` — every enabled server in your
  organisation behind one URL. Tool names are prefixed with the server ID, as
  `<server-id>__<tool>`.
* `https://gateway.corevalue.dev/mcp/<server-id>` — one server, with its tool
  names unchanged. Use this when a client needs to receive requests from the
  server itself, such as sampling or elicitation, which the combined URL does
  not relay.

Use the URL exactly as written, with no trailing slash.

## Connect with an API key [#connect-with-an-api-key]

Create a CoreValue API key (see [API keys](/features/organization/api-keys)), then add
the URL and the key to your client's MCP configuration. Send the key as
`Authorization: Bearer <key>`. A key in the URL (`?key=`) is refused.

For Claude Code:

```bash
claude mcp add --transport http corevalue \
  https://gateway.corevalue.dev/mcp \
  --header "Authorization: Bearer $COVA_API_KEY"
```

Cursor, and any other client that accepts a remote MCP server with a header,
needs the same two things: the URL and the `Authorization` header. Restart or
reconnect the client, then open its MCP tools list to confirm the tools are
there.

Calls made with a key are attributed to the key and its team. To attribute
them to an end user or a customer as well, send `Cova-User-Id` and
`Cova-Customer-Id`, as on the LLM gateway.

## Connect by signing in [#connect-by-signing-in]

### Turn on client sign-in [#turn-on-client-sign-in]

Signing in is off until an owner or admin turns it on for the organisation:

1. Go to **Gateway › MCP servers**.
2. Under **Client sign-in**, turn on **Let members connect MCP clients by
   signing in**.

From then on, any member of the organisation can connect a client. The
setting changes nothing about how the gateway reaches the servers themselves;
for servers that need their own sign-in, see
[Servers that need their own sign-in](/features/mcp/upstream-sign-in).

Turning it off disconnects every client connected to the organisation at once,
and members cannot connect new ones until it is turned back on.

### Add the URL to the client [#add-the-url-to-the-client]

In claude.ai, add the URL as a custom connector, for example
`https://gateway.corevalue.dev/mcp`. When you connect it, claude.ai sends you
to CoreValue to sign in.

A connection works on the URL it was made for and no other. To use a second
URL, such as a single server's, connect it separately.

### Approve the connection [#approve-the-connection]

After you sign in, CoreValue shows a consent screen, **Connect *app* to
CoreValue**, before anything is shared. It shows:

* **The app.** Its name, and how it was identified. An app that publishes a
  metadata document is shown with the host that serves it. An app that
  registered itself chose its own name, and the screen says the name is
  provided by the app, not verified by CoreValue.
* **Organisation.** Which organisation the connection works in. Only
  organisations you belong to that have the MCP gateway and have client
  sign-in turned on are offered.
* **Team (optional).** Tool calls from the connection are attributed to this
  team. You can only pick teams you belong to.
* **Where you will be sent next.** The host the app receives the answer at.
  When that is an app on your own computer, the screen says so: continue only
  if you started the connection from an app you trust.

**Allow** completes the connection. **Deny** returns you to the app with
nothing shared.

### What the client can then do [#what-the-client-can-then-do]

The client uses the MCP gateway as you. It can list and call the tools your
organisation makes available through the gateway, subject to the same
[access rules](/features/access/mcp-tool-policies) as any other caller, matched to you
and the team you picked.

* Every tool call is recorded under your name. On **Requests › MCP**, a call
  made this way reads *via* the app and your email, for example
  `via Claude · ana@example.com`.
* The client's access lasts an hour at a time. A client that asks for
  offline access, as claude.ai does, renews it in the background, so you do
  not sign in again every hour. After 30 days without use, or 90 days after
  you connected, you sign in again.
* The sign-in works on the MCP gateway only. It does not work on the LLM
  gateway or on agent routes, which take an API key.

## Connected clients [#connected-clients]

**Gateway › MCP servers › Connected clients** lists the apps you have signed
in, under **Your connections**: the app, the organisation, the team, when it
was connected and when it was last used.

* **Revoke** disconnects an app at once. Tool calls it already made stay in
  your logs. To use it again, connect it again.
* **Owners and admins** also see &#x2A;*All connections in *organisation***, with
  the member each connection belongs to, and can revoke any of them.
* A connection whose member has left its team keeps working; it shows
  *team removed*, and its calls are attributed to no team.
* **A member who leaves the organisation** loses their connections to it.

## Troubleshooting [#troubleshooting]

**"No organisation can accept this connection."** None of your organisations
can take the connection: either client sign-in is off, or the organisation does
not have the MCP gateway, or you are not a member of the organisation you
expected. Ask an owner or admin to turn on **Client sign-in** in
**Gateway › MCP servers**, or to invite you.

**"This connection request can't be completed."** The app made a request
CoreValue cannot accept, and the page says why in brackets — for example, the
app is not one CoreValue knows, or its return address is not one it
registered. Nothing was shared. Return to the app and connect again; if it
keeps happening, remove the connector from the app and add it again.

**The client connected, but every call fails.** The connection may have been
revoked — by you, an owner or admin, or by client sign-in being turned off.
Check **Connected clients**, then reconnect from the client.

**No tools appear.** Check that the server is enabled on
**Gateway › MCP servers**, that an access rule does not deny you its tools, and,
for a key, that the key is active. A server where each member signs in shows
only a `<server-id>__connect` tool until you connect your own account; see
[Servers that need their own sign-in](/features/mcp/upstream-sign-in).

## Related [#related]

* [How the MCP gateway works](/features/mcp/overview)
* [Servers that need their own sign-in](/features/mcp/upstream-sign-in)
* [Roles and permissions](/references/roles): who can turn on client sign-in
  and revoke other members' connections.


# How the MCP gateway works (https://docs.corevalue.dev/features/mcp/overview)



<Callout type="info">
  The MCP gateway is available on plans that include it. See
  [Billing, plans and credits](/features/organization/billing).
</Callout>

## What it does [#what-it-does]

The MCP gateway sits between your MCP client (Claude Code, claude.ai, an
agent built on an MCP SDK, or any client that speaks streamable HTTP) and the
MCP servers your organisation registers. Each request is authenticated,
checked against your organisation's rate limits and
[access rules](/features/access/mcp-tool-policies), forwarded to the server, and logged
as a tool call you can attribute to a key, team, user or customer.

* **Authentication** with a CoreValue API key or virtual key, sent in
  `Authorization: Bearer`. Keys in the URL (`?key=`) are refused, because a URL
  ends up in access logs. Clients that can only sign in with OAuth, such as
  claude.ai, sign a member in through CoreValue instead, once client sign-in
  is turned on for the organisation on **Gateway › MCP servers**.
* **No credential forwarding.** Only `content-type`, `accept`,
  `mcp-session-id`, `mcp-protocol-version` and `last-event-id` reach the MCP
  server. Your CoreValue key never does. When a server needs its own
  credential, CoreValue adds the one stored for that server — or, for a server
  that uses OAuth, an OAuth token for that server. See
  [Servers that need their own sign-in](/features/mcp/upstream-sign-in).
* **Streaming** passes through unbuffered, including progress notifications.
* **Tool-call logging**: method, tool name, status, latency and attribution
  for every call. See [Tool-call logging and cost](/features/mcp/tool-calls).

## Endpoints [#endpoints]

Register servers on **Gateway › MCP servers** (see
[Add MCP servers](/features/mcp/servers)). Each has an id. Clients can connect to:

* `https://gateway.corevalue.dev/mcp` — one endpoint for all of your
  organisation's enabled servers. Tool names are prefixed with the server id,
  as `<server-id>__<tool>`.
* `https://gateway.corevalue.dev/mcp/<server-id>` — a single server.

For Claude Code:

```bash
claude mcp add --transport http corevalue \
  https://gateway.corevalue.dev/mcp \
  --header "Authorization: Bearer $COVA_API_KEY"
```

Any other client that accepts a header needs the same two things: the
endpoint, and the `Authorization` header. See
[Connect MCP clients](/features/mcp/connect-clients).

To attribute tool calls to an end user or a customer, send `Cova-User-Id` and
`Cova-Customer-Id` on each request, as on the LLM gateway. They are read for
logging and never forwarded to the MCP server.

To try a server with a model before connecting a client, use
**Gateway › Playground**. See [Try tools in the playground](/features/mcp/playground).

## Limits [#limits]

* **Tool calls are not priced.** Their cost is recorded as unknown, never as
  $0, and they are excluded from spend totals.
* **Streamable HTTP only.** stdio and the legacy SSE transport are not
  supported.

To decide which teams, members and keys may call which tools, see
[Access rules for tools and agents](/features/access/mcp-tool-policies).


# Try tools in the playground (https://docs.corevalue.dev/features/mcp/playground)



The playground is a chat in the console where a model can call the MCP tools
your organisation has registered. Pick a model and some tools, ask something
that needs them, and watch each tool call as it happens: its name, arguments,
result, latency and any error. It is the quickest way to check that a server
works through the gateway, and to see what an agent would see, before you set
up an MCP client.

The playground uses the MCP gateway, so it is available on plans that include
it. See [Billing, plans and credits](/features/organization/billing).

## Who can use it [#who-can-use-it]

Using the playground needs the `playground.use` permission. Owners and admins
have it; members do not, unless a custom role gives it to them. See
[Roles and permissions](/references/roles). Without it, the **Playground** tab
does not appear.

## Start a chat [#start-a-chat]

1. Go to **Gateway › Playground**.
2. Under **Setup**, choose a **Model**. The list is the models the gateway can
   reach for your organisation, through your provider keys or credits.
3. If your organisation scopes requests by team, choose a **Team**. Calls
   count against its budget.
4. Under **Servers and tools**, select the servers, or individual tools, the
   model may use.
5. Ask something that needs a tool, and click **Send**.

The model sees only the tools you selected. If it asks for any other tool, the
call is not made, and the chat says so.

You can also start from **Gateway › MCP servers**. **List tools** on a
server's row shows the tools the server offers through the gateway, and which
of them are visible to you, with the reason when one is hidden. From there,
**Open in playground** opens the playground with that server's tools selected.

## Approve each tool call [#approve-each-tool-call]

**Ask before each tool call** is on when you open the playground. Tools can
change real systems, so with this on nothing runs until you approve it. Each
tool call the model asks for appears as a card, *Awaiting approval*:

* **Approve** runs it. When the model asks for several calls at once, they run
  once every pending call is decided.
* **Skip** does not run it. The model is told it was not run.

Turn the switch off to let tool calls run as soon as the model asks for them.
**Stop** ends the current message; a tool call that was already running may
still have run.

## Limits [#limits]

Each message you send can lead to several rounds of tool calls. The playground
stops a message after:

* 8 tool rounds,
* 16 tool calls, or
* 120 seconds.

A chat can select up to 64 tools, and each member can send up to 10 messages a
minute. When a limit stops a message, the chat says which one. These limits
stop a runaway loop; they are not a spending control. Your budgets are.

## Costs and budgets [#costs-and-budgets]

The playground runs as you. Every model call goes through the gateway like
any other request, and is paid for the way your organisation pays for
requests.

* **Budgets apply.** Your member budget, your team's budget and the
  organisation's budget all count the playground's model calls, and a budget
  that is spent stops the chat with *Budget reached*. Customer budgets do not
  apply: the playground sends no customer.
* **Each model call shows its tokens and cost**, with **View request** to
  open it on **Requests**. Tool calls are listed on **Requests › MCP** and,
  like every tool call, are not priced.
* **Access rules apply.** A tool call that an access rule refuses shows
  *Refused by policy*.
* Every request and tool call from the playground carries the property
  `playground`, so you can tell them apart from your applications' traffic.

Chats are not saved. Leaving or reloading the page clears the chat. The model
calls and tool calls are logged like any others, with their bodies only if
your organisation keeps bodies.

## Not available yet [#not-available-yet]

**Servers where each member signs in** cannot be used in the playground yet.
They are listed, marked *Not available in the playground yet*. Use them from an
MCP client instead; see
[Servers that need their own sign-in](/features/mcp/upstream-sign-in).

## Related [#related]

* [How the MCP gateway works](/features/mcp/overview)
* [Access rules for tools and agents](/features/access/mcp-tool-policies)
* [Budgets and limits](/features/budgets/budgets-and-limits)


# Add MCP servers (https://docs.corevalue.dev/features/mcp/servers)







**Gateway** → **MCP servers** is where you register the MCP servers your agents use. Every server listed there is reachable through CoreValue's MCP endpoint, and every call to it is [logged](/features/mcp/tool-calls).

<img alt="The MCP servers tab" src="__img0" />

## Before you start [#before-you-start]

| Requirement    | Detail                                                                                                                                                     |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Plan**       | The MCP gateway is included on the Growth and Enterprise plans. On other plans the tab says &#x2A;"The MCP gateway is not enabled for this organisation."* |
| **Role**       | Owner or Admin (the `governance.write` permission) to add, test, edit, switch off or remove a server. Members see the list but cannot change it.           |
| **Where**      | Top bar → **Gateway** → **MCP servers** (`/gateway?tab=mcp-servers`).                                                                                      |
| **The server** | Reachable over `https` at a public address, and speaking streamable HTTP. stdio and the legacy SSE transport are not supported.                            |

## Add a server [#add-a-server]

<Steps>
  <Step>
    ### Open the dialog [#open-the-dialog]

    Under **Your servers**, click **Add server**.

        <img alt="The Add an MCP server dialog" src="__img1" />
  </Step>

  <Step>
    ### Fill in the server [#fill-in-the-server]

    | Field              | What it does                                                                                                                                                                             |
    | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Server ID**      | 1 to 64 lowercase letters, digits or hyphens, for example `github-tools`. Used in tool names (`<server-id>__<tool>`) and in the server's own endpoint. &#x2A;*Cannot be changed later.** |
    | **Name**           | Shown across the console.                                                                                                                                                                |
    | **URL**            | The server's MCP endpoint, for example `https://mcp.example.com/mcp`.                                                                                                                    |
    | **Tools**          | **All tools**, or **Only these**: one tool name per line. Tools not listed are hidden from agents and refused if called. An empty list allows none.                                      |
    | **Authentication** | How CoreValue signs in to the server: **None**, **Header / API key**, **OAuth — service account** or **OAuth — each member signs in**.                                                   |

    For **Header / API key**, paste the secret. It is sent as `Authorization: Bearer <secret>` unless you name another **Header** and **Prefix**. The two OAuth choices are set up on [Servers that need their own sign-in](/features/mcp/upstream-sign-in).

    If **Add server** stays disabled, the line beside it says what is still needed, for example &#x2A;To add the server: enter a name; enter the URL.*
  </Step>

  <Step>
    ### Save and test [#save-and-test]

    Click **Add server*&#x2A;. You will see &#x2A;"Server added"*. Then click **Test** on the server's row. CoreValue connects to the server with its credential and reports, for example, &#x2A;Connected to … in 120 ms. 4 tools: search, get\_issue, …* — including which tools your **Only these** list hides. If it cannot connect, Test shows the server's error.
  </Step>
</Steps>

The URL is checked when you save and again on every request: CoreValue only connects over `https`, and only to an address on the public internet. A credential is stored encrypted and is never shown again; the server row shows only which header it is sent in, or *no credential*.

## Connect agents to it [#connect-agents-to-it]

The **Connect an agent** box at the top of the tab gives the endpoint for your organisation:

* `https://gateway.corevalue.dev/mcp` — every enabled server at once, each tool named `<server-id>__<tool>`.
* `https://gateway.corevalue.dev/mcp/<server-id>` — one server alone.

Clients authenticate with a CoreValue API key as `Authorization: Bearer`, or by signing in. Your CoreValue key is never forwarded to the server. See [Connect MCP clients](/features/mcp/connect-clients).

## Change, switch off or remove a server [#change-switch-off-or-remove-a-server]

On the server's row:

* **The switch** turns the server off and on. An off server is marked **Off**, and its tools disappear from agents.
* **Edit** changes the name, URL, tools and credential. For a stored header credential, choose **Keep** or **Replace**. The server ID cannot change.
* **Remove** asks &#x2A;Remove …?*. Agents lose its tools, its stored credential is deleted, and its past calls stay in **MCP tool calls**.

Every change reaches the gateway within a minute; agents need no reconfiguring.

## Platform servers [#platform-servers]

Below your own servers, **Platform servers** lists servers CoreValue provides with your plan. You cannot edit them, but you can switch one off for your organisation, or click **Limit tools** to allow only some of its tools.

## Who can call which tools [#who-can-call-which-tools]

Every enabled server's tools are open to every key in the organisation until you narrow them. Two controls narrow them:

* **Tools** on the server (above) applies to everyone.
* [Access rules](/features/access/mcp-tool-policies) decide which teams, members and keys may call each server or tool, with an audit mode to try a rule first.

## Troubleshooting [#troubleshooting]

<Accordions>
  <Accordion title="The server ID is refused">
    Only lowercase letters, digits and hyphens are allowed, up to 64 characters. Underscores are not: `<server-id>__<tool>` uses a double underscore to separate the two.
  </Accordion>

  <Accordion title="Test says it cannot connect">
    Check that the URL is the MCP endpoint itself (often ending in `/mcp`), that it is served over `https` from a public address, and that the credential and its header are what the server expects.
  </Accordion>

  <Accordion title="An agent doesn't see a tool">
    Check that the server's switch is on, that the tool is not hidden by **Only these**, and that no enforced [access rule](/features/access/mcp-tool-policies) denies it for that caller. Allow up to a minute after a change.
  </Accordion>

  <Accordion title="Add server is greyed out">
    Adding servers needs the Owner or Admin role; hover the button for the reason. If the tab shows no servers list at all, the MCP gateway is not on your organisation's plan.
  </Accordion>
</Accordions>

## Related [#related]

* [How the MCP gateway works](/features/mcp/overview)
* [Servers that need their own sign-in](/features/mcp/upstream-sign-in)
* [Tool-call logging and cost](/features/mcp/tool-calls)
* [Try tools in the playground](/features/mcp/playground)


# Tool-call logging and cost (https://docs.corevalue.dev/features/mcp/tool-calls)





Every request an authenticated MCP client sends through CoreValue's MCP gateway is recorded as a row, whether the server answered it, it failed, or an access rule refused it. **Requests** → **MCP tool calls** is where you read them.

<img alt="The MCP tool calls page" src="__img0" />

## Before you start [#before-you-start]

| Requirement       | Detail                                                                                    |
| ----------------- | ----------------------------------------------------------------------------------------- |
| **Plan**          | The MCP gateway is included on the Growth and Enterprise plans.                           |
| **Where**         | Top bar → **Requests**, then **MCP tool calls** at the top of the page (`/requests/mcp`). |
| **Who sees what** | The same [request visibility](/features/access/request-visibility) as LLM requests.       |

## What is recorded for each call [#what-is-recorded-for-each-call]

| Recorded                | Detail                                                                                                                                                                                                                                  |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Server** and **tool** | The server ID and name, the JSON-RPC method (`tools/call`, `tools/list`, `initialize`, …) and, for a tool call, the tool's own name.                                                                                                    |
| **Who**                 | The CoreValue API key or virtual key that made it, and that key's team. A call from a client that signed in with OAuth is marked with the client and member, for example &#x2A;via Claude · [ana@example.com](mailto:ana@example.com)*. |
| **Your attribution**    | `Cova-User-Id`, `Cova-Customer-Id` and `Cova-Property-*` headers on the MCP request. They are recorded, never forwarded to the server.                                                                                                  |
| **Outcome**             | The HTTP status the server answered with, or *no response* when the call ended without one. The latency, and the size of the request and response.                                                                                      |
| **Policy**              | What an [access rule](/features/access/mcp-tool-policies) or the [PII policy](/features/access/guardrails) did to the call, and which rule.                                                                                             |
| **Cost**                | Not priced — see [Cost](#cost) below.                                                                                                                                                                                                   |

## Read the page [#read-the-page]

The page covers the time range chosen in its header, in three parts.

**Servers.** One card per server that had traffic: **Calls**, **Error rate** (shown in red at 5% or more) and **p95** latency, with the number of discovery and handshake requests underneath. Click a card to narrow the table to that server; click it again to clear it.

**Most-called tools.** The ten tools with the most calls, with their server, calls, error rate and p95.

**Tool calls.** The rows themselves, newest first, 50 to a page. The tabs choose the kind of request:

| Tab                 | Shows                                                                   |
| ------------------- | ----------------------------------------------------------------------- |
| **Calls** (default) | Tool calls                                                              |
| **Discovery**       | `tools/list`, `resources/list` and `prompts/list`                       |
| **Handshake**       | `initialize`, `notifications/initialized`, `ping` and `server/discover` |
| **All**             | Everything                                                              |

Narrow it further with the four filters, which offer only values seen in the range:

| Filter      | Choices                                                                                                        |
| ----------- | -------------------------------------------------------------------------------------------------------------- |
| **Tool**    | Any tool name; narrowed to one server's tools when a server card is selected                                   |
| **Outcome** | **OK**, &#x2A;*Error (4xx/5xx)**, **No response**                                                              |
| **API key** | One API key. Virtual keys and OAuth sign-ins cannot be picked here, but are included under **Any key or app**. |
| **Policy**  | **Allowed by a rule**, &#x2A;*Would be refused (audit)**, **Refused**                                          |

The **Policy** column reads *allowed*, *would deny* or *refused* for an access rule, and *PII redacted*, *PII flagged* or *PII refused* for the PII policy; hover it for the rule. A refused call stays on the page, so you can see what a rule blocked. The [Access rules](/features/access/mcp-tool-policies) list links here with the matching policy filter already set.

### How the numbers are counted [#how-the-numbers-are-counted]

* **Errors and latency count tool calls only.** Discovery and handshake requests are listed but never counted, so a client's connection probe that a server answers with `400` does not inflate the error rate.
* **A tool failure inside HTTP 200 is not visible yet.** MCP servers often report a failed tool as a successful HTTP response with an error in its body; such a call shows as OK.
* **Latency starts after CoreValue authenticates the caller,** so the first call in a session can feel slower than its row says.

## Cost [#cost]

**MCP tool calls are not priced.** The **Cost*&#x2A; column shows &#x2A;—*, never $0, and tool calls are left out of every dollar total, budget and report in the console. A dash means *not known*, not *free*.

On **Budgets & Limits** → **Tool rates**, owners and admins can already declare what one call to a tool costs your organisation — a flat rate per call in US dollars, with the **Source** of the number — for a single tool or every tool on a server. These rates are your own figures, never a charge from CoreValue.

<Callout type="info">
  Tool rates are stored but not applied yet. As the tab says: &#x2A;no call is priced from them and no total, budget or report changes.&#x2A; Rows on this page keep showing &#x2A;—* until they are.
</Callout>

## Related [#related]

* [Add MCP servers](/features/mcp/servers)
* [Access rules for tools and agents](/features/access/mcp-tool-policies)
* [How the MCP gateway works](/features/mcp/overview)


# Servers that need their own sign-in (https://docs.corevalue.dev/features/mcp/upstream-sign-in)



Many MCP servers need a credential of their own: an API key, or an OAuth
sign-in with the service behind them. CoreValue holds that credential and adds
it to each request it forwards. Your MCP clients never see it, and the
CoreValue key or sign-in a client uses is never forwarded to the server.

This page is about how the **gateway signs in to a server**. How your
**clients sign in to the gateway** is a separate setting, described in
[Connect MCP clients](/features/mcp/connect-clients).

## Choose how the server authenticates [#choose-how-the-server-authenticates]

When you add or edit a server on **Gateway › MCP servers**, the
**Authentication** field has four choices:

| Choice                           | What it does                                                                                                  | Use it when                                                                                                          |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| **None**                         | The server needs no sign-in.                                                                                  | The server is open, or reachable only from where you run it.                                                         |
| **Header / API key**             | One secret you paste, sent in a header. Every member uses it.                                                 | The server takes a static API key or token.                                                                          |
| **OAuth — service account**      | CoreValue signs in as one app identity and refreshes its token. Every member uses it.                         | The server supports machine-to-machine OAuth, and every caller should act as the same identity there.                |
| **OAuth — each member signs in** | Each member connects their own account the first time they use the server. Calls act as that member upstream. | Calls must act as the person making them, with their own permissions at the service — a code host, an issue tracker. |

With **Header / API key**, the secret is sent as
`Authorization: Bearer <secret>` unless you name another header and prefix.
It is stored encrypted and never shown again.

With **OAuth — each member signs in**, only members who connect their MCP
client by [signing in](/features/mcp/connect-clients#connect-by-signing-in) can use
the server, because only a sign-in tells CoreValue which member is calling. A
client that connects with an API key does not see the server's tools, and a
call to it is refused with a message saying to connect with CoreValue sign-in.
Turn on **Client sign-in** first; the dialog warns you when it is off.

## Add an OAuth server [#add-an-oauth-server]

Owners and admins add servers. Each server needs a **Server ID**: 1 to 64
lowercase letters, digits or hyphens. It is used in tool names
(`<server-id>__<tool>`) and in the server's URL, and cannot be changed later.

<Steps>
  <Step>
    ### Enter the server and choose OAuth [#enter-the-server-and-choose-oauth]

    Go to **Gateway › MCP servers** and click **Add server**. Enter the
    **Server ID**, a **Name** and the server's **URL**, then choose **OAuth —
    service account** or **OAuth — each member signs in** under
    **Authentication**.
  </Step>

  <Step>
    ### Find the sign-in settings [#find-the-sign-in-settings]

    Click **Find sign-in settings**. CoreValue asks the server where its members
    sign in and how CoreValue can register with it, and shows what it found under
    **Sign-in settings found**:

    * **Members sign in at** (or, for a service account, **CoreValue signs in
      at**): the authorization server's host.
    * **Client registration**: how CoreValue identifies itself there.
      * *Automatic*: CoreValue identifies itself with its client metadata
        document, or registers itself with the server. Nothing more to do.
      * *This provider offers no automatic registration*: you register CoreValue
        with the provider yourself, and enter the client ID and secret it gives
        you. See [the GitHub example](#example-githubs-mcp-server) below.
    * **Disconnecting**: whether CoreValue can revoke access at the provider when
      a member disconnects.
    * **Scopes**: what the server asks for. Space-separated; leave them as found
      to use what the server asks for.

    If the server does not say where its members sign in, the dialog asks for
    the &#x2A;*Authorization server (issuer)**: the provider's issuer URL, for example
    `https://auth.example.com`. Enter it and look again.

    You cannot save an OAuth server until its sign-in settings have been found
    for the URL and choice you are saving.
  </Step>

  <Step>
    ### Enter your own client, if needed [#enter-your-own-client-if-needed]

    Tick **Use a client I registered with the provider** to use a client you
    registered yourself, even where automatic registration is offered. It is
    ticked for you when the provider offers none. Enter the **Client ID** and
    **Client secret**. The secret is stored encrypted and never shown again; when
    you edit the server later, leave it empty to keep it.

    A service account always needs a client secret.
  </Step>

  <Step>
    ### Save, then test [#save-then-test]

    Click **Add server**, then **Test** on the server's row. What Test does
    depends on the choice:

    * **OAuth — each member signs in**: Test uses **your own** connection to the
      server. If you have not connected yet, it says &#x2A;Each member signs in to this
      server. Connect your account to test it.*, and sends nothing. Connect from
      **Connected accounts** (below), then test again. If the server rejects your
      connection, Test says so and asks you to reconnect.
    * **OAuth — service account**: Test uses the service account's token, and
      says so if the server rejects it.
    * **Header / API key** and **None**: Test sends the stored credential, if any.
  </Step>
</Steps>

### Example: GitHub's MCP server [#example-githubs-mcp-server]

GitHub's MCP server is at `https://api.githubcopilot.com/mcp/`. GitHub offers
no automatic client registration, so you register CoreValue with GitHub first:

1. In GitHub, create an **OAuth app** (under your organisation's or your
   account's developer settings). Set its **Authorization callback URL** to:

   ```
   https://api.corevalue.dev/v1/public/mcp/upstream/callback
   ```

2. Copy the app's client ID, and generate a client secret.

3. In CoreValue, add the server with a **Server ID** such as `github`, the
   URL above, and **OAuth — each member signs in**.

4. Click **Find sign-in settings**. The dialog reports that the provider
   offers no automatic registration, and that GitHub offers no revocation.

5. Enter the GitHub app's **Client ID** and **Client secret**, check the
   **Scopes**, and click **Add server**.

Each member then connects their own GitHub account, and their tool calls act
as them on GitHub.

Use the same callback URL for any other provider where you register CoreValue
yourself.

## Connect your account [#connect-your-account]

On a server where each member signs in, every member connects their own
account once. There are three ways to do it, and all end on the same pages.

**From Connected accounts.** **Gateway › MCP servers › Connected accounts**
lists, under **Servers you can connect**, the servers you have not connected
yet. Click **Connect**.

**From the MCP client.** Until you connect, a client on
`https://gateway.corevalue.dev/mcp` sees one tool from the server,
`<server-id>__connect` (*Connect your* server &#x2A;account to use its tools.*).
Calling it, or any of the server's tools, returns a connect link. A client on
`https://gateway.corevalue.dev/mcp/<server-id>` has its requests refused, with
the connect link in the error message. Open the link in your browser.

**When you add the client.** A client added at
`https://gateway.corevalue.dev/mcp/<server-id>` that signs in to CoreValue
also signs you in to the server in the same step: the consent screen says
*You will also sign in at* the provider's host. If that sign-in fails or you
cancel it, the client is still connected to CoreValue, and you connect the
server later from a link.

The connect page, &#x2A;*Connect *server* as *your email***, names the host you
will sign in at. Click **Continue**, sign in there and grant access. CoreValue
stores the access it grants, encrypted, and uses it only for your calls to
that server. When the page says the server is connected, return to your MCP
client and try again.

A connect link works once, for ten minutes, and only for the CoreValue account
it was made for. If it has expired or was already used, ask your MCP client
for a new one or reconnect from **Connected accounts**. If you are signed in
as someone else, the page says the link belongs to another account.

## Connected accounts [#connected-accounts]

**Gateway › MCP servers › Connected accounts** shows, under **Your accounts**,
each server you have connected: where you signed in, the scopes granted, when
you connected and last used it, and its **Status**.

* **Needs reconnecting** means the provider stopped accepting the connection —
  for example, you removed CoreValue's access there. Click **Reconnect** to
  sign in again. CoreValue renews access in the background otherwise, so you
  do not reconnect when a token expires.
* **Disconnect** stops your calls to that server at once. Where the provider
  supports it, CoreValue also asks the provider to revoke its access. Where it
  does not — GitHub is one — the dialog says so: also remove CoreValue's
  access in your account settings at the provider. On GitHub, that is the
  OAuth app in your authorized applications.

Owners and admins also see &#x2A;*All connected accounts in *organisation***, with
each member's server connections, and can disconnect any of them.

### When connections end on their own [#when-connections-end-on-their-own]

* **The server changes.** Changing a server's URL, authorization server,
  client or authentication choice ends every member's connection to it,
  because their access was granted for the old settings. The dialog warns you
  before you save. Members connect again
  the next time they use the server. Changing only the scopes, the name or the
  allowed tools keeps connections.
* **The server is removed.** Its connections end with it.
* **A member leaves the organisation.** Their connections can no longer be
  used.

## Related [#related]

* [Connect MCP clients](/features/mcp/connect-clients): API keys, client sign-in and
  Connected clients.
* [Add MCP servers](/features/mcp/servers)
* [Tool-call logging and cost](/features/mcp/tool-calls)


# Caching (https://docs.corevalue.dev/features/routing/caching)



# Response Caching [#response-caching]

Cache responses to reduce cost and latency for repeated requests. Cache is opt-in per request via `Cova-Cache-*` headers.

<Callout type="info">
  **Tier:**

   Available on all tiers.
</Callout>

<Callout type="warning">
  **Semantic cache deferred:** The exact-match cache is shipped. Semantic cache
  (vector similarity) is planned behind `SEMANTIC_CACHE_ENABLED` — the flag
  exists in `env.ts` but is not yet wired into the cache middleware.
</Callout>

## How It Works [#how-it-works]

When cache is enabled, the gateway:

1. Computes a cache key from the request body (excluding ignored keys)
2. Checks the cache bucket for a matching response
3. On hit: returns the cached response with `Cova-Cache: HIT` header
4. On miss: forwards to upstream, saves the response to the cache bucket

## Cache Request Headers [#cache-request-headers]

| Header                       | Purpose                          | Default    |
| ---------------------------- | -------------------------------- | ---------- |
| `Cova-Cache-Enabled`         | Enable read + write              | —          |
| `Cova-Cache-Save`            | Enable write only                | —          |
| `Cova-Cache-Read`            | Enable read only                 | —          |
| `Cova-Cache-Bucket-Max-Size` | Max cached responses per key     | 1 (max 20) |
| `Cova-Cache-Seed`            | Cache isolation seed             | —          |
| `Cova-Cache-Ignore-Keys`     | Body keys to ignore in cache key | —          |

## Usage [#usage]

### Read + Write (Default) [#read--write-default]

```bash
curl https://gateway.corevalue.dev/v1/chat/completions \
  -H "Authorization: Bearer sk-cova-..." \
  -H "Content-Type: application/json" \
  -H "Cova-Cache-Enabled: true" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"What is 2+2?"}]}'
```

### Write Only (Seed Cache) [#write-only-seed-cache]

```bash
curl https://gateway.corevalue.dev/v1/chat/completions \
  -H "Authorization: Bearer sk-cova-..." \
  -H "Cova-Cache-Save: true" \
  -d '{"model":"gpt-4o","messages":[...]}'
```

### Read Only (No Saving) [#read-only-no-saving]

```bash
curl https://gateway.corevalue.dev/v1/chat/completions \
  -H "Authorization: Bearer sk-cova-..." \
  -H "Cova-Cache-Read: true" \
  -d '{"model":"gpt-4o","messages":[...]}'
```

## Cache Response Headers [#cache-response-headers]

On a cache hit, the gateway sets:

| Header                  | Purpose                      |
| ----------------------- | ---------------------------- |
| `Cova-Cache`            | "HIT"                        |
| `Cova-Cache-Bucket-Idx` | Cache bucket index           |
| `Cova-Cache-Latency`    | Cached response latency (ms) |

## Escrow Interaction [#escrow-interaction]

When `ESCROW_ENABLED` is true, the gateway still consults the request cache after reserving escrow. On a cache hit, the escrow hold is **cancelled** (a cached response incurs no PTB cost), so the user is not charged for a cached answer. The request cache itself is never bypassed by escrow.

<Callout>
  **Related:** Escrow for reserve-before-execute
  behavior. [Cova-\* Headers Guide](/api-reference/headers) for
  all headers.
</Callout>


# Fallbacks (https://docs.corevalue.dev/features/routing/fallbacks)



When your primary model fails, the gateway can send the request to the next model in a list you specify, until one succeeds or the list runs out.

## How It Works [#how-it-works]

Set the `Cova-Fallbacks` request header to a JSON array of model names, ordered from first fallback to last. When the primary model still fails after its retries, with status 401, 402, 403, 429, 500, 502, 503, 504 or 529, the gateway:

1. **Rewrites the model** to the next entry in the fallback chain
2. **Re-resolves the provider key** for the new model (BYOK first, then PTB)
3. **Re-runs the wallet check** if the new model will be served by a PTB key
4. **Forwards the retried request** upstream

This repeats for each entry in the chain until one succeeds or the chain is exhausted.

<Callout type="info">
  To fall back to a **different provider**, use the base URL `https://gateway.corevalue.dev/gateway/v1` and prefixed model names, for example `["anthropic/claude-sonnet-4-5-20250929", "groq/llama-3.3-70b-versatile"]`. On a provider's own endpoint, such as `/v1/messages`, every fallback model goes to that same provider.
</Callout>

## Header Format [#header-format]

`Cova-Fallbacks` is a JSON array of model name strings.

| Rule           | Detail                                                                         |
| -------------- | ------------------------------------------------------------------------------ |
| **Type**       | Valid JSON array of strings                                                    |
| **Max length** | 5 models                                                                       |
| **Non-empty**  | Array must contain at least one entry                                          |
| **Each entry** | A non-empty model name string                                                  |
| **Invalid**    | Invalid JSON, empty array, or > 5 entries → header ignored (no fallback chain) |

### Example [#example]

```http
Cova-Fallbacks: ["gpt-4o","gpt-4o-mini"]
```

## Using the SDK [#using-the-sdk]

The `@cova/gateway` SDK exposes a `buildHeaders()` helper that constructs and validates `Cova-*` headers for you. Invalid input throws rather than being silently dropped.

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { buildHeaders } from "@cova/gateway";

    const headers = buildHeaders({
      fallbacks: ["gpt-4o", "gpt-4o-mini"],
    });

    const response = await fetch("https://gateway.corevalue.dev/v1/chat/completions", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.COVA_API_KEY}`,
        "Content-Type": "application/json",
        ...headers,
      },
      body: JSON.stringify({
        model: "groq/llama-3.3-70b-versatile",
        messages: [{ role: "user", content: "Hello!" }],
      }),
    });
    ```
  </Tab>

  <Tab>
    ```python
    from cova_gateway import build_headers

    headers = build_headers(
        fallbacks=["gpt-4o", "gpt-4o-mini"],
    )

    response = requests.post(
        "https://gateway.corevalue.dev/v1/chat/completions",
        headers={
            "Authorization": f"Bearer {os.environ['COVA_API_KEY']}",
            "Content-Type": "application/json",
            **headers,
        },
        json={
            "model": "groq/llama-3.3-70b-versatile",
            "messages": [{"role": "user", "content": "Hello!"}],
        },
    )
    ```
  </Tab>
</Tabs>

## Example Scenario [#example-scenario]

Your primary model is a fast, cheap Groq model. If Groq is down or rate-limited, fall back to Claude Sonnet 4.5, then to Llama on Together. Prefixed models need the `/gateway/v1` base URL:

```http
POST /gateway/v1/chat/completions HTTP/1.1
Host: gateway.corevalue.dev
Authorization: Bearer $COVA_API_KEY
Content-Type: application/json
Cova-Fallbacks: ["anthropic/claude-sonnet-4-5-20250929","together/meta-llama/Llama-3.3-70B-Instruct-Turbo"]

{
  "model": "groq/llama-3.3-70b-versatile",
  "messages": [{ "role": "user", "content": "Summarize this article." }]
}
```

**What happens:** The gateway tries `groq/llama-3.3-70b-versatile` first, with its retries. If it still fails, it rewrites the model to `anthropic/claude-sonnet-4-5-20250929`, resolves your Anthropic key, and sends it there; then the Together model. The `Cova-Model` response header reflects whichever model actually served the request.

## Response Headers [#response-headers]

After a fallback succeeds, the response headers tell you which model ended up serving the request:

| Header          | Meaning                                                                          |
| --------------- | -------------------------------------------------------------------------------- |
| `Cova-Model`    | The model that actually served the request (may differ from the requested model) |
| `Cova-Provider` | The provider that served the request                                             |
| `Cova-Status`   | `success` or `error`                                                             |

## Related [#related]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Provider Routing" href="/features/routing/other-providers">
    Fail over across providers for the same model
  </Card>

  <Card title="Request Pipeline" href="#">
    Where fallback resolution fits in the 12-step pipeline
  </Card>
</div>


# Several keys for one provider (https://docs.corevalue.dev/features/routing/key-pool)



You can add more than one key for the same provider under **Gateway → Provider keys**. Today the gateway uses the first enabled key for every request; spreading requests across several keys, and moving to another key when one is rate-limited, is not built yet.

To keep requests working when a provider or key fails, use [retries and fallbacks](/guides/provider-failover).


# Route by model name (https://docs.corevalue.dev/features/routing/model-routing)



With the OpenAI SDK pointed at `https://gateway.corevalue.dev/v1`, a request names only a model, so it would normally go to OpenAI. **Routing by model name** lets the same request reach the provider that actually serves that model: send `claude-sonnet-4-5-20250929` to `/v1/chat/completions`, and if your organisation holds an Anthropic or Bedrock key, CoreValue sends it there and returns an OpenAI-format reply.

It is on for every organisation. There is nothing to configure.

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://gateway.corevalue.dev/v1",
    api_key=os.environ["COVA_API_KEY"],
)

# No prefix, no second SDK: routed to a provider you hold a key for.
response = client.chat.completions.create(
    model="claude-sonnet-4-5-20250929",
    messages=[{"role": "user", "content": "Hello"}],
)
```

## How the provider is chosen [#how-the-provider-is-chosen]

For each eligible request, CoreValue:

1. Looks the model up in its [price registry](/additional/supported-models#the-price-registry). It accepts the registry name (as [`GET /v1/models`](/api-reference/list-models) lists it) or the provider's own id for the model.
2. Keeps the providers that serve that model **and** that your organisation can reach: you have added a key for it on **Gateway** → **Provider keys**.
3. Picks the one with the lowest list price for that model.

If the chosen provider is OpenAI, nothing changes. Otherwise the request is rewritten as if you had sent it to `/gateway/v1` with that provider's prefix, for example `anthropic/claude-sonnet-4-5-20250929`, and is translated and mapped back exactly as a prefixed request would be.

The request is left on the OpenAI path, unchanged, when:

* the registry does not know the model,
* no provider you hold a key for serves it, or
* the cheapest one you hold a key for is one this route cannot address (Vertex AI).

A request left on the OpenAI path is sent to OpenAI. Without an OpenAI key, it fails with *No openai provider key available*.

## What it applies to [#what-it-applies-to]

Only `POST /v1/chat/completions` in OpenAI format. These are never rerouted:

* the Responses API (`/v1/responses`), embeddings, images, audio and the rest of the OpenAI API;
* requests to `/gateway/v1`, where you have already named the provider with a prefix;
* requests to a provider's own endpoint, such as `/v1/messages` (Anthropic), `:generateContent` (Gemini, Vertex AI) or `/model/{id}/invoke` (Bedrock).

## It is not failover [#it-is-not-failover]

Routing chooses **one** provider before the request is sent. If that provider then fails, CoreValue retries the same provider and does not try another one that serves the model. To fall over to another provider or model, add a [fallback chain](/features/routing/fallbacks) with `Cova-Fallbacks`, naming each fallback with its provider prefix, for example `["groq/llama-3.3-70b-versatile"]`.

## See where a request went [#see-where-a-request-went]

The response says which provider served it:

| Header              | Routed request                                                                                                  |
| ------------------- | --------------------------------------------------------------------------------------------------------------- |
| `Cova-Provider`     | The provider chosen, for example `anthropic` or `bedrock`                                                       |
| `Cova-Model`        | The model as sent to the provider, with the provider prefix, for example `anthropic/claude-sonnet-4-5-20250929` |
| `Cova-Gateway-Mode` | `translated` when the request went to Anthropic, Gemini or Bedrock                                              |

See [Response headers](/api-reference/response-headers). On **Requests**, the request is recorded under the provider that served it.

## With key scopes and budgets [#with-key-scopes-and-budgets]

* A key's **Models** [scope](/features/access/key-scopes) is checked against the model you asked for, before routing. Its **Providers** scope is checked against the provider routing chose, so a key limited to Anthropic is refused if the request would be routed to Bedrock.
* [Budgets](/features/budgets/budgets-and-limits) and key caps are charged at the price of the provider that served the request.

## Related [#related]

* [Supported providers and models](/additional/supported-models) — every provider, prefix and native endpoint
* [Call other providers](/features/routing/other-providers) — naming the provider yourself with a prefix
* [Fallbacks](/features/routing/fallbacks) — trying another model when one fails


# Call other providers (https://docs.corevalue.dev/features/routing/other-providers)



Never worry about provider outages again. The AI Gateway automatically routes your requests to the best available provider, with instant failover when things go wrong.

## The Problem [#the-problem]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Provider Outages">
    Provider downtime breaks your app and frustrates users
  </Card>

  <Card title="Rate Limits">
    Hit provider quotas and block your users from accessing your service
  </Card>

  <Card title="Regional Restrictions">
    Limited availability in certain regions reduces your global reach
  </Card>

  <Card title="Vendor Lock-in">
    Tied to one provider prevents cost optimization and flexibility
  </Card>
</div>

## The Solution [#the-solution]

Provider routing gives you access to models across multiple providers through a single OpenAI-compatible API. When you hit rate limits or a provider has issues, the gateway automatically retries with exponential backoff and can fall back to alternative models you configure. All without setup or code changes.

## Using Provider Routing [#using-provider-routing]

Zero configuration required for OpenAI models. Just request a model:

```typescript
const response = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Hello!" }]
});
```

That's it. The gateway automatically:

* Routes to the correct provider based on the model prefix
* Retries transient failures with exponential backoff
* Falls back to alternative models if you configure a fallback chain

Your request succeeds even when providers fail.

## How It Works [#how-it-works]

The gateway routes requests based on model name prefixes. Models without a prefix are routed to OpenAI. Models with a provider prefix (e.g., `groq/llama-3.3-70b-versatile`) are routed to the corresponding provider.

**Routing Priority:**

1. Your provider keys (BYOK) if configured
2. CoreValue's managed keys (credits) - automatic fallback at 0% markup

**Retries:** The gateway automatically retries transient failures (429, 500, 502, 503, 504, 529) with exponential backoff and jitter. Retry behavior is configured via environment variables on the gateway, not per-request headers.

**Failover:** Use [fallback chains](/features/routing/fallbacks) (`Cova-Fallbacks` header) to fail over to different models when the primary model returns a retryable error.

<Accordions>
  <Accordion title="What are credits?">
    Credits let you access LLM providers without signing up for each one. Add funds to your CoreValue account and we manage all the provider API keys for you. You pay exactly what providers charge (0% markup) and avoid provider rate limits. [Learn more about credits](https://corevalue.dev/credits).
  </Accordion>
</Accordions>

## Advanced: Customizing Routing [#advanced-customizing-routing]

The default routing handles most use cases. Customize only if you need specific control:

### Route to a Specific Provider [#route-to-a-specific-provider]

Force requests to a specific provider by prefixing the model name with the provider prefix:

```typescript
model: "groq/llama-3.3-70b-versatile"  // Route through Groq
```

**When to use:** You want to use a specific provider for cost, latency, or feature reasons.

**What happens:** The gateway strips the prefix and forwards the request to that provider's base URL. The provider is determined by the prefix — see the [Provider Prefixes](#provider-prefixes) table below.

### Bring Your Own Keys (BYOK) [#bring-your-own-keys-byok]

Add your provider API keys in [Provider keys](https://app.corevalue.dev/gateway?tab=provider-keys):

**What happens:** Your keys are always tried first, then CoreValue's managed keys as fallback. This gives you control over provider accounts while maintaining reliability.

**Benefits:** Use provider credits, meet compliance requirements, or maintain direct provider relationships while still getting automatic failover.

<Callout type="info">
  The gateway forwards **any** model/provider combination, even models not yet in our registry. Unknown models only route through your BYOK keys.
</Callout>

## Retry Triggers [#retry-triggers]

The gateway automatically retries when encountering these transient errors:

| Error | Description            |
| ----- | ---------------------- |
| 429   | Rate limit errors      |
| 500   | Internal server errors |
| 502   | Bad gateway            |
| 503   | Service unavailable    |
| 504   | Gateway timeout        |
| 529   | Overloaded             |

Retries use exponential backoff with jitter and respect the provider's `Retry-After` header. The `X-Cova-Retry-Count` response header indicates how many retries were attempted.

## Provider Prefixes [#provider-prefixes]

The gateway recognizes 19 model prefixes. When a request model starts with one of these prefixes, the prefix is stripped and the request is forwarded to the corresponding provider's base URL.

<Callout type="warning">
  Prefixes are honoured only on the `/gateway/v1` base URL: `https://gateway.corevalue.dev/gateway/v1`. On the plain `/v1` base URL, a prefixed model such as `groq/llama-3.3-70b-versatile` is not routed by its prefix; the request goes to OpenAI.
</Callout>

| Prefix        | Provider                                            |
| ------------- | --------------------------------------------------- |
| `together/`   | together                                            |
| `groq/`       | groq                                                |
| `openrouter/` | openrouter                                          |
| `fireworks/`  | fireworks                                           |
| `mistral/`    | mistral                                             |
| `perplexity/` | perplexity                                          |
| `deepseek/`   | deepseek                                            |
| `cerebras/`   | cerebras                                            |
| `deepinfra/`  | deepinfra                                           |
| `novita/`     | novita                                              |
| `nebius/`     | nebius                                              |
| `baseten/`    | baseten                                             |
| `chutes/`     | chutes                                              |
| `xai/`        | xai                                                 |
| `canopywave/` | canopywave                                          |
| `vercel/`     | Vercel AI Gateway (see [below](#vercel-ai-gateway)) |
| `anthropic/`  | anthropic (native — cross-provider translation)     |
| `google/`     | google (native — cross-provider translation)        |
| `bedrock/`    | bedrock (native — cross-provider translation)       |

<Callout type="info">
  The `anthropic/`, `google/`, and `bedrock/` prefixes route to native provider formats. When cross-provider translation is enabled, the OpenAI-format request body is translated to the native format before forwarding.
</Callout>

<Callout type="warning">
  Requests that don't match a known provider prefix are routed to OpenAI (for OpenAI-format requests) or the matching native handler (for Anthropic, Google, or Bedrock-format requests). The `GATEWAY_TARGET` env var is intentionally not used as a fallback — this prevents SSRF attacks.
</Callout>

## Vercel AI Gateway [#vercel-ai-gateway]

If you already use [Vercel AI Gateway](https://vercel.com/docs/ai-gateway), put CoreValue in front of it with your Vercel key; you do not need an account with each model provider.

1. Add your Vercel AI Gateway API key in [Provider keys](https://app.corevalue.dev/gateway?tab=provider-keys) as **Vercel AI Gateway**.
2. Point your client at `https://gateway.corevalue.dev/gateway/v1` and prefix Vercel's model id with `vercel/`:

```typescript
const client = new OpenAI({
  apiKey: process.env.COVA_API_KEY,
  baseURL: "https://gateway.corevalue.dev/gateway/v1",
});

const response = await client.chat.completions.create({
  model: "vercel/openai/gpt-4o-mini", // Vercel's id is openai/gpt-4o-mini
  messages: [{ role: "user", content: "Hello!" }],
});
```

Your Vercel account is billed as before. CoreValue records each request at the model's list price on its author's own API, the price Vercel states it charges, so `vercel/openai/gpt-4o-mini` costs what `gpt-4o-mini` costs on OpenAI. Budgets, key caps and attribution apply as for any provider. Requests through `vercel/` use your own Vercel key only.

## Models the gateway cannot price [#models-the-gateway-cannot-price]

Every spend control — budget gates, per-key caps, reservations, escrow — runs on
an estimate of what the request will cost, and every cost you see is what the
gateway priced the response at. A model id that nothing prices would pass all
of them having reserved nothing and be recorded at $0.

The gateway refuses such a request instead, before it is sent, with **HTTP
400** in your provider's error format:

```
Model "not-a-model" on groq cannot be priced by this gateway and was not sent.
List priceable models at GET /v1/models.
```

Every model `GET /v1/models` returns is one the gateway prices. Provider
prefixes count as routing, so `openrouter/anthropic/claude-3.5-sonnet` is
priced under OpenRouter's rates for that id.

In a fallback chain, a candidate that cannot be priced is skipped rather than
attempted, and the chain continues with the next one.

Operators can run this gate in a shadow mode that logs what it would refuse
and admits the request; see `UNPRICED_MODEL_GATE_MODE` in the gateway
environment reference.

## Fallback Chains [#fallback-chains]

To fail over to another model, or to another provider by its prefix, use [fallback chains](/features/routing/fallbacks) with the `Cova-Fallbacks` header.

## Real World Examples [#real-world-examples]

### Scenario: Provider Outage [#scenario-provider-outage]

Your production app uses a Groq model. Groq goes down at 3am.

```typescript
// Your code doesn't change — the gateway retries automatically
const response = await client.chat.completions.create({
  model: "groq/llama-3.3-70b-versatile",
  messages: [{ role: "user", content: "Process this customer request" }]
});
```

**What happens:** The gateway retries with exponential backoff. If you configured a [fallback chain](/features/routing/fallbacks), it falls back to the next model. Your app stays online, customers never notice.

### Scenario: Using Fallback Chains [#scenario-using-fallback-chains]

You want to try a fast Groq model first, then fall back to GPT-4o if Groq fails:

```typescript
import { buildHeaders } from "@cova/gateway";

const response = await client.chat.completions.create({
  model: "groq/llama-3.3-70b-versatile",
  messages: messages,
  extraHeaders: buildHeaders({
    fallbacks: ["gpt-4o", "anthropic/claude-sonnet-4-5-20250929"],
  }),
});
```

**What happens:** Tries `groq/llama-3.3-70b-versatile` first, then falls back to `gpt-4o`, then `anthropic/claude-sonnet-4-5-20250929` if each preceding model returns a retryable error.

### Scenario: Using BYOK with Credits Fallback [#scenario-using-byok-with-credits-fallback]

You have your own OpenAI API key but want credits as a safety net.

```typescript
// Just request the model — BYOK is tried first, then PTB credits
const response = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Analyze this data" }]
});
```

**What happens:** The gateway tries your BYOK key first. If your key fails or hits rate limits, it falls back to CoreValue-managed credits (PTB) at 0% markup.

## Next Steps [#next-steps]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Browse Models" href="https://corevalue.dev/models">
    Explore all available models and providers
  </Card>

  <Card title="Add Provider Keys" href="https://app.corevalue.dev/gateway?tab=provider-keys">
    Connect your provider accounts
  </Card>

  <Card title="Prompt Management" href="#">
    Combine routing with managed prompts
  </Card>
</div>


# Streaming (https://docs.corevalue.dev/features/routing/streaming)



When you make a streaming request through the CoreValue AI Gateway, the response cannot carry the `Cova-Request-Cost` header: headers are sent before the first chunk, and the cost is only known once the stream ends. The gateway reports it in one of two places instead.

* **SSE streams** (`text/event-stream`): the gateway appends a final Server-Sent Events frame containing per-call FinOps metadata — the actual cost, the provider that served the request, your remaining credit balance, and whether cross-provider translation was applied. The `/meta` endpoint is the fallback when this frame is missing.
* **Every other stream format** — Bedrock's `application/vnd.amazon.eventstream` and Google's streaming JSON: no frame is appended, because appending text would corrupt a binary event stream or a JSON document. Read the cost from the `/meta` endpoint, using the `Cova-Id` response header.

## Final SSE Frame Format [#final-sse-frame-format]

For SSE streams only. After the upstream stream closes (including after the OpenAI-format `data: [DONE]` frame), the gateway appends one final SSE event:

```
data: {"cova":{"requestId":"...","provider":"...","model":"...","status":"success","requestCost":0.0015,"creditsRemaining":48.77,"gatewayMode":"translated","translationWarning":"logprobs"}}

```

The frame is a single SSE `data:` line terminated by `\n\n` (two newlines). The JSON payload is nested under a `"cova"` key so the SDK can detect it by checking for `"cova" in lastChunk`.

<Callout type="warning">
  The `requestCost` field is only present when `usage.costUsd > 0`. If no usage was extracted (e.g. the upstream errored before completion), the field is **absent** and you should fall back to the `/meta` endpoint to retrieve the cost.
</Callout>

## CovaGatewayMeta Fields [#covagatewaymeta-fields]

The `cova` object in the final frame contains the following fields:

| Field                | Type                             | Description                                                          |
| -------------------- | -------------------------------- | -------------------------------------------------------------------- |
| `requestId`          | `string`                         | Gateway-generated request UUID (same as `Cova-Id` header)            |
| `provider`           | `string`                         | Provider that served the request (after fallback resolution)         |
| `model`              | `string`                         | Model that served the request (after fallback/budget-gate downgrade) |
| `status`             | `"success" \| "error"`           | Gateway-level status                                                 |
| `requestCost`        | `number?`                        | Actual cost in USD (absent if no usage extracted)                    |
| `estimatedCost`      | `number?`                        | Upfront worst-case cost estimate (PTB only)                          |
| `creditsRemaining`   | `number?`                        | Post-request credit balance in USD (PTB only)                        |
| `gatewayMode`        | `"passthrough" \| "translated"?` | Whether cross-provider translation was applied                       |
| `translationWarning` | `string?`                        | Comma-separated list of dropped fields (only when translated)        |

## Using the SDK [#using-the-sdk]

The `@cova/gateway` SDK's `streamChat()` wraps the OpenAI streaming call and yields chunks unchanged. After `[DONE]`, it yields one final `{ cova: CovaGatewayMeta }` value with the actual cost.

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import OpenAI from "openai";
    import { streamChat } from "@cova/gateway";

    const client = new OpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/v1",
    });

    const stream = streamChat(client, {
      model: "groq/llama-3.3-70b-versatile",
      messages: [{ role: "user", content: "Hello!" }],
      stream: true,
    });

    for await (const chunk of stream) {
      if ("cova" in chunk) {
        // Final frame — per-call FinOps metadata
        console.log(chunk.cova.requestCost);
        console.log(chunk.cova.provider);
        console.log(chunk.cova.creditsRemaining);
      } else {
        // Normal streaming chunk — process as usual
        process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
      }
    }
    ```
  </Tab>

  <Tab>
    ```python
    from openai import OpenAI
    from cova_gateway import stream_chat

    client = OpenAI(
        api_key=os.environ["COVA_API_KEY"],
        base_url="https://gateway.corevalue.dev/v1",
    )

    stream = stream_chat(client, model="groq/llama-3.3-70b-versatile",
        messages=[{"role": "user", "content": "Hello!"}],
        stream=True,
    )

    for chunk in stream:
        if "cova" in chunk:
            # Final frame — per-call FinOps metadata
            print(chunk["cova"]["requestCost"])
            print(chunk["cova"]["provider"])
        else:
            print(chunk.choices[0].delta.content or "", end="")
    ```
  </Tab>
</Tabs>

<Callout type="info">
  If no `cova` frame is present (e.g. the upstream errored before completion), `streamChat()` yields an empty meta. In that case, fall back to the `/meta` endpoint using the `Cova-Id` from the response headers.
</Callout>

## Upfront Cost Estimate (M4) [#upfront-cost-estimate-m4]

Before the stream even begins, the gateway may set the `Cova-Request-Cost-Estimate` response header (M4 milestone 1.5a). This is the upfront worst-case cost estimate — the escrow reservation amount from the wallet check.

| Header                       | When set                                                        |
| ---------------------------- | --------------------------------------------------------------- |
| `Cova-Request-Cost-Estimate` | Only when escrow is enabled and a PTB key may serve the request |

<Callout type="warning">
  `Cova-Request-Cost-Estimate` is a **UX hint only** — it is not the source of truth. The actual charge is the `requestCost` field in the final SSE `cova` frame (or `Cova-Request-Cost` header for non-streaming).
</Callout>

## Fallback: /meta Endpoint [#fallback-meta-endpoint]

For Bedrock and Google streaming JSON responses this is the only source of the cost. For SSE streams, use it when the final frame is missing or the `requestCost` field is absent. Call it after the stream completes, with the `Cova-Id` response header as the request id:

```bash
curl https://gateway.corevalue.dev/gateway/v1/meta/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $COVA_API_KEY"
```

See the [Meta Endpoint](/api-reference/request-cost-lookup) page for full details on response codes and the `CovaGatewayMeta` shape.

## Related [#related]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Meta Endpoint" href="/api-reference/request-cost-lookup">
    Fallback lookup for per-request FinOps metadata
  </Card>

  <Card title="Request Pipeline" href="#">
    How cost is computed during async post-processing
  </Card>

  <Card title="Cross-Provider Translation" href="#">
    The gatewayMode and translationWarning fields
  </Card>
</div>


# OpenAI Codex (https://docs.corevalue.dev/sdks/integrations/codex)







<Callout type="info">
  This integration uses the [AI Gateway](/), which provides a unified API for multiple LLM providers. The AI Gateway is currently in beta.
</Callout>

## CLI Integration [#cli-integration]

<Steps>
  <Step title="strings.generateKey">
    <Html html="strings.generateKeyInstructions" />
  </Step>

  <Step title="Configure Codex config file">
    Update your `$CODEX_HOME/.codex/config.toml` file to include the CoreValue provider configuration:

    <Callout type="info">
      `$CODEX_HOME` is typically `~/.codex` on Mac or Linux.
    </Callout>

    ```plaintext config.toml
    model_provider = "corevalue"

    [model_providers.corevalue]
    name = "CoreValue"
    base_url = "https://gateway.corevalue.dev/v1"
    env_key = "COVA_API_KEY"
    wire_api = "chat"
    ```
  </Step>

  <Step title="Set your CoreValue API key">
    Set the `COVA_API_KEY` environment variable:

    ```bash
    export COVA_API_KEY=<your-corevalue-api-key>
    ```
  </Step>

  <Step title="Run Codex with CoreValue">
    Use Codex as normal. Your requests will automatically be logged to CoreValue:

    ```bash
    # If you set model_provider in config.toml
    codex "What files are in the current directory?"

    # Or specify the provider explicitly
    codex -c model_provider="corevalue" "What files are in the current directory?"
    ```
  </Step>

  <Step title="strings.verifyInCoreValue">
    <Html html="strings.verifyInCoreValueDesciption(&#x22;Codex CLI&#x22;)" />

    <Star />
  </Step>
</Steps>

## SDK Integration [#sdk-integration]

<Steps>
  <Step title="strings.generateKey">
    <Html html="strings.generateKeyInstructions" />
  </Step>

  <Step title="Install the Codex SDK">
    ```bash
    npm install @openai/codex-sdk
    ```
  </Step>

  <Step title="Configure the SDK with CoreValue">
    Initialize the Codex SDK with the AI Gateway base URL:

    ```typescript
    import { Codex } from "@openai/codex-sdk";

    const codex = new Codex({
      baseUrl: "https://gateway.corevalue.dev/v1",
      apiKey: process.env.COVA_API_KEY,
    });

    const thread = codex.startThread({
      model: "gpt-5" // many models supported
    });
    const turn = await thread.run("What files are in the current directory?");

    console.log(turn.finalResponse);
    console.log(turn.items);
    ```

    <Callout type="info">
      The Codex SDK doesn't currently support specifying the wire API, so it will use the Responses API by default. This works with the AI Gateway with limited model and provider support. See the [Responses API documentation](/features/routing/provider-features/responses-api) for more details.
    </Callout>
  </Step>

  <Step title="strings.verifyInCoreValue">
    <Html html="strings.verifyInCoreValueDesciption(&#x22;Codex SDK&#x22;)" />
  </Step>
</Steps>

## Additional Features [#additional-features]

Once integrated with CoreValue AI Gateway, you can take advantage of:

* **Unified Observability**: Monitor all your Codex usage alongside other LLM providers
* **Cost Tracking**: Track costs across different models and providers
* **Custom Properties**: Add metadata to your requests for better organization
* **Rate Limiting**: Control usage and prevent abuse

<RequestIntegration />

## {strings.relatedGuides} [#stringsrelatedguides]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/" iconType="light">
    Learn more about CoreValue's AI Gateway and its features
  </Card>

  <Card title="Responses API Support" href="/features/routing/provider-features/responses-api" iconType="light">
    Use the OpenAI Responses API format through CoreValue AI Gateway
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers" iconType="light">
    Configure automatic routing and fallbacks for reliability
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution" iconType="light">
    Add metadata to your requests for better tracking and organization
  </Card>
</div>


# DSPy (https://docs.corevalue.dev/sdks/integrations/dpsy)







## Introduction [#introduction]

[DSPy](https://dspy.ai) is a declarative framework for building modular AI software with structured code instead of brittle prompts, offering algorithms that compile AI programs into effective prompts and weights for language models across classifiers, RAG pipelines, and agent loops.

## Integration Steps [#integration-steps]

<Steps>
  <Step title="strings.generateKey">
    <Html html="strings.generateKeyInstructions" />
  </Step>

  <Step title="strings.setApiKey">
    Create a `.env` file in your project.

    ```bash
    COVA_API_KEY=sk-cova-XXXXXXXXXXXXXXXX
    ```
  </Step>

  <Step title="strings.installSDK(&#x22;DSPy&#x22;)">
    <p>
      {strings.installRequiredDependencies}
    </p>

    ```bash Python
    pip install dspy
    ```
  </Step>

  <Step title="strings.startUsing(&#x22;DSPy&#x22;)">
    <p>
      {strings.viewRequestsInDashboard}
    </p>

    ```python Python
    import dspy
    import os
    from dotenv import load_dotenv

    load_dotenv()

    # Configure DSPy to use CoreValue AI Gateway
    lm = dspy.LM(
      'gpt-4o-mini',  # or any other model from the CoreValue model registry
      api_key=os.getenv('COVA_API_KEY'),
      api_base='https://gateway.corevalue.dev/v1'
    )

    dspy.configure(lm=lm)

    print(lm("Hello, world!"))
    ```

    <Html html="strings.modelRegistryDescription" />
  </Step>

  <Step title="strings.verifyInCoreValue">
    <Html html="strings.verifyInCoreValueDesciption(&#x22;DSPy&#x22;)" />

    <Star />
  </Step>
</Steps>

## Complete Working Examples [#complete-working-examples]

### Basic Chain of Thought [#basic-chain-of-thought]

```python Python
import dspy
import os
from dotenv import load_dotenv

load_dotenv()

# Configure CoreValue AI Gateway
lm = dspy.LM(
    'gpt-4o-mini',
    api_key=os.getenv('COVA_API_KEY'),
    api_base='https://gateway.corevalue.dev/v1'
)
dspy.configure(lm=lm)

# Define a module
qa = dspy.ChainOfThought('question -> answer')

# Run inference
response = qa(question="How many floors are in the castle David Gregory inherited?")

print('Answer:', response.answer)
print('Reasoning:', response.reasoning)
```

### Custom Generation Configuration [#custom-generation-configuration]

Configure temperature, max\_tokens, and other parameters:

```python Python
import dspy
import os
from dotenv import load_dotenv

load_dotenv()

# Configure with custom generation parameters
lm = dspy.LM(
    'gpt-4o-mini',
    api_key=os.getenv('COVA_API_KEY'),
    api_base='https://gateway.corevalue.dev/v1',
    temperature=0.9,
    max_tokens=2000
)
dspy.configure(lm=lm)

# Use with any DSPy module
predict = dspy.Predict("question -> creative_answer")
response = predict(question="Write a creative story about AI")
print(response.creative_answer)
```

### Tracking with Custom Properties [#tracking-with-custom-properties]

Add custom properties to track and filter your requests in the CoreValue dashboard:

```python Python
import dspy
import os
from dotenv import load_dotenv

load_dotenv()

# Configure with custom CoreValue headers
lm = dspy.LM(
    'gpt-4o-mini',
    api_key=os.getenv('COVA_API_KEY'),
    api_base='https://gateway.corevalue.dev/v1',
    extra_headers={
        # Session tracking
        'Cova-Property-Session-Id': 'dspy-example-session',
        'Cova-Property-Session-Name': 'Question Answering',

        # User tracking
        'Cova-User-Id': 'user-123',

        # Custom properties for filtering
        'Cova-Property-Environment': 'production',
        'Cova-Property-Module': 'chain-of-thought',
        'Cova-Property-Version': '1.0.0'
    }
)
dspy.configure(lm=lm)

# Use normally
qa = dspy.ChainOfThought('question -> answer')
response = qa(question="What is DSPy?")
print(response.answer)
```

## CoreValue Prompts Integration [#corevalue-prompts-integration]

Use CoreValue Prompts for centralized prompt management with DSPy signatures:

```python Python
import dspy
import os
from dotenv import load_dotenv

load_dotenv()

# Configure with prompt parameters
lm = dspy.LM(
    'gpt-4o-mini',
    api_key=os.getenv('COVA_API_KEY'),
    api_base='https://gateway.corevalue.dev/v1',
    extra_body={
        'prompt_id': 'customer-support-prompt-id',
        'version_id': 'version-uuid',
        'environment': 'production',
        'inputs': {
            'customer_name': 'Sarah',
            'issue_type': 'technical'
        }
    }
)
dspy.configure(lm=lm)
```

<Callout type="info">
  Learn more about [Prompts with AI Gateway](/features/routing/provider-features/prompt-caching).
</Callout>

## Advanced Features [#advanced-features]

### Rate Limiting [#rate-limiting]

Configure rate limits for your DSPy applications:

```python Python
lm = dspy.LM(
    'gpt-4o-mini',
    api_key=os.getenv('COVA_API_KEY'),
    api_base='https://gateway.corevalue.dev/v1',
    extra_headers={
        'Cova-RateLimit-Policy': 'basic-100'
    }
)
```

### Caching [#caching]

Enable intelligent caching to reduce costs:

```python Python
lm = dspy.LM(
    'gpt-4o-mini',
    api_key=os.getenv('COVA_API_KEY'),
    api_base='https://gateway.corevalue.dev/v1',
    cache=True  # DSPy's built-in caching works with CoreValue
)
```

### Session Tracking for Multi-Turn Conversations [#session-tracking-for-multi-turn-conversations]

Track entire conversation flows in DSPy programs:

```python Python
import uuid

session_id = str(uuid.uuid4())

lm = dspy.LM(
    'gpt-4o-mini',
    api_key=os.getenv('COVA_API_KEY'),
    api_base='https://gateway.corevalue.dev/v1',
    extra_headers={
        'Cova-Property-Session-Id': session_id,
        'Cova-Property-Session-Name': 'Customer Support',
        'Cova-Property-Session-Path': '/support/chat'
    }
)
dspy.configure(lm=lm)

# All calls in this session will be grouped together
qa = dspy.ChainOfThought('question -> answer')

# Multiple turns
response1 = qa(question="What is your return policy?")
response2 = qa(question="How long does shipping take?")
response3 = qa(question="Do you ship internationally?")

# View the full conversation in CoreValue Sessions
```

## Related Documentation [#related-documentation]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/">
    Learn about CoreValue's AI Gateway features and capabilities
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Configure intelligent routing and automatic failover
  </Card>

  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all available models and providers
  </Card>

  <Card title="Prompt Management" href="/features/routing/provider-features/prompt-caching">
    Version and manage prompts with CoreValue Prompts
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution">
    Add metadata to track and filter your requests
  </Card>

  <Card title="Sessions" href="#">
    Track multi-turn conversations and user sessions
  </Card>

  <Card title="Rate Limiting" href="/features/rate-limits">
    Configure rate limits for your applications
  </Card>

  <Card title="Caching" href="/features/routing/caching">
    Reduce costs and latency with intelligent caching
  </Card>
</div>


# LangChain (https://docs.corevalue.dev/sdks/integrations/langchain)







## Introduction [#introduction]

[LangChain](https://www.langchain.com/) is a popular open-source framework for building applications with large language models across Python, TypeScript, and other languages. By integrating CoreValue AI Gateway with LangChain, you can:

* **Route to different models & providers** with automatic failover through a single endpoint
* **Unified billing** with pass-through billing or bring your own keys
* **Monitor all requests** with automatic cost tracking in one dashboard
* **Stream responses** with full observability for real-time applications

<Callout type="info">
  This integration requires only **two changes** to your existing LangChain code - updating the base URL and API key.
</Callout>

## Integration Steps [#integration-steps]

<Steps>
  <Step title="Create an account + Generate an API Key">
    Sign up at [corevalue.dev](https://www.corevalue.dev) and generate an [API key](https://app.corevalue.dev/governance?tab=keys).

    <Callout type="info">
      You'll also need to configure your provider API keys (OpenAI, Anthropic, etc.) at [CoreValue Providers](https://app.corevalue.dev/gateway?tab=provider-keys) for BYOK (Bring Your Own Keys).
    </Callout>
  </Step>

  <Step title="Set environment variables">
    ```bash
    # Your CoreValue API key
    export COVA_API_KEY=<your-corevalue-api-key>
    ```

    Create a `.env` file in your project:

    ```bash
    COVA_API_KEY=sk-cova-XXXXXXXXXXXXXXXX
    ```
  </Step>

  <Step title="Install LangChain packages">
    ```bash TypeScript
    npm install @langchain/openai @langchain/core dotenv
    # or
    yarn add @langchain/openai @langchain/core dotenv
    ```

    ```bash Python
    pip install langchain-openai langchain-core python-dotenv
    ```
  </Step>

  <Step title="Configure LangChain with CoreValue AI Gateway">
    ```typescript TypeScript
    import { ChatOpenAI } from "@langchain/openai";
    import { HumanMessage, SystemMessage } from "@langchain/core/messages";
    import dotenv from 'dotenv';

    dotenv.config();

    // Initialize ChatOpenAI with CoreValue AI Gateway
    const chat = new ChatOpenAI({
        model: 'gpt-4.1-mini',  // many models supported
        apiKey: process.env.COVA_API_KEY,
        configuration: {
            baseURL: "https://gateway.corevalue.dev/v1",
            defaultHeaders: {
                // Optional: Add custom tracking headers
                // Session tracking via custom properties:
                "Cova-Property-Session-Id": "my-session",
                "Cova-User-Id": "user-123",
                "Cova-Property-Environment": "production",
            },
        },
    });
    ```

    ```python Python
    import os
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage, SystemMessage
    from dotenv import load_dotenv

    load_dotenv()

    # Initialize ChatOpenAI with CoreValue AI Gateway
    chat = ChatOpenAI(
        model='gpt-4.1-mini',  # many models supported
        api_key=os.getenv('COVA_API_KEY'),
        base_url="https://gateway.corevalue.dev/v1",
        default_headers={
            # Optional: Add custom tracking headers
            'Cova-Property-Session-Id': 'my-session',
            'Cova-User-Id': 'user-123',
            'Cova-Property-Environment': 'production',
        },
    )
    ```

    <Callout type="info">
      The **only changes** from a standard LangChain setup are the `apiKey`, `baseURL` (or `base_url` in Python), and optional tracking headers. Everything else stays the same!
    </Callout>

    <Html html="strings.modelRegistryDescription" />
  </Step>

  <Step title="Use LangChain normally">
    Your existing LangChain code continues to work without any changes:

    ```typescript TypeScript
    // Simple completion
    const response = await chat.invoke([
        new SystemMessage("You are a helpful assistant."),
        new HumanMessage("What is the capital of France?"),
    ]);

    console.log(response.content);
    ```

    ```python Python
    # Simple completion
    messages = [
        SystemMessage(content="You are a helpful assistant."),
        HumanMessage(content="What is the capital of France?"),
    ]

    response = chat.invoke(messages)
    print(response.content)
    ```
  </Step>

  <Step title="strings.viewRequestsInDashboard">
    <Html html="strings.viewRequestsInDashboardDescription(&#x22;LangChain&#x22;)" />

    * Request/response bodies
    * Latency metrics
    * Token usage and costs
    * Model performance analytics
    * Error tracking
    * Session tracking

    <Star />
  </Step>
</Steps>

## Migration Example [#migration-example]

Here's what migrating an existing LangChain application looks like:

### Before (Direct OpenAI) [#before-direct-openai]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { ChatOpenAI } from "@langchain/openai";

    const chat = new ChatOpenAI({
        model: 'gpt-4o-mini',
        apiKey: process.env.OPENAI_API_KEY,
    });
    ```
  </Tab>

  <Tab>
    ```python
    from langchain_openai import ChatOpenAI

    chat = ChatOpenAI(
        model='gpt-4o-mini',
        api_key=os.getenv('OPENAI_API_KEY'),
    )
    ```
  </Tab>
</Tabs>

### After (CoreValue AI Gateway) [#after-corevalue-ai-gateway]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { ChatOpenAI } from "@langchain/openai";

    const chat = new ChatOpenAI({
        model: 'gpt-4.1-mini',                      // many models supported
        apiKey: process.env.COVA_API_KEY,      // Your CoreValue API key
        configuration: {
            baseURL: "https://gateway.corevalue.dev/v1"  // Add this!
        },
    });
    ```
  </Tab>

  <Tab>
    ```python
    from langchain_openai import ChatOpenAI

    chat = ChatOpenAI(
        model='gpt-4.1-mini',                      # many models supported
        api_key=os.getenv('COVA_API_KEY'),    # Your CoreValue API key
        base_url="https://gateway.corevalue.dev/v1"  # Add this!
    )
    ```
  </Tab>
</Tabs>

That's it! Just two changes and you're routing through CoreValue's AI Gateway.

## Complete Working Examples [#complete-working-examples]

### Basic Example [#basic-example]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { ChatOpenAI } from "@langchain/openai";
    import { HumanMessage, SystemMessage } from "@langchain/core/messages";
    import dotenv from 'dotenv';

    dotenv.config();

    const chat = new ChatOpenAI({
        model: 'gpt-4.1-mini',  // many models supported
        apiKey: process.env.COVA_API_KEY,
        configuration: {
            baseURL: "https://gateway.corevalue.dev/v1",
            defaultHeaders: {
                // Session tracking via custom properties:
                    "Cova-Property-Session-Id": "langchain-example",
                "Cova-User-Id": "demo-user",
            },
        },
    });

    async function main() {
        console.log('🦜 Starting LangChain + CoreValue AI Gateway example...\n');

        const response = await chat.invoke([
            new SystemMessage("You are a helpful assistant."),
            new HumanMessage("Tell me a joke about programming."),
        ]);

        console.log('🤖 Assistant response:');
        console.log(response.content);
        console.log('\n✅ Completed successfully!');
    }

    main().catch(console.error);
    ```
  </Tab>

  <Tab>
    ```python
    import os
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage, SystemMessage
    from dotenv import load_dotenv

    load_dotenv()

    chat = ChatOpenAI(
        model='gpt-4.1-mini',  # many models supported
        api_key=os.getenv('COVA_API_KEY'),
        base_url="https://gateway.corevalue.dev/v1",
        default_headers={
            'Cova-Property-Session-Id': 'langchain-example',
            'Cova-User-Id': 'demo-user',
        },
    )

    def main():
        print('🐍 Starting LangChain + CoreValue AI Gateway example...\n')

        messages = [
            SystemMessage(content="You are a helpful assistant."),
            HumanMessage(content="Tell me a joke about Python programming."),
        ]

        response = chat.invoke(messages)

        print('🤖 Assistant response:')
        print(response.content)
        print('\n✅ Completed successfully!')

    if __name__ == "__main__":
        main()
    ```
  </Tab>
</Tabs>

### Streaming Example [#streaming-example]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    async function streamingExample() {
        console.log('\n🌊 Streaming example...\n');

        const stream = await chat.stream([
            new SystemMessage("You are a helpful assistant."),
            new HumanMessage("Write a short story about a robot learning to code."),
        ]);

        console.log('🤖 Assistant (streaming):');
        for await (const chunk of stream) {
            process.stdout.write(chunk.content as string);
        }
        console.log('\n\n✅ Streaming completed!');
    }

    streamingExample().catch(console.error);
    ```
  </Tab>

  <Tab>
    ```python
    def streaming_example():
        print('\n🌊 Streaming example...\n')

        messages = [
            SystemMessage(content="You are a helpful assistant."),
            HumanMessage(content="Write a short story about a robot learning to code."),
        ]

        print('🤖 Assistant (streaming):')
        for chunk in chat.stream(messages):
            print(chunk.content, end='', flush=True)

        print('\n\n✅ Streaming completed!')

    streaming_example()
    ```
  </Tab>
</Tabs>

### Multiple Models Example [#multiple-models-example]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    async function testMultipleModels() {
        console.log('🚀 Testing multiple models through CoreValue AI Gateway\n');

        const models = [
            { id: 'gpt-4.1-mini', name: 'OpenAI GPT-4.1 Mini' },
            { id: 'anthropic/claude-opus-4-1-20250805', name: 'Anthropic Claude Opus 4.1' },
            { id: 'google/gemini-2.5-flash-lite', name: 'Google Gemini 2.5 Flash Lite' },
        ];

        for (const model of models) {
            try {
                const chat = new ChatOpenAI({
                    model: model.id,
                    apiKey: process.env.COVA_API_KEY,
                    configuration: {
                        baseURL: "https://gateway.corevalue.dev/v1",
                    },
                });

                console.log(`🤖 Testing ${model.name}... `);
                const response = await chat.invoke([
                    new HumanMessage("Say hello in one sentence."),
                ]);
                console.log(`   Response: ${response.content}\n`);
            } catch (error) {
                console.error(`   Error: ${error}\n`);
            }
        }

        console.log('✅ All models tested!');
        console.log('🔍 Check your dashboard: https://app.corevalue.dev/dashboard');
    }

    testMultipleModels().catch(console.error);
    ```
  </Tab>

  <Tab>
    ```python
    def test_multiple_models():
        print('🚀 Testing multiple models through CoreValue AI Gateway\n')

        models = [
            {'id': 'gpt-4.1-mini', 'name': 'OpenAI GPT-4.1 Mini'},
            {'id': 'anthropic/claude-opus-4-1-20250805', 'name': 'Anthropic Claude Opus 4.1'},
            {'id': 'google/gemini-2.5-flash-lite', 'name': 'Google Gemini 2.5 Flash Lite'},
        ]

        for model in models:
            try:
                chat = ChatOpenAI(
                    model=model['id'],
                    api_key=os.getenv('COVA_API_KEY'),
                    base_url="https://gateway.corevalue.dev/v1",
                )

                print(f"🤖 Testing {model['name']}... ")
                response = chat.invoke([
                    HumanMessage(content="Say hello in one sentence."),
                ])
                print(f"   Response: {response.content}\n")
            except Exception as error:
                print(f"   Error: {error}\n")

        print('✅ All models tested!')
        print('🔍 Check your dashboard: https://app.corevalue.dev/dashboard')

    test_multiple_models()
    ```
  </Tab>
</Tabs>

### Batch Processing Example (Python) [#batch-processing-example-python]

```python Python
def batch_example():
    print('\n📦 Batch processing example...\n')

    message_batches = [
        [HumanMessage(content="What is Python?")],
        [HumanMessage(content="What is JavaScript?")],
        [HumanMessage(content="What is TypeScript?")],
    ]

    responses = chat.batch(message_batches)

    print('🤖 Batch responses:')
    for i, response in enumerate(responses, 1):
        print(f'\nResponse {i}: {response.content}')

    print('\n✅ Batch processing completed!')

batch_example()
```

## CoreValue Prompts Integration [#corevalue-prompts-integration]

You can use CoreValue Prompts for centralized prompt management and versioning by passing parameters through `modelKwargs`:

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    const chat = new ChatOpenAI({
        model: 'gpt-4.1-mini',
        apiKey: process.env.COVA_API_KEY,
        modelKwargs: {
            prompt_id: 'customer-support-prompt',
            version_id: 'version-uuid',
            environment: 'production',
            inputs: { customer_name: 'John', issue_type: 'billing' },
        },
        configuration: {
            baseURL: "https://gateway.corevalue.dev/v1",
        },
    });
    ```
  </Tab>

  <Tab>
    ```python
    chat = ChatOpenAI(
        model='gpt-4.1-mini',
        api_key=os.getenv('COVA_API_KEY'),
        base_url="https://gateway.corevalue.dev/v1",
        model_kwargs={
            'prompt_id': 'customer-support-prompt',
            'version_id': 'version-uuid',
            'environment': 'production',
            'inputs': {'customer_name': 'John', 'issue_type': 'billing'},
        },
    )
    ```
  </Tab>
</Tabs>

<Callout type="info">
  All prompt parameters (`prompt_id`, `version_id`, `environment`, `inputs`) are optional. Learn more about [Prompts with AI Gateway](/features/routing/provider-features/prompt-caching).
</Callout>

## Custom Headers and Properties [#custom-headers-and-properties]

You can add custom properties to track and filter your requests:

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    const chat = new ChatOpenAI({
        model: 'gpt-4.1-mini',
        apiKey: process.env.COVA_API_KEY,
        configuration: {
            baseURL: "https://gateway.corevalue.dev/v1",
            defaultHeaders: {
                // Session tracking
                // Session tracking via custom properties:
                    "Cova-Property-Session-Id": "session-abc-123",
                "Cova-Property-Session-Name": "Customer Support Chat",
                "Cova-Property-Session-Path": "/support/chat/456",

                // User tracking
                "Cova-User-Id": "user-789",

                // Custom properties for filtering
                "Cova-Property-Environment": "production",
                "Cova-Property-App-Version": "2.1.0",
                "Cova-Property-Feature": "customer-support",

                // Rate limiting (optional)
                "Cova-RateLimit-Policy": "basic-100",
            },
        },
    });
    ```
  </Tab>

  <Tab>
    ```python
    chat = ChatOpenAI(
        model='gpt-4.1-mini',
        api_key=os.getenv('COVA_API_KEY'),
        base_url="https://gateway.corevalue.dev/v1",
        default_headers={
            # Session tracking
            'Cova-Property-Session-Id': 'session-abc-123',
            'Cova-Property-Session-Name': 'Customer Support Chat',
            'Cova-Property-Session-Path': '/support/chat/456',

            # User tracking
            'Cova-User-Id': 'user-789',

            # Custom properties for filtering
            'Cova-Property-Environment': 'production',
            'Cova-Property-App-Version': '2.1.0',
            'Cova-Property-Feature': 'customer-support',

            # Rate limiting (optional)
            'Cova-RateLimit-Policy': 'basic-100',
        },
    )
    ```
  </Tab>
</Tabs>

<RequestIntegration />

## Related Documentation [#related-documentation]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/">
    Learn about CoreValue's AI Gateway features and capabilities
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Configure intelligent routing and automatic failover
  </Card>

  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all available models and providers
  </Card>

  <Card title="Prompt Management" href="/features/routing/provider-features/prompt-caching">
    Version and manage prompts with CoreValue Prompts
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution">
    Add metadata to track and filter your requests
  </Card>

  <Card title="Sessions" href="#">
    Track multi-turn conversations and user sessions
  </Card>

  <Card title="Rate Limiting" href="/features/rate-limits">
    Configure rate limits for your applications
  </Card>
</div>


# Langfuse (https://docs.corevalue.dev/sdks/integrations/langfuse)







## Introduction [#introduction]

[Langfuse](https://langfuse.com/) is an open-source LLM observability and analytics platform that provides tracing, monitoring, and analytics for LLM applications.

<Callout type="info">
  This integration requires only **two changes** to your existing Langfuse code - updating the base URL and API key.
</Callout>

## Integration Steps [#integration-steps]

<Steps>
  <Step title="strings.generateKey">
    <Html html="strings.generateKeyInstructions" />

    Create a `.env` file in your project:

    ```bash
    COVA_API_KEY=sk-cova-XXXXXXXXXXXXXXXX
    ```
  </Step>

  <Step title="Install Langfuse packages">
    ```bash
    pip install langfuse python-dotenv
    ```
  </Step>

  <Step title="Create a Langfuse OpenAI client using CoreValue">
    Use Langfuse's OpenAI client wrapper with CoreValue's base URL:

    ```python
    import os
    from dotenv import load_dotenv
    from langfuse.openai import openai

    # Load environment variables
    load_dotenv()

    # Create an OpenAI client with CoreValue's base URL
    client = openai.OpenAI(
        api_key=os.getenv("COVA_API_KEY"),
        base_url="https://gateway.corevalue.dev/v1"
    )
    ```
  </Step>

  <Step title="Make requests with Langfuse tracing">
    Your existing Langfuse code continues to work without any changes:

    ```python
    # Make a chat completion request
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "Tell me a fun fact about space."}
        ],
        name="fun-fact-request"  # Optional: Name of the generation in Langfuse
    )

    # Print the assistant's reply
    print(response.choices[0].message.content)
    ```

    <Html html="strings.modelRegistryDescription" />
  </Step>

  <Step title="strings.viewRequestsInDashboard">
    <Html html="strings.viewRequestsInDashboardDescription(&#x22;Langfuse&#x22;)" />

    * Request/response bodies
    * Latency metrics
    * Token usage and costs
    * Model performance analytics
    * Error tracking
    * LLM traces and spans in Langfuse
    * Session tracking

    <Star />
  </Step>
</Steps>

## Complete Working Example [#complete-working-example]

```python
#!/usr/bin/env python3

import os
from dotenv import load_dotenv
from langfuse.openai import openai

# Load environment variables
load_dotenv()

# Create an OpenAI client with CoreValue's base URL
client = openai.OpenAI(
    api_key=os.getenv("COVA_API_KEY"),
    base_url="https://gateway.corevalue.dev/v1"
)

# Make a chat completion request
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Tell me a fun fact about space."}
    ],
    name="fun-fact-request"  # Optional: Name of the generation in Langfuse
)

# Print the assistant's reply
print(response.choices[0].message.content)
```

### Streaming Responses [#streaming-responses]

Langfuse supports streaming responses with full observability:

```python
# Streaming example
stream = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "Write a short story about a robot learning to code."}
    ],
    stream=True,
    name="streaming-story"
)

print("🤖 Assistant (streaming):")
for chunk in stream:
    if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="", flush=True)
print("\n")
```

### Nested Example [#nested-example]

```python
import os
from dotenv import load_dotenv
from langfuse import observe
from langfuse.openai import openai

load_dotenv()

client = openai.OpenAI(
    base_url="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY"),
)

@observe()  # This decorator enables tracing of the function
def analyze_text(text: str):
    # First LLM call: Summarize the text
    summary_response = summarize_text(text)
    summary = summary_response.choices[0].message.content

    # Second LLM call: Analyze the sentiment of the summary
    sentiment_response = analyze_sentiment(summary)
    sentiment = sentiment_response.choices[0].message.content

    return {
        "summary": summary,
        "sentiment": sentiment
    }

@observe()  # Nested function to be traced
def summarize_text(text: str):
    return client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": "You summarize texts in a concise manner."},
            {"role": "user", "content": f"Summarize the following text:\n{text}"}
        ],
        name="summarize-text"
    )

@observe()  # Nested function to be traced
def analyze_sentiment(summary: str):
    return client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": "You analyze the sentiment of texts."},
            {"role": "user", "content": f"Analyze the sentiment of the following summary:\n{summary}"}
        ],
        name="analyze-sentiment"
    )

# Example usage
text_to_analyze = "OpenAI's GPT-4 model has significantly advanced the field of AI, setting new standards for language generation."
analyze_text(text_to_analyze)
```

## Related Documentation [#related-documentation]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/">
    Learn about CoreValue's AI Gateway features and capabilities
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Configure intelligent routing and automatic failover
  </Card>

  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all available models and providers
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution">
    Add metadata to track and filter your requests
  </Card>

  <Card title="Sessions" href="#">
    Track multi-turn conversations and user sessions
  </Card>

  <Card title="Rate Limiting" href="/features/rate-limits">
    Configure rate limits for your applications
  </Card>
</div>


# LangGraph (https://docs.corevalue.dev/sdks/integrations/langgraph)





## Introduction [#introduction]

[LangGraph](https://www.langchain.com/langgraph) is a framework for building stateful, multi-agent applications with LLMs. The integration with CoreValue AI Gateway is nearly identical to the [LangChain integration](/sdks/integrations/langchain), with the addition of agent-specific features.

<Callout type="info">
  This integration requires only **two changes** to your existing LangGraph code - updating the base URL and API key. See the [LangChain AI Gateway docs](/sdks/integrations/langchain) for full feature details.
</Callout>

## Quick Start [#quick-start]

Follow the same setup as [LangChain AI Gateway integration](/sdks/integrations/langchain), then create your agent:

<Tabs items="[&#x22;TypeScript - OpenAI&#x22;, &#x22;Python - OpenAI&#x22;]">
  <Tab>
    ```typescript
    import { ChatOpenAI } from "@langchain/openai";
    import { createReactAgent } from "@langchain/langgraph/prebuilt";
    import { MemorySaver } from "@langchain/langgraph";

    const model = new ChatOpenAI({
        model: 'gpt-4.1-mini',
        apiKey: process.env.COVA_API_KEY,
        configuration: {
            baseURL: "https://gateway.corevalue.dev/v1",
        },
    });

    const agent = createReactAgent({
        llm: model,
        tools: yourTools,
        checkpointer: new MemorySaver(),
    });
    ```
  </Tab>

  <Tab>
    ```python
    from langchain_openai import ChatOpenAI
    from langgraph.prebuilt import create_react_agent
    from langgraph.checkpoint.memory import MemorySaver

    model = ChatOpenAI(
        model='gpt-4.1-mini',
        api_key=os.getenv('COVA_API_KEY'),
        base_url="https://gateway.corevalue.dev/v1",
    )

    agent = create_react_agent(
        model,
        tools=your_tools,
        checkpointer=MemorySaver(),
    )
    ```
  </Tab>
</Tabs>

<Star />

## Migration Example [#migration-example]

### Before (Direct Provider) [#before-direct-provider]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { ChatOpenAI } from "@langchain/openai";
    import { createReactAgent } from "@langchain/langgraph/prebuilt";

    const model = new ChatOpenAI({
        model: 'gpt-4o-mini',
        apiKey: process.env.OPENAI_API_KEY,
    });

    const agent = createReactAgent({
        llm: model,
        tools: myTools,
    });
    ```
  </Tab>

  <Tab>
    ```python
    from langchain_openai import ChatOpenAI
    from langgraph.prebuilt import create_react_agent

    model = ChatOpenAI(
        model='gpt-4o-mini',
        api_key=os.getenv('OPENAI_API_KEY'),
    )

    agent = create_react_agent(model, tools=my_tools)
    ```
  </Tab>
</Tabs>

### After (CoreValue AI Gateway) [#after-corevalue-ai-gateway]

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { ChatOpenAI } from "@langchain/openai";
    import { createReactAgent } from "@langchain/langgraph/prebuilt";

    const model = new ChatOpenAI({
        model: 'gpt-4.1-mini',                      // many models supported
        apiKey: process.env.COVA_API_KEY,      // Your CoreValue API key
        configuration: {
            baseURL: "https://gateway.corevalue.dev/v1"  // Add this!
        },
    });

    const agent = createReactAgent({
        llm: model,
        tools: myTools,
    });
    ```
  </Tab>

  <Tab>
    ```python
    from langchain_openai import ChatOpenAI
    from langgraph.prebuilt import create_react_agent

    model = ChatOpenAI(
        model='gpt-4.1-mini',                      # many models supported
        api_key=os.getenv('COVA_API_KEY'),    # Your CoreValue API key
        base_url="https://gateway.corevalue.dev/v1"  # Add this!
    )

    agent = create_react_agent(model, tools=my_tools)
    ```
  </Tab>
</Tabs>

## Adding Custom Headers to Agent Invocations [#adding-custom-headers-to-agent-invocations]

You can add custom properties when calling your agent with `invoke()`:

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab>
    ```typescript
    import { HumanMessage } from "@langchain/core/messages";
    import { v4 as uuidv4 } from 'uuid';

    const result = await agent.invoke(
        { messages: [new HumanMessage("What is the weather in San Francisco?")] },
        {
            options: {
                headers: {
                    // Session tracking via custom properties:
                    "Cova-Property-Session-Id": uuidv4(),
                    "Cova-Property-Session-Path": "/weather/query",
                    "Cova-Property-Query-Type": "weather",
                },
            },
        }
    );
    ```
  </Tab>

  <Tab>
    ```python
    from langchain_core.messages import HumanMessage
    import uuid

    result = agent.invoke(
        {"messages": [HumanMessage(content="What is the weather in San Francisco?")]},
        {
            "configurable": {
                "headers": {
                    // Session tracking via custom properties:
                    "Cova-Property-Session-Id": str(uuid.uuid4()),
                    "Cova-Property-Session-Path": "/weather/query",
                    "Cova-Property-Query-Type": "weather",
                }
            }
        }
    )
    ```
  </Tab>
</Tabs>

<RequestIntegration />

## Related Documentation [#related-documentation]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/">
    Learn about CoreValue's AI Gateway features and capabilities
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Configure intelligent routing and automatic failover
  </Card>

  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all available models and providers
  </Card>

  <Card title="LangChain Integration" href="/sdks/integrations/langchain">
    Full AI Gateway feature documentation
  </Card>

  <Card title="Sessions" href="#">
    Track multi-turn conversations and agent workflows
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution">
    Add metadata to track and filter your requests
  </Card>
</div>


# LiteLLM (https://docs.corevalue.dev/sdks/integrations/litellm)







## Introduction [#introduction]

[LiteLLM](https://www.litellm.ai/) is a unified interface for calling LLM APIs.

## Integration Steps [#integration-steps]

<Steps>
  <Step title="strings.generateKey">
    <Html html="strings.generateKeyInstructions" />
  </Step>

  <Step title="strings.setApiKey">
    <Html html="strings.setApiKeyInstructions" />

    ```bash
    COVA_API_KEY=sk-cova-XXXXXXXXXXXXXXXX
    ```
  </Step>

  <Step title="strings.installSDK(&#x22;LiteLLM&#x22;)">
    <p>
      {strings.installRequiredDependencies}
    </p>

    ```bash
    pip install litellm python-dotenv
    ```
  </Step>

  <Step title="Use LiteLLM with Cova">
    Point LiteLLM at the Cova AI Gateway by setting `api_base` to the gateway URL and using the `openai/` prefix for models:

    ```python
    import os
    from litellm import completion
    from dotenv import load_dotenv

    load_dotenv()

    # Route through Cova AI Gateway
    response = completion(
        model="openai/gpt-4o",
        messages=[{"role": "user", "content": "What is the capital of France?"}],
        api_base="https://gateway.corevalue.dev/v1",
        api_key=os.getenv("COVA_API_KEY")
    )

    print(response.choices[0].message.content)
    ```

    <Html html="strings.modelRegistryDescription" />
  </Step>

  <Step title="strings.viewRequestsInDashboard">
    <Html html="strings.viewRequestsInDashboardDescription(&#x22;LiteLLM&#x22;)" />

    <Html html="strings.verifyInCoreValueDesciption(&#x22;any LiteLLM request&#x22;)" />

    <Star />
  </Step>
</Steps>

## Complete Working Examples [#complete-working-examples]

### Basic Completion [#basic-completion]

```python
import os
from litellm import completion
from dotenv import load_dotenv

load_dotenv()

# Simple completion
response = completion(
    model="openai/gpt-4o-mini",
    messages=[{"role": "user", "content": "Tell me a fun fact about space"}],
    api_base="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY")
)

print(response.choices[0].message.content)
```

### Streaming Responses [#streaming-responses]

```python
import os
from litellm import completion
from dotenv import load_dotenv

load_dotenv()

# Streaming example
response = completion(
    model="openai/anthropic/claude-sonnet-4-5-20250929",
    messages=[{"role": "user", "content": "Write a short story about a robot learning to paint"}],
    stream=True,
    api_base="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY")
)

print("🤖 Assistant (streaming):")
for chunk in response:
    if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print("\n")
```

### Custom Properties and Session Tracking [#custom-properties-and-session-tracking]

Add metadata to track and filter your requests:

```python
import os
from litellm import completion
from dotenv import load_dotenv

load_dotenv()

response = completion(
    model="openai/gpt-4o-mini",
    messages=[{"role": "user", "content": "What's the weather like?"}],
    api_base="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY"),
    extra_headers={
        "Cova-User-Id": "user-789",
        "Cova-Property-Environment": "production",
        "Cova-Property-App-Version": "2.1.0",
        "Cova-Property-Feature": "weather-query"
    }
)

print(response.choices[0].message.content)
```

## Provider Selection and Fallback [#provider-selection-and-fallback]

Cova's AI Gateway supports automatic failover between providers. Use the `provider/model` format to select a specific provider:

```python
import os
from litellm import completion
from dotenv import load_dotenv

load_dotenv()

# Automatic routing (cheapest provider)
response = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello!"}],
    api_base="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY")
)

# Manual provider selection (anthropic/)
response = completion(
    model="openai/anthropic/claude-sonnet-4-5-20250929",
    messages=[{"role": "user", "content": "Hello!"}],
    api_base="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY")
)
```

For fallback chains, use the `Cova-Fallbacks` header:

```python
response = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello!"}],
    api_base="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY"),
    extra_headers={
        "Cova-Fallbacks": '["anthropic/claude-sonnet-4-5-20250929"]'
    }
)
```

## Advanced Features [#advanced-features]

### Caching [#caching]

Enable caching to reduce costs and latency for repeated requests:

```python
import os
from litellm import completion
from dotenv import load_dotenv

load_dotenv()

# Enable caching for this request
response = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "What is 2+2?"}],
    api_base="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY"),
    extra_headers={
        "Cova-Cache-Enabled": "true"
    }
)

print(response.choices[0].message.content)

# Subsequent identical requests will be served from cache
response2 = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "What is 2+2?"}],
    api_base="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY"),
    extra_headers={
        "Cova-Cache-Enabled": "true"
    }
)

print(response2.choices[0].message.content)
```

### Rate Limiting [#rate-limiting]

Apply rate limiting policies to control request rates:

```python
import os
from litellm import completion
from dotenv import load_dotenv

load_dotenv()

response = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    api_base="https://gateway.corevalue.dev/v1",
    api_key=os.getenv("COVA_API_KEY"),
    extra_headers={
        "Cova-RateLimit-Policy": "10000;w=3600"
    }
)

print(response.choices[0].message.content)
```

## Related Documentation [#related-documentation]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/">
    Learn about Cova's AI Gateway features and capabilities
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Configure intelligent routing and automatic failover
  </Card>

  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all available models and providers
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution">
    Add metadata to track and filter your requests
  </Card>

  <Card title="Sessions" href="#">
    Track multi-turn conversations and user sessions
  </Card>

  <Card title="Rate Limiting" href="/features/rate-limits">
    Configure rate limits for your applications
  </Card>

  <Card title="Caching" href="/features/routing/caching">
    Reduce costs and latency with intelligent caching
  </Card>

  <Card title="LiteLLM Documentation" href="https://docs.litellm.ai">
    Official LiteLLM documentation
  </Card>
</div>


# LlamaIndex (https://docs.corevalue.dev/sdks/integrations/llamaindex)





## Introduction [#introduction]

The CoreValue LLM for LlamaIndex lets you send OpenAI‑compatible requests through the CoreValue AI Gateway — no provider keys needed. Gain centralized routing, observability, and control across many models and providers.

<Callout type="info">
  This integration uses a dedicated LlamaIndex package: <code>llama-index-llms-corevalue</code>.
</Callout>

## Install [#install]

```bash
pip install llama-index-llms-corevalue
```

## Usage [#usage]

```python
from llama_index.llms.corevalue import CoreValue
from llama_index.llms.openai_like.base import ChatMessage

llm = CoreValue(
    api_key="<corevalue-api-key>",
    model="gpt-4o-mini",  # works across providers
    is_chat_model=True,
)

message: ChatMessage = ChatMessage(role="user", content="Hello world!")

response = llm.chat(messages=[message])
print(str(response))
```

### Parameters [#parameters]

* model: OpenAI‑compatible model name routed via CoreValue. See the
  <a href="https://www.corevalue.dev/models" target="_blank">model registry</a>.
* api\_base (optional): Base URL for CoreValue AI Gateway (defaults to the package’s `DEFAULT_API_BASE`). Can also be set via `COVA_API_BASE`.
* api\_key: Your CoreValue API key. You can set via constructor or `COVA_API_KEY`.
* default\_headers (optional): Add additional headers; the `Authorization: Bearer <api_key>` header is set automatically.

## Environment Variables [#environment-variables]

```bash
export COVA_API_KEY=sk-cova-XXXXXXXXXXXXXXXX
# Optional override
export COVA_API_BASE=https://gateway.corevalue.dev/v1
```

## Advanced Configuration [#advanced-configuration]

```python
from llama_index.llms.corevalue import CoreValue

llm = CoreValue(
    model="gpt-4.1-mini",
    api_key="<corevalue-api-key>",
    api_base="https://gateway.corevalue.dev/v1",
    default_headers={
        "Cova-Session-Id": "demo-session",
        "Cova-User-Id": "user-123",
        "Cova-Property-Environment": "production",
    },
    temperature=0.2,
    max_tokens=256,
)
```

<Star />

## Notes [#notes]

* Authentication uses your CoreValue API key; provider keys are not required when using the AI Gateway.
* All requests appear in the CoreValue dashboard with full request/response visibility and cost tracking.
* Learn more about routing and model coverage:
  * <a href="/features/routing/other-providers">
      Provider routing
    </a>
  * <a href="https://www.corevalue.dev/models" target="_blank">
      Model registry
    </a>

<RequestIntegration />


# n8n (https://docs.corevalue.dev/sdks/integrations/n8n)















## Introduction [#introduction]

The CoreValue Chat Model is a community node for [n8n](https://n8n.io/) that provides a LangChain-compatible interface for AI workflows. Route requests to any LLM provider through the CoreValue AI Gateway.

<Callout type="info">
  This is an n8n community node that integrates seamlessly with n8n's AI chain functionality.
</Callout>

## Prerequisites [#prerequisites]

* An n8n account (see [n8n installation docs](https://docs.n8n.io/hosting/) for setup options)
* A CoreValue API key ([get one here](https://app.corevalue.dev/governance?tab=keys))

## Integration Steps [#integration-steps]

<Steps>
  <Step title="Install the CoreValue community node">
    From your n8n interface:

    1. Click the **user menu** (bottom left corner)
    2. Select **Settings**
    3. Go to **Community Nodes**
    4. Click **Install a community node**
    5. Enter the package name: `n8n-nodes-corevalue`
    6. Click **Install**

    Wait \~30 seconds for installation. The node will appear in your nodes panel.

    <Callout type="info">
      Learn more about installing community nodes in the [n8n documentation](https://docs.n8n.io/integrations/community-nodes/installation/).
    </Callout>

        <img alt="n8n install community node" src="__img0" />
  </Step>

  <Step title="Configure CoreValue credentials">
    Add your CoreValue API key to n8n:

    1. Go to **Settings** → **Credentials**
    2. Click **Add Credential**
    3. Search for "CoreValue" and select **CoreValue LLM Observability**
    4. Enter your CoreValue API key
    5. Click **Save**

        <img alt="n8n credentials tab" src="__img1" />
  </Step>

  <Step title="Add the CoreValue Chat Model node to your workflow">
    1. Create a new workflow or open an existing one
    2. Click "+" to add a node
    3. Search for "CoreValue Chat Model"
    4. Configure the node:
       * **Credentials**: Select your saved CoreValue credentials
       * **Model**: Choose any model from the [model registry](https://corevalue.dev/models) (e.g., `gpt-4.1-mini`, `anthropic/claude-3-opus-20240229`)
       * **Options**: Configure temperature, max tokens, and other model parameters

        <img alt="n8n search for CoreValue node" src="__img2" />

    <Callout type="info">
      The CoreValue Chat Model node outputs a LangChain-compatible model that can be used with other AI nodes in n8n.
    </Callout>
  </Step>

  <Step title="Use in AI chains">
    The CoreValue Chat Model node is designed to work with n8n's AI chain functionality:

    1. Connect the node to other AI nodes that accept `ai_languageModel` inputs
    2. Build complex AI workflows with Chat nodes, Chain nodes, and other AI processing nodes
    3. All requests are automatically logged to CoreValue

    Example workflow:
    Chat Input → CoreValue Chat Model → Chat Output

        <img alt="n8n workflow example" src="__img3" />
  </Step>

  <Step title="View requests in CoreValue dashboard">
    Open your [CoreValue dashboard](https://app.corevalue.dev/dashboard) to see:

    * All workflow requests logged automatically
    * Token usage and costs per request
    * Response time metrics
    * Full request/response bodies
    * Session tracking for multi-turn conversations
    * Custom properties for filtering and analysis

        <img alt="CoreValue dashboard verification" src="__img4" />

    <Star />
  </Step>
</Steps>

## Node Configuration [#node-configuration]

### Required Parameters [#required-parameters]

* **Model**: Any model supported by CoreValue AI Gateway.
  Examples: `gpt-4.1-mini`, `anthropic/claude-opus-4-1-20250805`, `google/gemini-2.5-flash-lite`.
  See all models in the [CoreValue's model registry](https://corevalue.dev/models)

### Model Options [#model-options]

* **Temperature** (0-2): Controls randomness in responses
* **Max Tokens**: Maximum tokens to generate
* **Top P** (0-1): Nucleus sampling parameter
* **Frequency Penalty** (-2 to 2): Reduces repetition
* **Presence Penalty** (-2 to 2): Encourages new topics
* **Response Format**: Text or JSON
* **Timeout**: Request timeout in milliseconds
* **Max Retries**: Number of retry attempts on failure

## Example Workflows [#example-workflows]

### Basic Chat Workflow [#basic-chat-workflow]

```
[Chat Input] → [CoreValue Chat Model] → [Chat Output]
```

1. Add a **Chat Input** node (triggers on user message)
2. Add the **CoreValue Chat Model** node
   * Model: `gpt-4.1-mini`
   * Temperature: 0.7
3. Add a **Chat Output** node to display the response

### Multi-Step AI Chain [#multi-step-ai-chain]

```
[Webhook] → [CoreValue Chat Model] → [Extract Data] → [CoreValue Chat Model] → [Response]
```

1. Receive data via webhook
2. First CoreValue Chat Model analyzes the input
3. Extract structured data
4. Second CoreValue Chat Model generates a response
5. Both requests appear in CoreValue dashboard with session tracking

### Workflow with Custom Properties [#workflow-with-custom-properties]

Configure the node with custom properties to track workflow metadata:

1. Open the **CoreValue Chat Model** node
2. Expand **CoreValue Options** → **Custom Properties**
3. Add a JSON object:

```json
{
  "workflow_name": "customer-onboarding",
  "environment": "production",
  "version": "2.1.0"
}
```

All requests from this node will include these properties in CoreValue.

## Troubleshooting [#troubleshooting]

### Node Installation Issues [#node-installation-issues]

* **Node not appearing**: Wait 30 seconds after installation, then refresh n8n
* **Installation failed**: Check your n8n instance has internet access
* **Version conflicts**: Ensure you're running a compatible n8n version (>= 1.0)

### Authentication Errors [#authentication-errors]

* **Invalid API key**: Verify your CoreValue API key starts with `sk-cova-`
* **403 Forbidden**: Ensure your API key has write access enabled
* **Provider not configured**: Check the name of the model is exactly the [model ID expected by the gateway](https://corevalue.dev/models). If you've added your own provider keys, make sure they are correctly set in [your CoreValue dashboard](https://app.corevalue.dev/gateway?tab=provider-keys)

### Model Errors [#model-errors]

* **Model not found**: Check the exact model name at [CoreValue's model registry](https://corevalue.dev/models)
* **Model unavailable**: Verify provider access in your CoreValue account
* **Different naming**: Providers use different conventions (e.g., OpenAI uses `gpt-4o-mini`, while the gateway uses `gpt-4.1-mini`)

### Getting Help [#getting-help]

* [n8n Community Forum](https://community.n8n.io/)

* [CoreValue Documentation](https://docs.corevalue.dev)

* [GitHub Repository](https://github.com/CoreValue/n8n-nodes-corevalue)

<RequestIntegration />

## Related Documentation [#related-documentation]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/">
    Learn about CoreValue's AI Gateway features and capabilities
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Configure intelligent routing and automatic failover
  </Card>

  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all available models and providers
  </Card>

  <Card title="Request Pipeline" href="#">
    Explore the 12-step gateway request pipeline
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution">
    Add metadata to track and filter your requests
  </Card>

  <Card title="Sessions" href="#">
    Track multi-turn conversations and user sessions
  </Card>
</div>


# OpenAI Agents (https://docs.corevalue.dev/sdks/integrations/openai-agents)







## Introduction [#introduction]

[OpenAI Agents SDK](https://github.com/openai/agents) is a framework for building AI agents with tool calling, multi-step reasoning, and structured outputs.

## {strings.howToIntegrate} [#stringshowtointegrate]

<Steps>
  <Step title="strings.generateKey">
    {strings.generateKeyInstructions}

    ```js
    COVA_API_KEY=sk-cova-XXXXXXXXXXXXXXXX
    ```
  </Step>

  <Step title="strings.installSDK('OpenAI Agents SDK')">
    ```bash
    npm install @openai/agents openai
    # or
    pip install openai-agents
    ```
  </Step>

  <Step title="Configure OpenAI Agents with CoreValue AI Gateway">
    ```typescript TypeScript
    import { Agent, setDefaultOpenAIClient } from "@openai/agents";
    import OpenAI from "openai";
    import dotenv from "dotenv";

    dotenv.config();

    const client = new OpenAI({
      baseURL: "https://gateway.corevalue.dev/v1",
      apiKey: process.env.COVA_API_KEY
    });

    // Set the client globally for all agents
    setDefaultOpenAIClient(client);
    ```

    ```python Python
    import os
    from agents import set_default_openai_client
    from openai import OpenAI

    client = OpenAI(
        base_url="https://gateway.corevalue.dev/v1",
        api_key=os.getenv("COVA_API_KEY")
    )

    # Set the client globally for all agents
    set_default_openai_client(client)
    ```

    <Html html="strings.modelRegistryDescription" />
  </Step>

  <Step title="Use OpenAI Agents normally">
    Your existing OpenAI Agents code continues to work without any changes:

    ```typescript TypeScript
    import { Agent, run, tool } from "@openai/agents";
    import { z } from "zod";

    // Define tools
    const calculator = tool({
      name: "calculator",
      description: "Perform basic arithmetic operations",
      parameters: z.object({
        operation: z.enum(["add", "subtract", "multiply", "divide"]),
        a: z.number(),
        b: z.number()
      }),
      async execute({ operation, a, b }) {
        switch (operation) {
          case "add":
            return a + b;
          case "subtract":
            return a - b;
          case "multiply":
            return a * b;
          case "divide":
            if (b === 0) return "Error: Division by zero";
            return a / b;
        }
      }
    });

    // Create an agent with tools
    const agent = new Agent({
      name: "Assistant",
      instructions: "You are a helpful assistant.",
      tools: [calculator],
      model: "gpt-4o-mini",
    });

    // Run the agent
    const result = await run(agent, "Multiply 2 by 2");
    console.log(result.finalOutput);
    ```

    ```python Python
    from agents import Agent, Runner, tool
    from typing import Literal

    # Define tools
    @tool
    def calculator(operation: Literal["add", "subtract", "multiply", "divide"], a: float, b: float) -> float | str:
        """Perform basic arithmetic operations."""
        if operation == "add":
            return a + b
        elif operation == "subtract":
            return a - b
        elif operation == "multiply":
            return a * b
        elif operation == "divide":
            if b == 0:
                return "Error: Division by zero"
            return a / b

    # Create an agent with tools
    agent = Agent(
        name="Assistant",
        instructions="You are a helpful assistant.",
        tools=[calculator],
        model="gpt-4o-mini"
    )

    # Run the agent
    result = Runner.run_sync(agent, "Multiply 2 by 2")
    print(result.final_output)
    ```
  </Step>

  <Step title="strings.viewRequestsInDashboard">
    <Html html="strings.viewRequestsInDashboardDescription(&#x22;OpenAI Agents&#x22;)" />

    * Request/response bodies
    * Latency metrics
    * Token usage and costs
    * Model performance analytics
    * Tool usage tracking
    * Agent reasoning steps
    * Error tracking
    * Session tracking

    <Star />
  </Step>
</Steps>

<RequestIntegration />

## Related Documentation [#related-documentation]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/">
    Learn about CoreValue's AI Gateway features and capabilities
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Configure intelligent routing and automatic failover
  </Card>

  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all available models and providers
  </Card>

  <Card title="Prompt Management" href="/features/routing/provider-features/prompt-caching">
    Version and manage prompts with CoreValue Prompts
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution">
    Add metadata to track and filter your requests
  </Card>

  <Card title="Sessions" href="#">
    Track multi-turn conversations and user sessions
  </Card>

  <Card title="Rate Limiting" href="/features/rate-limits">
    Configure rate limits for your applications
  </Card>

  <Card title="Tool Usage Tracking" href="#">
    Monitor tool calls and function usage in your agents
  </Card>
</div>


# All integrations (https://docs.corevalue.dev/sdks/integrations/overview)





CoreValue AI Gateway integrates with most AI frameworks and development tools to give you access to LLM providers across 18 provider prefixes and top tier observability.

## Why Use CoreValue Integrations? [#why-use-corevalue-integrations]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Minimal Code Changes">
    Most integrations require only updating your base URL and API key - your existing code stays the same
  </Card>

  <Card title="Many Models Available">
    Access GPT, Claude, Gemini, and many more models through a single endpoint
  </Card>

  <Card title="Full Observability">
    Track all requests, costs, latency, and errors in one unified dashboard
  </Card>

  <Card title="Automatic Failover">
    Built-in routing and fallback logic keeps your applications running
  </Card>
</div>

## Popular Integrations [#popular-integrations]

Most often, integrating to CoreValue is as simple as updating your `baseURL` to `"https://gateway.corevalue.dev/v1"` and adding your CoreValue API key as a header.

<RequestIntegration />

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="LangChain" href="/sdks/integrations/langchain">
    Works with both Python and TypeScript.
  </Card>

  <Card title="LangGraph" href="/sdks/integrations/langgraph">
    Building stateful, multi-actor applications with LLMs.
  </Card>

  <Card title="LlamaIndex" href="/sdks/integrations/llamaindex">
    Get full observability for your LlamaIndex searches.
  </Card>

  <Card title="Vercel AI SDK" href="/sdks/integrations/vercel-ai-sdk">
    Route, monitor, debug, and improve your AI applications.
  </Card>

  <Card title="Zapier" href="/sdks/integrations/zapier">
    Use LLMs in your automation workflows with full observability.
  </Card>

  <Card title="Codex" href="/sdks/integrations/codex">
    Enhance your code generation capabilities.
  </Card>
</div>

## Related Documentation [#related-documentation]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/">
    Learn about CoreValue's AI Gateway features and capabilities
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Configure intelligent routing and automatic failover
  </Card>

  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all available models and providers
  </Card>

  <Card title="Quick Start Guide" href="/getting-started/quickstart">
    Get started with CoreValue AI Gateway in 5 minutes
  </Card>
</div>


# Semantic Kernel (https://docs.corevalue.dev/sdks/integrations/semantic-kernel)





## Introduction [#introduction]

[Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/) is Microsoft's open-source SDK for building AI agents and orchestrating LLM workflows across multiple languages (.NET, Python, Java). By integrating CoreValue AI Gateway with Semantic Kernel, you can:

* **Route to different models & providers** with automatic failover through a single endpoint
* **Unified billing** with pass-through billing or bring your own keys
* **Monitor all requests** with automatic cost tracking in one dashboard

<Callout type="info">
  This integration requires only **one line change** to your existing Semantic Kernel code - adding the AI Gateway endpoint.
</Callout>

## Integration Steps [#integration-steps]

<Steps>
  <Step title="Create an account + Generate an API Key">
    Sign up at [corevalue.dev](https://www.corevalue.dev) and generate an [API key](https://app.corevalue.dev/governance?tab=keys).

    <Callout type="info">
      You'll also need to configure your provider API keys (OpenAI, Anthropic, etc.) at [CoreValue Providers](https://app.corevalue.dev/gateway?tab=provider-keys) for BYOK (Bring Your Own Keys).
    </Callout>
  </Step>

  <Step title="Set environment variables">
    ```bash
    # Your CoreValue API key
    export COVA_API_KEY=<your-corevalue-api-key>
    ```

    Create a `.env` file in your project:

    ```bash
    COVA_API_KEY=sk-cova-XXXXXXXXXXXXXXXX
    ```
  </Step>

  <Step title="Add the AI Gateway endpoint to your Semantic Kernel configuration">
    ```csharp .NET
    using Microsoft.SemanticKernel;
    using Microsoft.SemanticKernel.ChatCompletion;
    using DotNetEnv;

    // Load environment variables
    Env.Load();
    var corevalueApiKey = Environment.GetEnvironmentVariable("COVA_API_KEY");

    // Create kernel builder
    var builder = Kernel.CreateBuilder();

    // Add OpenAI chat completion with CoreValue AI Gateway endpoint
    builder.AddOpenAIChatCompletion(
        modelId: "gpt-4.1-mini",                                // Any model from CoreValue registry
        apiKey: corevalueApiKey,                                 // Your CoreValue API key
        endpoint: new Uri("https://gateway.corevalue.dev/v1")  // CoreValue AI Gateway
    );

    var kernel = builder.Build();
    ```

    ```python Python
    import semantic_kernel as sk
    from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
    import os

    # Load environment variables
    corevalue_api_key = os.getenv("COVA_API_KEY")

    # Create kernel
    kernel = sk.Kernel()

    # Add OpenAI chat completion with CoreValue AI Gateway endpoint
    kernel.add_service(
        OpenAIChatCompletion(
            service_id="corevalue-gateway",
            ai_model_id="gpt-4.1-mini",               # Any model from CoreValue registry
            api_key=corevalue_api_key,                 # Your CoreValue API key
            endpoint="https://gateway.corevalue.dev/v1"  # CoreValue AI Gateway
        )
    )
    ```

    <Callout type="info">
      The **only change** from a standard Semantic Kernel setup is adding the `endpoint` parameter. Everything else stays the same!
    </Callout>
  </Step>

  <Step title="Use the chat service normally">
    Your existing Semantic Kernel code continues to work without any changes:

    ```csharp .NET
    using Microsoft.SemanticKernel.ChatCompletion;

    // Get the chat service
    var chatService = kernel.GetRequiredService<IChatCompletionService>();

    // Create chat history
    var chatHistory = new ChatHistory();
    chatHistory.AddUserMessage("What is the capital of France?");

    // Get response
    var response = await chatService.GetChatMessageContentAsync(chatHistory);
    Console.WriteLine(response.Content);
    ```

    ```python Python
    from semantic_kernel.contents import ChatHistory

    # Get the chat service
    chat_service = kernel.get_service("corevalue-gateway")

    # Create chat history
    chat_history = ChatHistory()
    chat_history.add_user_message("What is the capital of France?")

    # Get response
    response = await chat_service.get_chat_message_content(
        chat_history=chat_history
    )
    print(response.content)
    ```
  </Step>

  <Step title="View requests in the CoreValue dashboard">
    All your Semantic Kernel requests are now visible in your [CoreValue dashboard](https://app.corevalue.dev/dashboard):

    * Request/response bodies
    * Latency metrics
    * Token usage and costs
    * Model performance analytics
    * Error tracking

    <Star />
  </Step>
</Steps>

## Migration Example [#migration-example]

Here's what migrating an existing Semantic Kernel application looks like:

### Before (Direct OpenAI) [#before-direct-openai]

```csharp
var builder = Kernel.CreateBuilder();

builder.AddOpenAIChatCompletion(
    modelId: "gpt-4o-mini",
    apiKey: openAiApiKey
);

var kernel = builder.Build();
```

### After (CoreValue AI Gateway) [#after-corevalue-ai-gateway]

```csharp
var builder = Kernel.CreateBuilder();

builder.AddOpenAIChatCompletion(
    modelId: "gpt-4.1-mini",                                // Use CoreValue model names
    apiKey: corevalueApiKey,                                 // Your CoreValue API key
    endpoint: new Uri("https://gateway.corevalue.dev/v1")  // Add this line!
);

var kernel = builder.Build();
```

That's it! Just one additional parameter and you're routing through CoreValue's AI Gateway.

## Complete Working Example [#complete-working-example]

Here's a full example that tests multiple models:

```csharp .NET
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using DotNetEnv;

// Load environment
Env.Load();
var apiKey = Environment.GetEnvironmentVariable("COVA_API_KEY");

if (string.IsNullOrEmpty(apiKey))
{
    Console.WriteLine("❌ COVA_API_KEY not found in environment");
    return;
}

Console.WriteLine("🚀 Testing multiple models through CoreValue AI Gateway\n");

// Test different models
await TestModel("gpt-4.1-mini", "OpenAI GPT-4.1 Mini");
await TestModel("anthropic/claude-opus-4-1-20250805", "Anthropic Claude Opus 4.1");
await TestModel("google/gemini-2.5-flash-lite", "Google Gemini 2.5 Flash Lite");

Console.WriteLine("\n✅ All models tested!");
Console.WriteLine("🔍 Check your dashboard: https://app.corevalue.dev/dashboard");

async Task TestModel(string modelId, string modelName)
{
    try
    {
        var builder = Kernel.CreateBuilder();

        // Configure with CoreValue AI Gateway
        builder.AddOpenAIChatCompletion(
            modelId: modelId,
            apiKey: apiKey,
            endpoint: new Uri("https://gateway.corevalue.dev/v1")
        );

        var kernel = builder.Build();
        var chatService = kernel.GetRequiredService<IChatCompletionService>();

        var chatHistory = new ChatHistory();
        chatHistory.AddUserMessage("Say hello in one sentence.");

        Console.Write($"🤖 Testing {modelName}... ");
        var response = await chatService.GetChatMessageContentAsync(chatHistory);
        Console.WriteLine("✅");
        Console.WriteLine($"   Response: {response.Content}\n");
    }
    catch (Exception ex)
    {
        Console.WriteLine("❌");
        Console.WriteLine($"   Error: {ex.Message}\n");
    }
}
```

```python Python
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.contents import ChatHistory
import os
import asyncio

# Load environment
corevalue_api_key = os.getenv("COVA_API_KEY")

if not corevalue_api_key:
    print("❌ COVA_API_KEY not found in environment")
    exit(1)

print("🚀 Testing multiple models through CoreValue AI Gateway\n")

async def test_model(model_id: str, model_name: str):
    try:
        # Create kernel
        kernel = sk.Kernel()

        # Configure with CoreValue AI Gateway
        kernel.add_service(
            OpenAIChatCompletion(
                service_id="corevalue-gateway",
                ai_model_id=model_id,
                api_key=corevalue_api_key,
                endpoint="https://gateway.corevalue.dev/v1"
            )
        )

        chat_service = kernel.get_service("corevalue-gateway")

        chat_history = ChatHistory()
        chat_history.add_user_message("Say hello in one sentence.")

        print(f"🤖 Testing {model_name}... ", end="")
        response = await chat_service.get_chat_message_content(
            chat_history=chat_history
        )
        print("✅")
        print(f"   Response: {response.content}\n")
    except Exception as ex:
        print("❌")
        print(f"   Error: {str(ex)}\n")

async def main():
    # Test different models
    await test_model("gpt-4.1-mini", "OpenAI GPT-4.1 Mini")
    await test_model("anthropic/claude-opus-4-1-20250805", "Anthropic Claude Opus 4.1")
    await test_model("google/gemini-2.5-flash-lite", "Google Gemini 2.5 Flash Lite")

    print("\n✅ All models tested!")
    print("🔍 Check your dashboard: https://app.corevalue.dev/dashboard")

if __name__ == "__main__":
    asyncio.run(main())
```

<RequestIntegration />

## Related Documentation [#related-documentation]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="AI Gateway Overview" href="/">
    Learn about CoreValue's AI Gateway features and capabilities
  </Card>

  <Card title="Provider Routing" href="/features/routing/other-providers">
    Configure intelligent routing and automatic failover
  </Card>

  <Card title="Model Registry" href="https://corevalue.dev/models">
    Browse all available models and providers
  </Card>

  <Card title="Custom Properties" href="/features/cost-attribution">
    Add metadata to track and filter your requests
  </Card>
</div>


# Vercel AI SDK (https://docs.corevalue.dev/sdks/integrations/vercel-ai-sdk)





## Introduction [#introduction]

The [Vercel AI SDK](https://ai-sdk.dev) (`ai`) works with CoreValue through its OpenAI provider: point
the provider at the CoreValue gateway and every call is metered, budgeted and attributed. Headers you
add tell CoreValue which customer, feature or end user a call was for, which is what cost per customer
and the other unit-economics reports are built from.

## Set up [#set-up]

<Steps>
  <Step title="Create a CoreValue API key">
    Sign in at [app.corevalue.dev](https://app.corevalue.dev/signin). Create a key under
    [Governance › Keys](https://app.corevalue.dev/governance?tab=keys), and add the key for each
    provider you call under [Gateway › Provider keys](https://app.corevalue.dev/gateway?tab=provider-keys).

    ```bash
    COVA_API_KEY=sk-cova-XXXXXXXXXXXXXXXX
    ```
  </Step>

  <Step title="Install the AI SDK and its OpenAI provider">
    ```bash npm
    npm install ai @ai-sdk/openai
    ```

    ```bash pnpm
    pnpm add ai @ai-sdk/openai
    ```
  </Step>

  <Step title="Point the provider at CoreValue">
    ```typescript
    import { createOpenAI } from "@ai-sdk/openai";
    import { generateText } from "ai";

    const cova = createOpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/gateway/v1",
    });

    const { text } = await generateText({
      model: cova.chat("gpt-4o-mini"),
      prompt: "Write a haiku about cost per customer.",
    });
    ```
  </Step>
</Steps>

<Callout type="warn">
  **Use `cova.chat(...)`.** Since AI SDK 5, calling the provider directly, as `cova("gpt-4o-mini")`,
  uses OpenAI's Responses API ([AI SDK OpenAI provider](https://ai-sdk.dev/providers/ai-sdk-providers/openai)).
  `cova.chat(...)` uses Chat Completions, which every provider and model prefix supports. For the
  Responses API itself, see [Responses API](/features/routing/provider-features/responses-api).
</Callout>

### Which base URL [#which-base-url]

| Base URL                                   | Use it for                                                                                                                                       |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `https://gateway.corevalue.dev/gateway/v1` | Any model, with or without a provider prefix: `gpt-4o-mini`, `groq/llama-3.3-70b-versatile`, `vercel/openai/gpt-4o-mini`. &#x2A;*Use this one.** |
| `https://gateway.corevalue.dev/v1`         | OpenAI models only. A prefixed model here is **not** routed by its prefix; the request goes to OpenAI.                                           |

The prefixes are listed in [Call other providers](/features/routing/other-providers#provider-prefixes).

## Attribute cost to customers and features [#attribute-cost-to-customers-and-features]

Add CoreValue headers to the provider, where they go on every request, or to a single call. A call's
headers are sent together with the provider's.

```typescript
import { createOpenAI } from "@ai-sdk/openai";
import { generateText } from "ai";

const cova = createOpenAI({
  apiKey: process.env.COVA_API_KEY,
  baseURL: "https://gateway.corevalue.dev/gateway/v1",
  headers: {
    "Cova-Feature-Id": "support-assistant", // the same for every call from this client
  },
});

const { text } = await generateText({
  model: cova.chat("gpt-4o-mini"),
  prompt: question,
  headers: {
    "Cova-Customer-Id": account.id, // who this call was for
    "Cova-User-Id": user.id,        // the person using your product
  },
});
```

| Header                 | What CoreValue does with it                                                                                              |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `Cova-Customer-Id`     | Cost per customer: [unit economics](/features/reports/unit-economics), customer budgets and the margin floor, chargeback |
| `Cova-Feature-Id`      | Cost per feature, on the [Spend](/features/cost-tracking/spend) page                                                     |
| `Cova-User-Id`         | Requests and spend per end user. Attribution only: no budget is set on it                                                |
| `Cova-Property-<Name>` | Any label you choose, for filtering requests, e.g. `Cova-Property-Env: production`                                       |

The team is not a header: it comes from the CoreValue API key that made the call. Every header is
described in the [Headers](/api-reference/headers) reference.

## Route through Vercel AI Gateway [#route-through-vercel-ai-gateway]

If you buy models through [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) and have no
account with each provider, keep doing so and put CoreValue in front of it:

1. Add your Vercel AI Gateway API key under
   [Gateway › Provider keys](https://app.corevalue.dev/gateway?tab=provider-keys) as **Vercel AI Gateway**.
2. Prefix Vercel's model id with `vercel/`, on the `/gateway/v1` base URL.

```typescript
const { text } = await generateText({
  model: cova.chat("vercel/openai/gpt-4o-mini"), // Vercel's id is openai/gpt-4o-mini
  prompt: question,
  headers: { "Cova-Customer-Id": account.id },
});
```

Vercel still bills you. CoreValue records each request at the model's list price on its author's own
API, which is what Vercel charges, and applies your budgets, key caps and attribution as for any
provider. More in [Call other providers](/features/routing/other-providers#vercel-ai-gateway).

<Callout type="warn">
  **Do not use the AI SDK's own Vercel AI Gateway provider** (`@ai-sdk/gateway`, the `gateway(...)`
  model helper) for these calls. It sends requests straight to Vercel, so CoreValue never sees them:
  no cost, no budgets, no attribution. Use the OpenAI provider pointed at CoreValue, as above.
</Callout>

## Examples [#examples]

### Streaming [#streaming]

Streamed calls are metered and attributed the same way.

```typescript
import { streamText } from "ai";

const result = streamText({
  model: cova.chat("gpt-4o-mini"),
  prompt: "Write a short story about a robot learning to paint.",
  maxOutputTokens: 300,
  headers: { "Cova-Customer-Id": account.id },
});

for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}
```

In a Next.js route handler, return `result.toUIMessageStreamResponse()` as usual.

### Another provider [#another-provider]

```typescript
const { text } = await generateText({
  model: cova.chat("anthropic/claude-sonnet-4-5-20250929"),
  prompt: "Explain quantum computing in two sentences.",
});
```

The provider's key must be saved under
[Gateway › Provider keys](https://app.corevalue.dev/gateway?tab=provider-keys).

### Tool calling [#tool-calling]

```typescript
import { generateText, tool } from "ai";
import { z } from "zod";

const result = await generateText({
  model: cova.chat("gpt-4o"),
  prompt: "What is the weather like in San Francisco?",
  tools: {
    getWeather: tool({
      description: "Get the weather for a location",
      inputSchema: z.object({ location: z.string().describe("The city name") }),
      execute: async ({ location }) => `It's sunny in ${location}`,
    }),
  },
});
```

<RequestIntegration />

## Related [#related]

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="Call other providers" href="/features/routing/other-providers">
    Provider prefixes, including Vercel AI Gateway
  </Card>

  <Card title="Cost attribution" href="/features/cost-attribution">
    Customers, features, end users and properties
  </Card>

  <Card title="Headers" href="/api-reference/headers">
    Every Cova-\* header, request and response
  </Card>

  <Card title="Caching" href="/features/routing/caching">
    Serve repeated requests from cache
  </Card>
</div>


# Xcode (https://docs.corevalue.dev/sdks/integrations/xcode)



This guide shows how to add CoreValue as a model provider in Xcode so your chats route through the CoreValue AI Gateway and show up in your CoreValue dashboard.

## Prerequisites [#prerequisites]

* A CoreValue account and API key
* Org/provider keys configured in CoreValue (so models can be listed)

## Steps [#steps]

1. Open Xcode Settings

   * Xcode → Settings…

   <img src="/images/integrations/xcode/xcode-settings.png" alt="Xcode Settings - Intelligence section" />

2. Add CoreValue as a model provider

   * Select the Intelligence tab
   * Click "Add a model provider…"

   <img src="/images/integrations/xcode/xcode-add-model.png" alt="Add model provider dialog in Xcode" />

   * Fill the form with:
     * URL: `https://gateway.corevalue.dev`
     * API Key: `Bearer <corevalue-api-key>`
     * API Key Header: `Authorization`
     * Description: `CoreValue` (you can name this however you like)

   <img src="/images/integrations/xcode/xcode-configure-corevalue.png" alt="Add model provider dialog in Xcode" />

3. Confirm models are available

   * After saving, Xcode should list available models from CoreValue
   * There are many models; use Favorites to pin the ones you use most

   <img src="/images/integrations/xcode/xcode-models-list.png" alt="Models list in Xcode with favorites" />

4. Start chatting and view logs in CoreValue

   * Use the chat in Xcode with your selected model
   * Open the CoreValue dashboard to see your requests, tokens, and costs

   <img src="/images/integrations/xcode/xcode-chat.png" alt="Chatting in Xcode and viewing requests in CoreValue" />

5. Switch chat model

   * In the chat widget, press the dropdown to select a new model.

   <img src="/images/integrations/xcode/xcode-chat-model-select.png" alt="Chatting in Xcode and viewing requests in CoreValue" />

## Notes [#notes]

* URL points to the CoreValue AI Gateway. Your CoreValue API key is sent via the `Authorization` header.
* If you don’t see models, verify your org/provider keys are set in CoreValue and that your key has access.


# Zapier (https://docs.corevalue.dev/sdks/integrations/zapier)





## Introduction [#introduction]

Use the CoreValue app on Zapier to generate chat completions from any supported model through the CoreValue AI Gateway. Provide your CoreValue API key, select a model, and pass your prompt data from any Zapier trigger — all with full observability in CoreValue.

<Callout type="info">
  The Zapier action uses CoreValue’s OpenAI-compatible Chat Completions API. No OpenAI or third‑party provider keys are required when using the AI Gateway.
</Callout>

## Integration Steps [#integration-steps]

<Steps>
  <Step title="Create a CoreValue API key">
    Create a CoreValue account at <a href="https://www.corevalue.dev/" target="_blank">corevalue.dev</a> and generate an
    <a href="https://app.corevalue.dev/governance?tab=keys" target="_blank">API key</a>.

    <Callout type="info">
      You can use a write-only key for logging requests. Learn more about
      <a href="/">gateway authentication</a>.
    </Callout>
  </Step>

  <Step title="Add the CoreValue app in Zapier">
    * In Zapier, create a new Zap (or edit an existing one).
    * For the Action step, search for and select "CoreValue".
    * Choose the "Chat Completion" action.
    * When prompted, connect your CoreValue account and paste your CoreValue API key.
  </Step>

  <Step title="Configure the Chat Completion action">
    * Model: pick any supported model (see <a href="https://www.corevalue.dev/models" target="_blank">model registry</a>).
    * Messages: map your trigger fields (e.g., prompt text) into the user message.

    <Callout type="info">
      The action routes through CoreValue’s AI Gateway. You can later change the target provider or model in CoreValue without updating your Zap.
    </Callout>
  </Step>

  <Step title="Test and publish your Zap">
    * Run a test to see the model’s response.
    * Publish the Zap when you’re satisfied.
  </Step>

  <Step title="Verify in CoreValue">
    Open your CoreValue dashboard and check the Requests tab to see your Zapier‑originated requests, costs, and latencies.

    <Star />
  </Step>
</Steps>

## Example Use Cases [#example-use-cases]

* Auto‑draft replies from form submissions or support tickets
* Summarize new rows in Google Sheets or Airtable
* Generate product descriptions from e‑commerce triggers

## Alternate Use cases [#alternate-use-cases]

In case you want to utilize the observability side of CoreValue (rather than just the gateway), we've got you covered!

This integration has searches and other creation actions that you could otherwise do in the CoreValue Dashboard

It also has a blank curl request, in the event that you don't find an action you want. Create what you need!

## Troubleshooting [#troubleshooting]

* Ensure your CoreValue API key is valid and has write access.
* If a request fails, review the error in the Zap run details and the corresponding request in CoreValue for provider/model‑specific messages.
* To switch models later, update the model field in the Zap action or use CoreValue routing/policies to control traffic centrally.

<RequestIntegration />


# Context editing (https://docs.corevalue.dev/features/routing/provider-features/context-editing)



Context editing enables automatic management of conversation context by intelligently clearing old tool uses and thinking blocks. This can greatly reduce costs in long-running sessions with minimal tradeoffs in context performance.

<Callout type="info">
  Context editing is currently supported for **Anthropic models only**. The configuration is ignored when routing to other providers.
</Callout>

## Why Context Editing [#why-context-editing]

<div className="grid gap-4 sm:grid-cols-3">
  <Card title="Prevent Context Overflow">
    Automatically clear old tool results before hitting context limits
  </Card>

  <Card title="Reduce Token Costs">
    Keep only relevant context, reducing input tokens on subsequent calls
  </Card>

  <Card title="Enable Long Sessions">
    Run AI agents for longer periods without manual context management
  </Card>
</div>

***

## Quick Start [#quick-start]

Enable context editing with a simple configuration. The AI Gateway handles the translation to Anthropic's native format.

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;, &#x22;bash&#x22;]">
  <Tab>
    ```typescript
    import OpenAI from "openai";

    const client = new OpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/v1",
    });

    const response = await client.chat.completions.create({
      model: "anthropic/claude-sonnet-4-20250514",
      messages: [
        { role: "system", content: "You are a helpful coding assistant." },
        { role: "user", content: "Help me debug this application..." }
        // ... many tool calls and responses
      ],
      tools: [/* your tools */],
      context_editing: {
        enabled: true
      }
    });
    ```
  </Tab>

  <Tab>
    ```python
    import os
    from openai import OpenAI

    client = OpenAI(
        api_key=os.environ.get("COVA_API_KEY"),
        base_url="https://gateway.corevalue.dev/v1",
    )

    response = client.chat.completions.create(
        model="claude-sonnet-4-20250514",
        messages=[
            {"role": "system", "content": "You are a helpful coding assistant."},
            {"role": "user", "content": "Help me debug this application..."}
            # ... many tool calls and responses
        ],
        tools=[# your tools],
        context_editing={
            "enabled": True
        }
    )
    ```
  </Tab>

  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/v1/chat/completions \
      -H "Authorization: Bearer $COVA_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "claude-sonnet-4-20250514",
        "messages": [
          {"role": "system", "content": "You are a helpful coding assistant."},
          {"role": "user", "content": "Help me debug this application..."}
        ],
        "tools": [],
        "context_editing": {
          "enabled": true
        }
      }'
    ```
  </Tab>
</Tabs>

***

## Configuration Options [#configuration-options]

The `context_editing` object supports two strategies for managing context:

### Clear Tool Uses [#clear-tool-uses]

Automatically clear old tool use results when context grows too large:

```typescript
context_editing: {
  enabled: true,
  clear_tool_uses: {
    // Trigger clearing when input tokens exceed this threshold
    trigger: 100000,

    // Keep the most recent N tool uses
    keep: 5,

    // Ensure at least this many tokens are cleared
    clear_at_least: 20000,

    // Never clear results from these tools
    exclude_tools: ["get_user_preferences", "read_config"],

    // Clear tool inputs (arguments) but keep outputs
    clear_tool_inputs: true
  }
}
```

| Parameter           | Type      | Description                             |
| ------------------- | --------- | --------------------------------------- |
| `trigger`           | number    | Token threshold to trigger clearing     |
| `keep`              | number    | Number of recent tool uses to preserve  |
| `clear_at_least`    | number    | Minimum tokens to clear when triggered  |
| `exclude_tools`     | string\[] | Tool names that should never be cleared |
| `clear_tool_inputs` | boolean   | Clear tool inputs while keeping outputs |

### Clear Thinking [#clear-thinking]

Manage thinking/reasoning blocks in multi-turn conversations:

```typescript
context_editing: {
  enabled: true,
  clear_thinking: {
    // Keep the N most recent thinking turns, or "all" to keep everything
    keep: 3
  }
}
```

| Parameter | Type            | Description                                |
| --------- | --------------- | ------------------------------------------ |
| `keep`    | number \| "all" | Number of thinking turns to keep, or "all" |

***

## Complete Example [#complete-example]

Here's a full configuration for a long-running coding agent:

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;, &#x22;bash&#x22;]">
  <Tab>
    ```typescript
    import OpenAI from "openai";

    const client = new OpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/v1",
    });

    const response = await client.chat.completions.create({
      model: "anthropic/claude-sonnet-4-20250514",
      messages: conversationHistory,
      tools: [
        {
          type: "function",
          function: {
            name: "read_file",
            description: "Read a file from the filesystem",
            parameters: {
              type: "object",
              properties: {
                path: { type: "string", description: "File path to read" }
              },
              required: ["path"]
            }
          }
        },
        {
          type: "function",
          function: {
            name: "write_file",
            description: "Write content to a file",
            parameters: {
              type: "object",
              properties: {
                path: { type: "string" },
                content: { type: "string" }
              },
              required: ["path", "content"]
            }
          }
        },
        {
          type: "function",
          function: {
            name: "run_command",
            description: "Execute a shell command",
            parameters: {
              type: "object",
              properties: {
                command: { type: "string" }
              },
              required: ["command"]
            }
          }
        }
      ],
      reasoning_effort: "medium",
      context_editing: {
        enabled: true,
        clear_tool_uses: {
          trigger: 150000,        // Trigger at 150k tokens
          keep: 10,               // Keep last 10 tool uses
          clear_at_least: 50000,  // Clear at least 50k tokens
          exclude_tools: ["read_file"],  // Always keep file reads
          clear_tool_inputs: true  // Clear large file contents from inputs
        },
        clear_thinking: {
          keep: 5  // Keep last 5 thinking blocks
        }
      },
      max_completion_tokens: 16000
    });
    ```
  </Tab>

  <Tab>
    ```python
    response = client.chat.completions.create(
        model="claude-sonnet-4-20250514",
        messages=conversation_history,
        tools=[
            {
                "type": "function",
                "function": {
                    "name": "read_file",
                    "description": "Read a file from the filesystem",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "path": {"type": "string", "description": "File path to read"}
                        },
                        "required": ["path"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "write_file",
                    "description": "Write content to a file",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "path": {"type": "string"},
                            "content": {"type": "string"}
                        },
                        "required": ["path", "content"]
                    }
                }
            }
        ],
        reasoning_effort="medium",
        context_editing={
            "enabled": True,
            "clear_tool_uses": {
                "trigger": 150000,
                "keep": 10,
                "clear_at_least": 50000,
                "exclude_tools": ["read_file"],
                "clear_tool_inputs": True
            },
            "clear_thinking": {
                "keep": 5
            }
        },
        max_completion_tokens=16000
    )
    ```
  </Tab>

  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/v1/chat/completions \
      -H "Authorization: Bearer $COVA_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "claude-sonnet-4-20250514",
        "messages": [...],
        "tools": [...],
        "reasoning_effort": "medium",
        "context_editing": {
          "enabled": true,
          "clear_tool_uses": {
            "trigger": 150000,
            "keep": 10,
            "clear_at_least": 50000,
            "exclude_tools": ["read_file"],
            "clear_tool_inputs": true
          },
          "clear_thinking": {
            "keep": 5
          }
        },
        "max_completion_tokens": 16000
      }'
    ```
  </Tab>
</Tabs>

***

## Responses API Support [#responses-api-support]

Context editing works with both the Chat Completions API and the [Responses API](/features/routing/provider-features/responses-api):

```typescript
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.COVA_API_KEY,
  baseURL: "https://gateway.corevalue.dev/v1",
});

const response = await client.responses.create({
  model: "anthropic/claude-sonnet-4-20250514",
  input: conversationInput,
  tools: [/* your tools */],
  context_editing: {
    enabled: true,
    clear_tool_uses: {
      trigger: 100000,
      keep: 5
    }
  }
});
```

***

## Default Behavior [#default-behavior]

When `context_editing.enabled` is `true` but no specific strategies are provided, the AI Gateway uses sensible defaults:

```typescript
// Minimal configuration
context_editing: {
  enabled: true
}

// Equivalent to
context_editing: {
  enabled: true,
  clear_tool_uses: {}  // Uses Anthropic defaults
}
```

***

## Related Features [#related-features]

* [Reasoning](/features/routing/provider-features/reasoning) - Extended thinking that benefits from context editing
* [Prompt Caching](/features/routing/provider-features/prompt-caching) - Cache static context for cost savings
* Sessions - Track and analyze long-running agent sessions

<Card title="Learn More" href="https://docs.anthropic.com/en/docs/build-with-claude/context-editing">
  Anthropic Context Editing Documentation
</Card>


# Image generation (https://docs.corevalue.dev/features/routing/provider-features/image-generation)



CoreValue's AI Gateway supports image generation through models with native image output capabilities. Use the unified OpenAI-compatible API to generate images - the Gateway handles provider-specific translations automatically.

<Callout type="info">
  Image generation is currently supported for &#x2A;*Nano Banana Pro (gemini-3-pro-image-preview)** via Google AI Studio. Support for additional providers will be added in future updates.
</Callout>

***

## Quick Start [#quick-start]

<Tabs items="[&#x22;Chat Completions&#x22;, &#x22;Responses API&#x22;]">
  <Tab>
    ```typescript
    import OpenAI from "openai";

    const client = new OpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/v1",
    });

    const response = await client.chat.completions.create({
      model: "google/gemini-3-pro-image-preview",
      messages: [
        { role: "user", content: "Generate an image of a sunset over mountains" }
      ],
      max_tokens: 8192
    });

    // Access generated images
    const images = response.choices[0].message.images;
    ```
  </Tab>

  <Tab>
    ```typescript
    import OpenAI from "openai";

    const client = new OpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/v1",
    });

    const response = await client.responses.create({
      model: "google/gemini-3-pro-image-preview",
      input: "Generate an image of a sunset over mountains",
      max_output_tokens: 8192
    });

    // Access generated images from output
    const messageOutput = response.output.find(item => item.type === "message");
    const imageContent = messageOutput?.content.find(c => c.type === "output_image");
    ```
  </Tab>
</Tabs>

***

## Configuration [#configuration]

To enable image generation:

1. Set the `model` to one that supports image output (currently `google/gemini-3-pro-image-preview`, also known as Nano Banana Pro)
2. Optionally configure `image_generation` to control aspect ratio and size

<Tabs items="[&#x22;Chat Completions&#x22;, &#x22;Responses API&#x22;]">
  <Tab>
    ```typescript
    {
      model: "google/gemini-3-pro-image-preview",
      messages: [...],
      image_generation: {
        aspect_ratio: "16:9",
        image_size: "2K"
      }
    }
    ```
  </Tab>

  <Tab>
    ```typescript
    {
      model: "google/gemini-3-pro-image-preview",
      input: "...",
      image_generation: {
        aspect_ratio: "16:9",
        image_size: "2K"
      }
    }
    ```
  </Tab>
</Tabs>

### image\_generation [#image_generation]

| Parameter      | Type   | Description                                            |
| -------------- | ------ | ------------------------------------------------------ |
| `aspect_ratio` | string | Image aspect ratio (e.g., `"16:9"`, `"1:1"`, `"9:16"`) |
| `image_size`   | string | Image resolution (e.g., `"2K"`, `"1K"`)                |

<Callout type="info">
  The `image_generation` field is optional. If omitted, the model uses default settings. However, if you specify `image_generation`, both `aspect_ratio` and `image_size` are required.
</Callout>

***

## Handling Responses [#handling-responses]

### Chat Completions [#chat-completions]

<Tabs items="[&#x22;Streaming&#x22;, &#x22;Non-Streaming&#x22;]">
  <Tab>
    When streaming, images arrive in chunks via the `images` delta field:

    ```json
    // Image chunks arrive in delta
    {
      "choices": [{
        "delta": {
          "images": [{
            "type": "image_url",
            "image_url": {
              "url": "data:image/png;base64,iVBORw0KGgo..."
            }
          }]
        }
      }]
    }
    ```
  </Tab>

  <Tab>
    Non-streaming responses include images in the message:

    ```json
    {
      "id": "chatcmpl-abc123",
      "object": "chat.completion",
      "model": "gemini-3-pro-image-preview",
      "choices": [{
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "Here's the image you requested:",
          "images": [{
            "type": "image_url",
            "image_url": {
              "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
            }
          }]
        },
        "finish_reason": "stop"
      }],
      "usage": {
        "prompt_tokens": 12,
        "completion_tokens": 1024,
        "total_tokens": 1036
      }
    }
    ```
  </Tab>
</Tabs>

### Responses API [#responses-api]

<Tabs items="[&#x22;Streaming&#x22;, &#x22;Non-Streaming&#x22;]">
  <Tab>
    Streaming events follow the Responses API format:

    ```json
    // Content part added for image
    {
      "type": "response.content_part.added",
      "item_id": "msg_abc123",
      "output_index": 0,
      "content_index": 0,
      "part": {
        "type": "output_image",
        "image_url": ""
      }
    }

    // Content part done with full image
    {
      "type": "response.content_part.done",
      "item_id": "msg_abc123",
      "output_index": 0,
      "content_index": 0,
      "part": {
        "type": "output_image",
        "image_url": "data:image/png;base64,iVBORw0KGgo..."
      }
    }
    ```
  </Tab>

  <Tab>
    ```json
    {
      "id": "resp_abc123",
      "object": "response",
      "status": "completed",
      "model": "gemini-3-pro-image-preview",
      "output": [
        {
          "id": "msg_abc123",
          "type": "message",
          "status": "completed",
          "role": "assistant",
          "content": [
            {
              "type": "output_text",
              "text": "Here's the image you requested:"
            },
            {
              "type": "output_image",
              "image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
            }
          ]
        }
      ],
      "usage": {
        "input_tokens": 12,
        "output_tokens": 1024
      }
    }
    ```
  </Tab>
</Tabs>

***

## Supported Models [#supported-models]

| Model                               | Provider Route   | Description                                                              |
| ----------------------------------- | ---------------- | ------------------------------------------------------------------------ |
| `google/gemini-3-pro-image-preview` | Google AI Studio | Nano Banana Pro - Google's multimodal model with native image generation |

***

## Related [#related]

* [Reasoning](/features/routing/provider-features/reasoning) - Enable reasoning for complex tasks
* [Responses API](/features/routing/provider-features/responses-api) - Alternative API format with image support


# Prompt caching (https://docs.corevalue.dev/features/routing/provider-features/prompt-caching)



Prompt caching allows you to cache frequently-used context (system prompts, examples, documents) and reuse it across multiple requests at significantly reduced costs.

## Why Prompt Caching [#why-prompt-caching]

<div className="grid gap-4 sm:grid-cols-3">
  <Card title="Reduce Token Costs">
    Cached prompts are processed at significantly reduced rates by providers (up to 90% savings)
  </Card>

  <Card title="Faster Processing">
    Providers skip re-processing cached prompt segments for faster response times
  </Card>

  <Card title="Automatic Optimization">
    Works out-of-the-box with OpenAI compatible AI Gateway across all providers
  </Card>
</div>

***

## OpenAI and Compatible Providers [#openai-and-compatible-providers]

**Automatic caching** for prompts over 1024 tokens. Use the `prompt_cache_key` parameter for better cache hit control.

**Compatible providers:** OpenAI, Grok, Groq, Deepseek, Moonshot AI

### Quick Start [#quick-start]

```typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://gateway.corevalue.dev/v1",
  apiKey: process.env.COVA_API_KEY,
});

const response = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [
    {
      role: "system",
      content: "Very long system prompt that will be automatically cached..." // 1024+ tokens
    },
    {
      role: "user", 
      content: "What is machine learning?"
    }
  ],
  prompt_cache_key: `doc-analysis-${documentId}` // Optional: control caching keys
});
```

### Pricing [#pricing]

OpenAI charges standard rates for cache writes and offers significant discounts for cache reads. Exact pricing varies by model.

<div className="grid gap-4 sm:grid-cols-2">
  <Card title="CoreValue Model Registry" href="https://corevalue.dev/models">
    View supported models and their caching capabilities
  </Card>

  <Card title="OpenAI Prompt Caching Documentation" href="https://platform.openai.com/docs/guides/prompt-caching">
    Official OpenAI prompt caching guide
  </Card>
</div>

***

## Anthropic (Claude) [#anthropic-claude]

Anthropic provides advanced caching with **cache control breakpoints** (up to 4 per request) and TTL control.

### Using OpenAI SDK with CoreValue Types [#using-openai-sdk-with-corevalue-types]

The `@cova/gateway` SDK extends OpenAI types to support Anthropic's cache control through the OpenAI-compatible interface:

```bash
npm install @cova/gateway
```

```typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://gateway.corevalue.dev/v1",
  apiKey: process.env.COVA_API_KEY,
});

const response = await client.chat.completions.create({
  model: "anthropic/claude-3-5-haiku-20241022",
  messages: [
    {
      role: "system",
      content: "You are a helpful assistant...",
      cache_control: {
        type: "ephemeral",
        ttl: "1h"
      }
    },
    {
        role: "assistant",
        content: "Example assistant message.",
        cache_control: { type: "ephemeral" }
    },
    {
      role: "user",
      content: [
        {
          type: "text",
          text: "This content will be cached.",
          cache_control: {
            type: "ephemeral",
            ttl: "5m"
          }
        },
        {
          type: "image_url",
          image_url: {
            url: "https://example.com/image.jpg",
            detail: "low"
          },
          cache_control: { type: "ephemeral" }
        }
      ]
    }
  ],
  temperature: 0.7
});
```

### Cache Key Mapping [#cache-key-mapping]

Anthropic uses `user_id` as a cache key on their servers. When using the OpenAI-compatible AI Gateway, these parameters automatically map to Anthropic's `user_id`:

* `prompt_cache_key`
* `safety_identifier`
* `user`

```typescript
const response = await client.chat.completions.create({
  model: "anthropic/claude-3-5-haiku-20241022",
  messages: [/* your messages */],
  prompt_cache_key: "doc-analysis-v1", // Maps to Anthropic's user_id for cache keying
  cache_control: {
    type: "ephemeral", 
    ttl: "1h"
  }
});
```

<Callout type="info">
  **Current Limitation**: Anthropic cache control is currently enabled for caching messages only. Support for caching tools is coming soon.
</Callout>

### Pricing Structure [#pricing-structure]

Anthropic uses a simple multiplier-based pricing model for prompt caching.

| Operation            | Multiplier | Example (Claude Sonnet @ $3/MTok) |
| -------------------- | ---------- | --------------------------------- |
| Cache Read           | 0.1×       | $0.30/MTok                        |
| Cache Write (5 min)  | 1.25×      | $3.75/MTok                        |
| Cache Write (1 hour) | 2.0×       | $6.00/MTok                        |

### Key Points [#key-points]

* **TTL Options**: 5 minutes or 1 hour
* **Providers**: Available on Anthropic API, Vertex AI, and AWS Bedrock
* **Limitation**: Vertex AI and Bedrock only support 5-minute caching
* **Minimum**: 1024 tokens for most models

### Calculation Example [#calculation-example]

```
Base input price: $3/MTok
5-min cache write: $3 × 1.25 = $3.75/MTok
1-hour cache write: $3 × 2.0 = $6.00/MTok
Cache read: $3 × 0.1 = $0.30/MTok
```

<Card title="Learn More" href="https://docs.claude.com/en/docs/build-with-claude/prompt-caching">
  Anthropic Prompt Caching Documentation
</Card>

***

## Google Gemini [#google-gemini]

Google uses a multiplier plus storage cost model for context caching.

### Pricing Structure [#pricing-structure-1]

| Operation   | Multiplier | Storage Cost  |
| ----------- | ---------- | ------------- |
| Cache Read  | 0.25×      | N/A           |
| Cache Write | 1.0×       | + Storage fee |

**Storage Rates:**

* Gemini 2.5 Pro: $4.50/MTok/hour
* Gemini 2.5 Flash: $1.00/MTok/hour
* Gemini 2.5 Flash-Lite: $1.00/MTok/hour

### Key Points [#key-points-1]

* **TTL**: 5 minutes only
* **Cache Types**: Implicit (automatic) and Explicit (manual)
* **Minimum**: 1024 tokens (Flash), 2048 tokens (Pro)
* **Discount**: 75% off input costs for cache reads

### Calculation Example [#calculation-example-1]

For Gemini 2.5 Pro (≤200K tokens):

```
Base input price: $1.25/MTok
Storage rate: $4.50/MTok/hour

Cache write (5 min):
- Input cost: $1.25 × 1.0 = $1.25
- Storage cost: $4.50 × (5/60) = $0.375
- Total: $1.625/MTok

Cache read: $1.25 × 0.25 = $0.31/MTok
```

### Tiered Pricing [#tiered-pricing]

Gemini 2.5 Pro has different rates for larger contexts:

| Context Size | Input Price | Cache Read  | Cache Write (5 min) |
| ------------ | ----------- | ----------- | ------------------- |
| ≤200K tokens | $1.25/MTok  | $0.31/MTok  | $1.625/MTok         |
| >200K tokens | $2.50/MTok  | $0.625/MTok | $2.875/MTok         |


# Reasoning models (https://docs.corevalue.dev/features/routing/provider-features/reasoning)



CoreValue's AI Gateway provides a unified interface for reasoning across providers. Use the same parameters regardless of provider - the Gateway handles the translation automatically.

***

## Quick Start [#quick-start]

<Tabs items="[&#x22;Chat Completions&#x22;, &#x22;Responses API&#x22;]">
  <Tab>
    ```typescript
    import OpenAI from "openai";

    const client = new OpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/v1",
    });

    const response = await client.chat.completions.create({
      model: "anthropic/claude-sonnet-4-20250514",
      messages: [
        { role: "user", content: "What is the sum of the first 100 prime numbers?" }
      ],
      reasoning_effort: "medium",
      max_completion_tokens: 16000
    });
    ```
  </Tab>

  <Tab>
    ```typescript
    import OpenAI from "openai";

    const client = new OpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/v1",
    });

    const response = await client.responses.create({
      model: "anthropic/claude-sonnet-4-20250514",
      input: "What is the sum of the first 100 prime numbers?",
      reasoning: {
        effort: "medium"
      }
    });
    ```
  </Tab>
</Tabs>

***

## Configuration [#configuration]

<Tabs items="[&#x22;Chat Completions&#x22;, &#x22;Responses API&#x22;]">
  <Tab>
    ```typescript
    {
      reasoning_effort: "low" | "medium" | "high",
      reasoning_options: {
        budget_tokens: 8000  // Optional token budget
      }
    }
    ```
  </Tab>

  <Tab>
    ```typescript
    {
      reasoning: {
        effort: "low" | "medium" | "high"
      },
      reasoning_options: {
        budget_tokens: 8000  // Optional token budget
      }
    }
    ```
  </Tab>
</Tabs>

### reasoning\_effort [#reasoning_effort]

| Level    | Description                         |
| -------- | ----------------------------------- |
| `low`    | Light reasoning for simple tasks    |
| `medium` | Balanced reasoning                  |
| `high`   | Deep reasoning for complex problems |

<Callout type="info">
  For Anthropic models, the default is 4096 max completion tokens with 2048 budget reasoning tokens.
</Callout>

### reasoning\_options.budget\_tokens [#reasoning_optionsbudget_tokens]

The `budget_tokens` parameter sets the maximum number of tokens the model can use for reasoning.

<Callout type="warn">
  **For Google (Gemini) models:** `reasoning_effort` is **required** to enable thinking. Passing `budget_tokens` alone will **not** enable reasoning - you must also specify `reasoning_effort`.
</Callout>

```typescript
// ✅ Correct: reasoning_effort enables thinking, budget_tokens limits it
{
  reasoning_effort: "high",
  reasoning_options: { budget_tokens: 4096 }
}

// ❌ Incorrect for Gemini: budget_tokens alone does nothing
{
  reasoning_options: { budget_tokens: 4096 }  // Reasoning will be disabled
}
```

***

## Handling Responses [#handling-responses]

### Chat Completions [#chat-completions]

<Tabs items="[&#x22;Streaming&#x22;, &#x22;Non-Streaming&#x22;]">
  <Tab>
    When streaming, reasoning content arrives in chunks via the `reasoning` delta field, followed by content, and finally `reasoning_details` with the finish reason:

    ```json
    // Reasoning chunks arrive first
    {
      "choices": [{
        "delta": { "reasoning": "Let me think about this..." }
      }]
    }

    // Then content chunks
    {
      "choices": [{
        "delta": { "content": "The answer is 42." }
      }]
    }

    // Final chunk includes reasoning_details with signature
    {
      "choices": [{
        "delta": {
          "reasoning_details": [{
            "thinking": "The user is asking for...",
            "signature": "EpICCkYIChgCKkCfWt1pnGxEcz48yQJvie3ppkXZ8ryd..."
          }]
        },
        "finish_reason": "stop"
      }]
    }
    ```
  </Tab>

  <Tab>
    Non-streaming responses include the full reasoning in the message:

    ```json
    {
      "id": "msg_01S1QpjYur8kLeEVKVoKxdTP",
      "object": "chat.completion",
      "model": "claude-haiku-4-5-20251001",
      "choices": [{
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "Why don't scientists trust atoms?\n\nBecause they make up everything!",
          "reasoning": "The user is asking for a very short joke. I should provide something quick, light, and funny...",
          "reasoning_details": [{
            "thinking": "The user is asking for a very short joke...",
            "signature": "Ev8DCkYIChgCKkBeHyembBdwl8C/a/8luinDP0w5/oQP..."
          }]
        },
        "finish_reason": "stop"
      }],
      "usage": {
        "prompt_tokens": 58,
        "completion_tokens": 108,
        "total_tokens": 166
      }
    }
    ```
  </Tab>
</Tabs>

### Responses API [#responses-api]

<Tabs items="[&#x22;Streaming&#x22;, &#x22;Non-Streaming (OpenAI)&#x22;, &#x22;Non-Streaming (Anthropic)&#x22;]">
  <Tab>
    Streaming events follow the Responses API format:

    ```json
    // Reasoning summary text delta
    {
      "type": "response.reasoning_summary_text.delta",
      "item_id": "rs_0ab50bce3156357b...",
      "output_index": 0,
      "summary_index": 0,
      "delta": "Let me think about this..."
    }

    // Reasoning item complete
    {
      "type": "response.output_item.done",
      "output_index": 0,
      "item": {
        "id": "rs_0ab50bce3156357b...",
        "type": "reasoning",
        "summary": [{
          "type": "summary_text",
          "text": "**Crafting the response**\n\nThe user wants..."
        }]
      }
    }
    ```
  </Tab>

  <Tab>
    ```json
    {
      "id": "resp_038bfaf6e50f1c45...",
      "object": "response",
      "status": "completed",
      "model": "gpt-5-mini-2025-08-07",
      "output": [
        {
          "id": "rs_038bfaf6e50f1c45...",
          "type": "reasoning",
          "summary": [{
            "type": "summary_text",
            "text": "**Generating programming jokes**\n\nThe user wants a short joke..."
          }]
        },
        {
          "id": "msg_038bfaf6e50f1c45...",
          "type": "message",
          "status": "completed",
          "role": "assistant",
          "content": [{
            "type": "output_text",
            "text": "To understand recursion, you must first understand recursion."
          }]
        }
      ],
      "usage": {
        "input_tokens": 17,
        "output_tokens": 336,
        "output_tokens_details": {
          "reasoning_tokens": 320
        }
      }
    }
    ```
  </Tab>

  <Tab>
    Anthropic responses include `encrypted_content` for reasoning validation:

    ```json
    {
      "id": "msg_017G4K2w5s6zEn3KZ6jp455j",
      "object": "response",
      "status": "completed",
      "model": "claude-haiku-4-5-20251001",
      "output": [
        {
          "id": "rs_msg_017G4K2w5s6zEn3KZ6jp455j_0",
          "type": "reasoning",
          "summary": [{
            "type": "summary_text",
            "text": "The user wants me to tell a short joke about programming..."
          }],
          "encrypted_content": "EuYGCkYIChgCKkBxEozbYO/Z5AL2tlDHwBHcBEOG..."
        },
        {
          "id": "msg_msg_017G4K2w5s6zEn3KZ6jp455j",
          "type": "message",
          "status": "completed",
          "role": "assistant",
          "content": [{
            "type": "output_text",
            "text": "Why do programmers prefer dark mode?\n\nBecause light attracts bugs!"
          }]
        }
      ],
      "usage": {
        "input_tokens": 47,
        "output_tokens": 294
      }
    }
    ```
  </Tab>
</Tabs>

<Callout type="info">
  Anthropic models always return `encrypted_content` (signatures) in reasoning items. These signatures validate the reasoning chain and are required for multi-turn conversations. Other providers like OpenAI can optionally return signatures when configured.
</Callout>

***

## Related [#related]

* [Responses API](/features/routing/provider-features/responses-api) - Alternative API format with reasoning support
* [Context Editing](/features/routing/provider-features/context-editing) - Manage context in long reasoning sessions


# Responses API (https://docs.corevalue.dev/features/routing/provider-features/responses-api)



The Responses API is OpenAI's newer interface for conversational AI that supports advanced features like reasoning, tool use, and streaming. CoreValue's AI Gateway supports the Responses API format for both OpenAI and Anthropic models.

## Quick Start [#quick-start]

Use your CoreValue API key and the AI Gateway base URL. Then call the OpenAI SDK's `responses.create` method as usual.

<Tabs items="[&#x22;TypeScript&#x22;, &#x22;Python&#x22;, &#x22;bash&#x22;]">
  <Tab>
    ```typescript
    import OpenAI from "openai";

    const client = new OpenAI({
      apiKey: process.env.COVA_API_KEY,
      baseURL: "https://gateway.corevalue.dev/v1",
    });

    const response = await client.responses.create({
      model: "gpt-5",
      input: "Write a one-sentence bedtime story about a unicorn.",
    });

    console.log(response.output_text);
    ```
  </Tab>

  <Tab>
    ```python
    import os
    from openai import OpenAI

    client = OpenAI(
        api_key=os.environ.get("COVA_API_KEY"),
        base_url="https://gateway.corevalue.dev/v1",
    )

    response = client.responses.create(
        model="gpt-5",
        input="Write a one-sentence bedtime story about a unicorn.",
    )

    print(response.output_text)
    ```
  </Tab>

  <Tab>
    ```bash
    curl https://gateway.corevalue.dev/v1/responses \
      -H "Authorization: Bearer $COVA_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "gpt-5",
        "input": "Write a one-sentence bedtime story about a unicorn."
      }'
    ```
  </Tab>
</Tabs>

<Callout type="info">
  For Chat Completions usage and more background on the AI Gateway, see the
  [AI Gateway Overview](/).
</Callout>

## Parameter Stripping (M2 0.5) [#parameter-stripping-m2-05]

The gateway runs a `paramStripper` on Responses API requests to remove parameters that are not supported by the target provider before forwarding. A `NEVER_STRIP` denylist protects critical parameters (such as `model`, `input`, `stream`, and `reasoning`) so they are always preserved regardless of provider.

<Callout type="info">
  The `NEVER_STRIP` denylist ensures that essential Responses API parameters are never stripped during provider translation. Provider-specific unsupported fields are dropped silently; check the `Cova-Translation-Warning` response header (in translated mode) to see which fields were dropped.
</Callout>

## References [#references]

* OpenAI Responses guide: [https://platform.openai.com/docs/guides/text](https://platform.openai.com/docs/guides/text)
* CoreValue AI Gateway overview: [https://docs.corevalue.dev/gateway/overview](https://docs.corevalue.dev/gateway/overview)
