# Cairva documentation > Cairva is the private OTC market for AI inference. Buyers send private requests for inference capacity to authorized suppliers, negotiate versioned terms, sign a capacity agreement, and call the resulting allocation through an OpenAI-compatible API at /v1 that meters every request against the agreement. Today the only route is a sandbox route serving an open-weight model. No live commercial supply is listed yet. --- # What is Cairva > Cairva is a private market for AI inference. Buyers negotiate capacity with authorized suppliers and call the agreement through an OpenAI-compatible API. Source: https://cairva.xyz/docs You describe the inference capacity you need, verified suppliers quote you privately, and the version you accept becomes an **allocation**: a fixed token allowance, delivery window and set of rate limits that your code calls through an OpenAI-compatible API. You don't buy credits from a price list. You negotiate an agreement for one model, a token allowance, a window and limits, and the gateway enforces exactly those terms on every request. > [!NOTE] > Cairva is in its Genesis phase. The only route open today is a **sandbox** route serving the open-weight model `gemma4:e2b` through Ollama on Cairva's development infrastructure. The full deal flow runs on it, but no money moves and no live commercial supply is listed yet. See [Sandbox](https://cairva.xyz/docs/sandbox). ## What you can do here - **Buy capacity.** Send a [private request](https://cairva.xyz/docs/requests-and-quotes) to the verified suppliers you choose, [negotiate](https://cairva.xyz/docs/negotiation) versioned terms, and accept one exact version. - **Run it.** Create an [API key](https://cairva.xyz/docs/api-keys) for the allocation, point an OpenAI SDK at Cairva, and call [chat completions](https://cairva.xyz/docs/chat-completions). - **Track it.** Read the [current allocation](https://cairva.xyz/docs/allocation-status) and per-request [usage events](https://cairva.xyz/docs/usage), the same numbers the supplier sees. - **Supply it.** [Become a supplier](https://cairva.xyz/docs/become-a-supplier), list [inventory lots](https://cairva.xyz/docs/inventory) on a route Cairva can reach, and quote buyers from them. ## The API in one minute The gateway lives under one base URL and speaks the OpenAI chat completions format. A key is bound to one allocation, so it can only call that allocation's model and spend that allocation's allowance. | Method | Path | Scope | What it does | | --- | --- | --- | --- | | `POST` | `/v1/chat/completions` | `inference` | Runs a chat completion, streamed or not, and meters it | | `GET` | `/v1/models` | `inference` | Lists the one model your key can call | | `GET` | `/v1/allocations/current` | `usage:read` | Quota, usage, holds, remaining allowance and limits | | `GET` | `/v1/usage` | `usage:read` | Metered usage events, newest first | ```bash curl https://cairva.xyz/v1/chat/completions \ -H "Authorization: Bearer $CAIRVA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "gemma4:e2b", "max_tokens": 200, "messages": [{"role": "user", "content": "Say hello in five words."}]}' ``` Every response carries a `cairva` object with the request id, the allocation id and the allowance left after the request. Read [Chat completions](https://cairva.xyz/docs/chat-completions) for the full shape. ## Where to go next - New here: follow the [Quickstart](https://cairva.xyz/docs/quickstart). - Want the model of the market first: read [How Cairva works](https://cairva.xyz/docs/how-cairva-works). - Migrating OpenAI code: check [OpenAI compatibility](https://cairva.xyz/docs/compatibility) before you switch the base URL. --- # Quickstart > Go from a private request to your first metered chat completion with an OpenAI SDK. Source: https://cairva.xyz/docs/quickstart This walks from nothing to a metered chat completion. On the sandbox route today the whole flow takes a few minutes and costs nothing, because sandbox agreements settle with no charge. ## 1. Get an allocation An API key only works against an allocation, and an allocation only exists after an agreement activates. In the Cairva workspace: 1. [Sign in](https://cairva.xyz/sign-in) and create a buyer organization. 2. Open **Requests** and send a private request for the model, token volume and delivery window you need. Pick at least one verified supplier, or request terms directly on a lot from the [market](https://cairva.xyz/market). 3. When a supplier issues a firm quote, review it in the deal room and accept that version. Only an organization admin can accept. 4. The supplier countersigns. For a live agreement you pay the invoice and Cairva operations confirm it. Sandbox agreements skip payment. 5. Activation checks run and the allocation goes live, or becomes `ready` if its window hasn't opened yet. The full sequence is in [How Cairva works](https://cairva.xyz/docs/how-cairva-works). ## 2. Create a key Open **API keys** in the workspace, choose the allocation, give the key a name and pick its scopes. Use `inference` to call models and `usage:read` to read the allocation and usage. The secret is shown once. Store it as an environment variable on the machine that will make requests: ```bash export CAIRVA_API_KEY="cv_test_XXXXXXXX_..." ``` Sandbox allocations get `cv_test_` keys and live allocations get `cv_live_` keys. See [API keys](https://cairva.xyz/docs/api-keys). ## 3. Check what the key can call `GET /v1/models` returns the single model your allocation covers. That id is the only value `model` can take. ```bash curl curl https://cairva.xyz/v1/models \ -H "Authorization: Bearer $CAIRVA_API_KEY" ``` ```javascript JavaScript // npm install openai import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.CAIRVA_API_KEY, baseURL: "https://cairva.xyz/v1", }); const { data } = await client.models.list(); console.log(data[0].id); // the one model your allocation covers ``` ```python Python # pip install openai import os from openai import OpenAI client = OpenAI(api_key=os.environ["CAIRVA_API_KEY"], base_url="https://cairva.xyz/v1") print(client.models.list().data[0].id) # the one model your allocation covers ``` ## 4. Send a request ```bash curl curl https://cairva.xyz/v1/chat/completions \ -H "Authorization: Bearer $CAIRVA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gemma4:e2b", "messages": [{"role": "user", "content": "Name three uses for a token allowance."}], "max_tokens": 300 }' ``` ```javascript JavaScript const completion = await client.chat.completions.create({ model: "gemma4:e2b", messages: [{ role: "user", content: "Name three uses for a token allowance." }], max_tokens: 300, }); console.log(completion.choices[0].message.content); console.log(completion.usage); console.log(completion.cairva); // request id, allocation id, remaining allowance ``` ```python Python completion = client.chat.completions.create( model="gemma4:e2b", messages=[{"role": "user", "content": "Name three uses for a token allowance."}], max_tokens=300, ) print(completion.choices[0].message.content) print(completion.usage) print(completion.model_extra["cairva"]) # request id, allocation id, remaining allowance ``` Replace `gemma4:e2b` with whatever `/v1/models` returned for your key. Any other value fails with `model_not_permitted`. > [!TIP] > Set `max_tokens` to what you actually need. Cairva holds that many output tokens from your allowance while the request runs, and caps it at what's left. See [Rate limits and holds](https://cairva.xyz/docs/rate-limits). ## 5. Watch the meter ```bash curl https://cairva.xyz/v1/allocations/current \ -H "Authorization: Bearer $CAIRVA_API_KEY" ``` `remaining` drops by the tokens the provider reported for your request. Each request also appears in [`GET /v1/usage`](https://cairva.xyz/docs/usage) once its usage is booked. ## If something fails | You see | Likely cause | | --- | --- | | `401 unauthenticated` | The key is missing, mistyped or revoked | | `403 scope_missing` | The key doesn't have `inference` (for completions) or `usage:read` (for allocation and usage) | | `403 model_not_permitted` | `model` isn't the id from `/v1/models` | | `403 allocation_not_started` | The delivery window hasn't opened | | `502 upstream_error` | The route didn't complete the request. Nothing was charged | Every code is listed in [Errors](https://cairva.xyz/docs/errors). --- # How Cairva works > Requests, quotes, agreements and allocations: the four objects every deal moves through, and who can see each one. Source: https://cairva.xyz/docs/how-cairva-works Every deal on Cairva moves through four objects. Each one has a short reference you'll see across the workspace, its own states, and a clear owner. | Object | Reference | Created when | Owned by | | --- | --- | --- | --- | | Request | `RFQ-…` | A buyer drafts what they need | Buyer | | Deal and quote versions | `DEAL-…` | A supplier opens a deal room on a request | Both parties | | Agreement | `AGR-…` | The buyer accepts one supplier-issued version | Both parties | | Allocation | `ALC-…` | The agreement is created, then activated | Buyer uses it, supplier delivers it | ## Request The buyer describes the model, input and output tokens, delivery window, capacity class, rate limits, region and anything else that matters, such as latency class, privacy requirements or a budget. They send it to the verified suppliers they pick. A request created from a market lot automatically includes that lot's supplier. A request is visible to the buyer, the invited suppliers and Cairva operations, and nobody else. Drafts stay private to the buyer until they're sent. Details: [Requests and quotes](https://cairva.xyz/docs/requests-and-quotes). ## Quote and negotiation An invited supplier opens a private deal room on the request. They can message the buyer, then issue a **firm quote** built from one of their active inventory lots. Cairva checks the terms fit inside that lot: same model and capacity class, window inside the lot's window, limits the lot can enforce, region and enough unreserved capacity. Either side can counter. Every proposal is a new, immutable, numbered version with the complete terms and a SHA-256 hash of them. Details: [Negotiation](https://cairva.xyz/docs/negotiation). ## Agreement The buyer accepts one specific supplier-issued version by echoing its number and terms hash. In the same transaction Cairva reserves the capacity on the lot, creates the agreement, and creates an allocation in the `reserved` state. Other open quotes on the request close. The supplier countersigns. A live agreement then waits for payment against an invoice, confirmed by Cairva operations. A sandbox agreement skips payment. Activation checks run, and when they all pass the agreement becomes `active`. Details: [Agreements](https://cairva.xyz/docs/agreements). ## Allocation The allocation is the agreement in its runnable form: the contracted input and output tokens, the delivery window and three rate limits. The buyer creates API keys against it and calls the [gateway](https://cairva.xyz/docs/chat-completions). Every request is metered against it, and the buyer and supplier see the same usage events. It ends when the allowance is used up (`depleted`), the window closes (`expired`), or it's terminated. Details: [Allocations](https://cairva.xyz/docs/allocations). ## What's recorded Every state change on a request, quote, agreement, allocation, key, lot or supplier writes an audit event with who made it and when. The agreement page shows its own history to both parties. ## Who Cairva is in the deal Cairva runs the marketplace and the gateway. It verifies suppliers, reviews inventory lots, confirms payments, reconciles usage that a provider didn't report inline, and handles disputes. The first-party supplier you'll see today, **Cairva Supply Desk**, is Cairva's own supply operation and serves the sandbox route. --- # Requests and quotes > Write a private request, choose which verified suppliers see it, and receive firm quotes built from their inventory. Source: https://cairva.xyz/docs/requests-and-quotes A request (RFQ) is how a buyer tells suppliers what they need without publishing it. Quotes come back privately, one deal room per supplier. ## Write a request Open **Requests** in the workspace and start a new one. You can save a draft with only a title and fill the rest in later. To send it, these have to be set: | Field | Required to send | Notes | | --- | --- | --- | | Title | Yes | 3 to 120 characters | | Model | Yes | Pick one from the catalog, or describe the model you need in words | | Input and output tokens | Yes | Your estimate of the volume for the whole window | | Delivery window | Yes | Start and end. The end has to be in the future | | Suppliers | Yes | At least one verified supplier, up to 20 | | Capacity class | Defaults to metered allowance | See [Capacity classes](https://cairva.xyz/docs/capacity-classes) | | Requests per minute, tokens per minute, concurrency | No | The limits you need enforced | | Region | No | Free text, such as `us-east` | | Latency class | No | `interactive`, `standard` or `batch` | | Privacy requirements, compliance, notes | No | Free text for anything the quote has to respect | | Budget | No | In USD | | Response deadline | No | When you'd like quotes by | Starting a request from a lot on the [market](https://cairva.xyz/market) adds that lot's supplier to the recipients automatically. > [!NOTE] > Requests can only go to suppliers Cairva has verified. If you name a supplier that isn't verified, the request won't send. ## Who sees it A sent request is visible to your organization, the suppliers you invited and Cairva operations. Nobody else can find it. Drafts are visible only to your organization. Each supplier sees the request, not the other suppliers' quotes. While a request is open you can invite more verified suppliers. An invited supplier can decline with a reason, and you're notified. ## Request states | State | Meaning | | --- | --- | | `draft` | Saved, not sent. Only drafts can be edited | | `open` | Sent to suppliers, no quote yet | | `negotiating` | At least one supplier has issued a quote | | `awarded` | You accepted a quote. Every other open quote on the request closes | | `cancelled` | You cancelled it. Open quotes on it are withdrawn | | `expired` | Its delivery window ended while it was still open or negotiating | ## How a quote is built A supplier opens a deal room on your request, where either side can post messages. A **firm quote** is a full set of terms the supplier issues from one of their active [inventory lots](https://cairva.xyz/docs/inventory). Cairva rejects a quote unless it fits that lot: - the model matches the lot's route and the capacity class matches the lot - the delivery window sits inside the lot's window - requests per minute, tokens per minute and concurrency are within the lot's limits - the region is the route's region - the lot has enough unreserved input and output tokens - settlement matches the route: sandbox lots settle as **sandbox, no charge**, live lots settle by **external invoice** ### Terms on every quote | Term | What it means | | --- | --- | | Model, capacity class | What runs and what kind of capacity it is | | Input tokens, output tokens | The allowance the allocation will hold | | Window start and end (UTC) | When requests are served | | Requests / min, tokens / min, max concurrency | Limits the gateway enforces | | Region | Where the route runs | | Input and output price per 1M tokens | In USD | | Settlement | External invoice, or sandbox with no charge | | Overage | Always `hard_stop`: requests stop when the allowance is used | | Cancellation, under-delivery remedy | The supplier's written terms | | Expiry | When this version stops being acceptable. It can't be later than the window end | The total price is input tokens times the input price per million, plus output tokens times the output price per million. ### Supplier identity A supplier can choose not to disclose its name when quoting. You'll see **Verified supplier** until you accept, and the name is disclosed on acceptance. Next: [Negotiation](https://cairva.xyz/docs/negotiation). --- # Negotiation > Counteroffers, immutable quote versions, terms hashes and how acceptance pins the exact version you reviewed. Source: https://cairva.xyz/docs/negotiation Negotiation happens in a private deal room between one buyer and one supplier. The rule that makes it safe is simple: every proposal is a new, numbered, immutable version, and acceptance names one exact version. ## Versions Each time the supplier issues terms or either side counters, Cairva stores a new version with: - the complete terms, not a diff - the total price, computed from the terms - an expiry - an optional note - a SHA-256 hash over the terms, price and expiry Old versions never change. The deal room shows them side by side so you can see exactly what moved. ## Taking turns | Who acts | Allowed when | Result | | --- | --- | --- | | Supplier issues a firm quote | The request is open or negotiating | Quote is `issued` and it's the buyer's turn | | Buyer counters | The latest version came from the supplier | Quote is `countered` and it's the supplier's turn | | Supplier counters | The latest version came from the buyer | A new firm version, `issued` again | | Supplier withdraws | Before acceptance | Quote is `withdrawn` | You can't counter your own latest version. Wait for the other side. A supplier's counter is checked against their lot again, so it can't promise capacity the lot doesn't have. A buyer's counter is a proposal and isn't checked against the lot until the supplier answers it. > [!WARNING] > If someone posts a new version while you're editing, your counter is rejected with `stale_version`. Review the latest version and try again. This keeps you from responding to terms that are no longer on the table. ## Accepting Only a supplier-issued version can be accepted, and only by an admin of the buyer organization. The acceptance carries the version number and the terms hash you reviewed. Cairva rejects it if: - a newer version exists (`stale_version`) - the hash doesn't match the current version (`stale_version`) - the version has expired (`quote_expired`). Ask the supplier to reissue - the latest version is your own counter, still waiting on the supplier On acceptance Cairva locks the lot, reserves the tokens on it so the same supply can't be sold twice, and creates the agreement and its allocation. The agreement records a contract hash over the version, the terms hash, both organizations, and who accepted and when. Quotes expire on their own once the current version's expiry passes. Next: [Agreements](https://cairva.xyz/docs/agreements). --- # Agreements > Countersignature, payment confirmation, activation checks, cancellation and disputes. Source: https://cairva.xyz/docs/agreements An agreement is created the moment a buyer accepts a quote version. It holds the accepted terms, the price and a contract hash, and it decides when the allocation behind it can run. ## States | State | What's happening | Who acts next | | --- | --- | --- | | `awaiting_approval` | The buyer accepted. Capacity is reserved on the lot | Supplier countersigns | | `awaiting_payment` | Countersigned. An invoice is issued | Buyer pays and records the reference, then Cairva operations confirm | | `provisioning` | Settled. Activation checks are running | Cairva | | `active` | The allocation can serve requests | Nobody | | `completed` | The allowance was used up, or the window ended after activation | Nobody | | `expired` | The window ended before activation | Nobody | | `cancelled` | Cancelled before activation | Nobody | | `disputed` | A dispute is open | Cairva operations | Sandbox agreements go straight from countersignature to `provisioning` with a zero-amount record marked paid. No money moves. ## Countersignature An admin of the supplier organization countersigns. That issues the invoice for a live agreement, or the no-charge record for a sandbox one, and notifies the buyer. ## Payment For a live agreement, the buyer pays the invoice outside Cairva and records the payment reference in the workspace. Cairva operations then confirm receipt or reject it with a note, which puts the invoice back to issued. See [Settlement](https://cairva.xyz/docs/settlement). ## Activation checks Activation only succeeds when every check is true at that moment: - the supplier countersigned - payment is confirmed, or the sandbox settlement is recorded - the supplier is still verified - the inventory lot is still active - the route is enabled - the route passed a live health check, run as part of activation - the delivery window hasn't ended If any check fails, the agreement stays in `provisioning` and shows which checks it's waiting on. When they pass, the allocation becomes `active`, or `ready` if its window hasn't started, and the buyer is told to create an API key. ## Cancelling Either party's admin, or Cairva operations, can cancel an agreement before it's active, with a reason. Cancelling releases the reserved tokens back to the lot, terminates the allocation and voids any unpaid invoice. An active agreement can't be cancelled from the workspace. It follows the cancellation terms written into the quote. Open a dispute or contact operations. ## Disputes The buyer or the supplier can open a dispute on an agreement that's active, completed or expired. Pick a category and describe the problem in at least 20 characters: | Category | Use it for | | --- | --- | | `under_delivery` | Capacity or limits not delivered as agreed | | `metering` | Usage counts you think are wrong | | `availability` | The route wasn't reachable when it should have been | | `billing` | Invoices and payments | | `other` | Anything else | The agreement moves to `disputed` and both sides and operations are notified. Operations move the dispute to `investigating`, then `resolved` or `rejected` with a written resolution. When it closes, the agreement returns to `active` if the allocation is still live, or to `completed`. --- # Capacity classes > Metered allowance, reserved throughput and dedicated window: what each class promises and when it can be offered. Source: https://cairva.xyz/docs/capacity-classes Every lot, request and quote names one capacity class. The class says what's actually being promised, and Cairva only lets a supplier offer a class the route behind it can back. | Class | Code | What you're buying | | --- | --- | --- | | Metered allowance | `METERED_ALLOWANCE` | A prepaid token allowance on a shared route. Rate limits are enforced, but no GPU is dedicated and no tokens-per-second minimum is promised | | Reserved throughput | `RESERVED_THROUGHPUT` | Provisioned capacity with a measured minimum throughput. Only offered when the supplier has a reserved deployment that Cairva can measure | | Dedicated window | `DEDICATED_WINDOW` | Exclusive use of a deployment for a fixed time window. Only offered when the hardware is actually set aside for the buyer | ## What every class shares - A fixed input and output token allowance - A delivery window - Requests per minute, tokens per minute and concurrency limits enforced by the gateway - A hard stop when the allowance is used. There's no overage ## Sandbox routes Sandbox routes can only offer **metered allowance**. Reserved throughput and dedicated windows need a live route Cairva can measure, so a lot that names either class on a sandbox route is rejected. > [!NOTE] > Today the only route is a sandbox route, so the only class you can buy is metered allowance. Reserved and dedicated capacity become possible once a supplier has a live, measurable deployment on Cairva. --- # Allocations > The live, metered form of an agreement: quota, holds, rate limits, delivery window and states. Source: https://cairva.xyz/docs/allocations An allocation is a block of capacity carved out for one buyer: the contracted tokens, the delivery window and the rate limits from the agreement. It's the thing API keys are bound to and the thing every request is metered against. ## What it holds | Field | Meaning | | --- | --- | | Quota | Contracted input and output tokens | | Used | Tokens the provider reported, booked against the quota | | Held | Tokens reserved by requests still running | | Remaining | Quota minus used minus held, never below zero | | Window | When requests are served | | Limits | Requests per minute, tokens per minute and max concurrency | Input and output are tracked separately. Running out of either one stops the allocation. ## States | State | Meaning | Serves requests | | --- | --- | --- | | `reserved` | The quote was accepted. The agreement isn't active yet | No | | `ready` | Activated, but the window hasn't started | No | | `active` | Inside the window and serving | Yes | | `paused` | The supplier or Cairva operations paused it, with a reason | No | | `depleted` | The input or output allowance is fully used. The agreement completes | No | | `expired` | The window ended | No | | `terminated` | The agreement was cancelled before activation | No | A `ready` allocation becomes `active` on the first request after its window opens, or when the background sweep reaches it. When the window ends, a `ready`, `active` or `paused` allocation becomes `expired` and its agreement completes. ## Pausing The supplier or Cairva operations can pause a `ready` or `active` allocation with a written reason, for example to investigate a compromised key or a delivery problem. The buyer is notified with that reason, and requests fail with `allocation_paused`. Resuming puts the allocation back to `active`, or `ready` if its window hasn't started. ## Low allowance When less than 10% of the output allowance remains, the buyer organization gets a notification once for that allocation. ## Using it - Create keys in **API keys** in the workspace. See [API keys](https://cairva.xyz/docs/api-keys). - Try prompts in the workspace **Playground**. Playground requests go through the same gateway and spend the same allowance. - Read it from code with [`GET /v1/allocations/current`](https://cairva.xyz/docs/allocation-status). - Read per-request usage with [`GET /v1/usage`](https://cairva.xyz/docs/usage). --- # Authentication > Bearer keys bound to one allocation, key formats, scopes and what each failure returns. Source: https://cairva.xyz/docs/authentication The gateway authenticates every request with a bearer key. A key belongs to one allocation, carries one or two scopes, and is the only credential the API accepts. Base URL: `https://cairva.xyz/v1` ## Send the key Put the key in the `Authorization` header: ```http Authorization: Bearer cv_live_XXXXXXXX_... ``` OpenAI SDKs do this for you when you pass the key as `apiKey` (JavaScript) or `api_key` (Python). See [SDKs](https://cairva.xyz/docs/sdks). ## Key format | Prefix | Issued for | | --- | --- | | `cv_live_` | Allocations on live routes | | `cv_test_` | Allocations on sandbox routes | A full key looks like `cv_test_AbC12xYz_` followed by a long random secret. The eight characters after the mode are the key's public prefix. That prefix is what the workspace and audit logs show, so you can tell keys apart without exposing the secret. Cairva stores only a keyed hash of the full key. A lost key can't be recovered, only rotated. ## Scopes | Scope | Endpoints | | --- | --- | | `inference` | `POST /v1/chat/completions`, `GET /v1/models` | | `usage:read` | `GET /v1/allocations/current`, `GET /v1/usage` | Give each key the least it needs. A reporting job that reads usage only needs `usage:read` and can't spend your allowance. ## One key, one allocation Each key is bound to a single allocation. It can only call that allocation's model, spend that allocation's allowance and read that allocation's usage. To use two allocations, use two keys. ## Failures All authentication failures return `401` with `"type": "authentication_error"` and `"code": "unauthenticated"`. The message tells you which case you hit: | Message | Cause | | --- | --- | | `Missing API key. Send it as: Authorization: Bearer ` | No header, or not a `Bearer` header | | `Malformed API key` | The value doesn't look like a Cairva key | | `Invalid API key` | No key matches | | `This API key was revoked` | The key was revoked or rotated | A valid key without the scope an endpoint needs gets `403 scope_missing`. ```json { "error": { "message": "scope_missing: this key lacks the \"usage:read\" scope", "type": "invalid_request_error", "code": "scope_missing" } } ``` > [!WARNING] > Anyone holding a key with the `inference` scope can spend your allowance. Keep keys on servers. Don't ship them in browser or mobile code. If a key leaks, rotate it in the workspace. The old key stops working on its next request. --- # Chat completions > POST /v1/chat/completions: request fields, headers, the response and the cairva metering object. Source: https://cairva.xyz/docs/chat-completions Runs a chat completion against your allocation's model and meters it. The request and response follow the OpenAI chat completions format, so existing OpenAI SDK code works once you change the base URL, the key and the model id. ```endpoint POST /v1/chat/completions ``` Requires the `inference` scope. ## Example ```bash curl curl https://cairva.xyz/v1/chat/completions \ -H "Authorization: Bearer $CAIRVA_API_KEY" \ -H "Content-Type: application/json" \ -H "x-request-id: ticket-4812" \ -d '{ "model": "gemma4:e2b", "messages": [ {"role": "system", "content": "Answer in one sentence."}, {"role": "user", "content": "What does a metered allowance include?"} ], "max_tokens": 200 }' ``` ```javascript JavaScript import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.CAIRVA_API_KEY, baseURL: "https://cairva.xyz/v1", }); const completion = await client.chat.completions.create({ model: "gemma4:e2b", messages: [ { role: "system", content: "Answer in one sentence." }, { role: "user", content: "What does a metered allowance include?" }, ], max_tokens: 200, }); console.log(completion.choices[0].message.content); console.log(completion.cairva); ``` ```python Python import os from openai import OpenAI client = OpenAI(api_key=os.environ["CAIRVA_API_KEY"], base_url="https://cairva.xyz/v1") completion = client.chat.completions.create( model="gemma4:e2b", messages=[ {"role": "system", "content": "Answer in one sentence."}, {"role": "user", "content": "What does a metered allowance include?"}, ], max_tokens=200, ) print(completion.choices[0].message.content) print(completion.model_extra["cairva"]) ``` ## Request body | Field | Type | Notes | | --- | --- | --- | | `model` | string, required | Must equal the model id your allocation covers. Get it from [`GET /v1/models`](https://cairva.xyz/docs/models) | | `messages` | array, required | 1 to 200 messages. `role` is `system`, `user` or `assistant`. `content` is a string or an array of `{"type": "text", "text": "..."}` parts, which are joined without a separator | | `max_tokens` | integer | 1 to 32,768. Defaults to 1,024. Lowered to your remaining output allowance if that's smaller | | `max_completion_tokens` | integer | Same limits. Wins over `max_tokens` when both are set | | `temperature` | number | 0 to 2. Passed to the provider | | `top_p` | number | 0 to 1. Passed to the provider | | `stop` | string or array | Up to 4 stop sequences | | `stream` | boolean | Stream server-sent events. See [Streaming](https://cairva.xyz/docs/streaming) | | `user` | string | Up to 200 characters. Accepted for SDK compatibility, not forwarded or stored | Any other field is dropped before the request reaches the provider. That includes `tools`, `n`, `response_format`, `logprobs` and `seed`. A request with an image part, or a `tool`, `function` or `developer` role, fails with `400 validation_failed`. Details in [OpenAI compatibility](https://cairva.xyz/docs/compatibility). ## Headers | Header | Direction | Notes | | --- | --- | --- | | `Authorization` | Request | `Bearer` and your key | | `x-request-id` | Request, optional | Your own identifier, truncated to 120 characters. Stored as `client_request_id` and returned in [usage events](https://cairva.xyz/docs/usage), so you can join Cairva's meter to your logs | | `x-cairva-request-id` | Response | Cairva's id for the request. Sent on non-streaming responses | ## Response ```json { "id": "chatcmpl-7d0c2f4e-6a51-4c43-9f0e-2b8f5a1c9e30", "object": "chat.completion", "created": 1793520000, "model": "gemma4:e2b", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "..." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 31, "completion_tokens": 18, "total_tokens": 49 }, "cairva": { "request_id": "7d0c2f4e-6a51-4c43-9f0e-2b8f5a1c9e30", "allocation_id": "c1a9e1d2-0b7f-4f9e-a3c4-58e2d6b0f114", "service_mode": "sandbox", "usage_source": "provider_reported", "remaining": { "input": 999969, "output": 499982 } } } ``` There's always exactly one choice. `finish_reason` is whatever the provider returned, usually `stop` or `length`. ## The cairva object | Field | Meaning | | --- | --- | | `request_id` | Cairva's id for this request. The response `id` is `chatcmpl-` followed by it | | `allocation_id` | The allocation that was charged, as a UUID | | `service_mode` | `live` or `sandbox` | | `usage_source` | `provider_reported` when the provider returned token counts, `sandbox_estimate` on a stand-in sandbox route that calls no model, or `pending` when no counts came back | | `remaining` | Input and output tokens left after this request, net of other requests still running | When `usage_source` is `pending`, `usage` is `null`. Cairva never fills in an estimate. The request's hold stays in place until Cairva operations reconcile it from provider metering. See [Rate limits and holds](https://cairva.xyz/docs/rate-limits). ## Output tokens you don't see Some models reason before they answer, and that reasoning counts as output tokens. The gateway returns only the message `content`, so reasoning isn't included in the response. Ollama, which serves the sandbox route, doesn't return reasoning as content either. If `max_tokens` runs out while the model is still reasoning, you can get an empty or very short `content` with `finish_reason: "length"`. The provider still reports the output tokens it generated, and they're metered. Give reasoning models enough `max_tokens` for both the reasoning and the answer. > [!TIP] > If you see empty answers with `finish_reason: "length"`, raise `max_tokens` before anything else. ## Errors Admission errors such as `model_not_permitted`, `allocation_exhausted` and `rate_limited` come back as an HTTP error before the provider is called. If the provider fails, you get `502 upstream_error`, the hold is released, and nothing is charged. All codes are in [Errors](https://cairva.xyz/docs/errors). --- # Streaming > Server-sent events from /v1/chat/completions, the final usage chunk, disconnects and mid-stream failures. Source: https://cairva.xyz/docs/streaming Set `"stream": true` on a chat completion and the response arrives as server-sent events. Usage and the `cairva` metering object come in the last chunk, so you don't need `stream_options`. ```endpoint POST /v1/chat/completions ``` ## Example ```bash curl curl -N https://cairva.xyz/v1/chat/completions \ -H "Authorization: Bearer $CAIRVA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "gemma4:e2b", "stream": true, "messages": [{"role": "user", "content": "Count to five."}]}' ``` ```javascript JavaScript const stream = await client.chat.completions.create({ model: "gemma4:e2b", messages: [{ role: "user", content: "Count to five." }], stream: true, }); for await (const chunk of stream) { const text = chunk.choices[0]?.delta?.content; if (text) process.stdout.write(text); // The last chunk has no choices. It carries usage and the cairva object. if (chunk.usage) console.log("\n", chunk.usage, chunk.cairva); } ``` ```python Python stream = client.chat.completions.create( model="gemma4:e2b", messages=[{"role": "user", "content": "Count to five."}], stream=True, ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="") # The last chunk has no choices. It carries usage and the cairva object. if chunk.usage: print("\n", chunk.usage, chunk.model_extra["cairva"]) ``` ## Event sequence The response has `Content-Type: text/event-stream`. Each event is one `data:` line holding a `chat.completion.chunk`: 1. Content deltas: `choices[0].delta.content` with the next piece of text and `finish_reason: null`. 2. One chunk with an empty `delta` and the `finish_reason`. 3. One chunk with an empty `choices` array, `usage`, and the `cairva` object. 4. `data: [DONE]`. ```text data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1793520000,"model":"gemma4:e2b","choices":[{"index":0,"delta":{"content":"One"},"finish_reason":null}]} data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1793520000,"model":"gemma4:e2b","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1793520000,"model":"gemma4:e2b","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":9,"total_tokens":21},"cairva":{"request_id":"…","allocation_id":"…","service_mode":"sandbox","usage_source":"provider_reported","remaining":{"input":999976,"output":499991}}} data: [DONE] ``` Deltas carry `content` only. There's no separate first chunk with `role`. The `cairva` object has the same fields as on a [non-streaming response](https://cairva.xyz/docs/chat-completions#the-cairva-object). If the provider didn't report usage, `usage` is `null` and `usage_source` is `pending`. ## Errors before the stream starts Authentication, validation and admission errors, such as `rate_limited` or `allocation_exhausted`, come back as a normal JSON error with an HTTP status. No stream is opened. See [Errors](https://cairva.xyz/docs/errors). ## Errors during the stream Once admission passes, the response is already `200` with an open stream, and the provider is called from inside it. So a provider failure, even one before the first token, arrives as one event and then the stream closes, without `[DONE]`: ```json {"error": {"code": "upstream_error", "message": "The inference provider stopped responding"}} ``` The request's hold is released and nothing is charged for it. ## Disconnecting early If your client disconnects, Cairva keeps reading the provider's stream to the end so the provider's usage report still arrives. The tokens the model generated after you left are still metered. To spend less, set `max_tokens` lower instead of disconnecting. > [!NOTE] > Reasoning models can spend output tokens on reasoning that isn't streamed as content. A stream can end with few or no content deltas and `finish_reason: "length"`, and those tokens are still metered. See [Chat completions](https://cairva.xyz/docs/chat-completions#output-tokens-you-don-t-see). --- # Models > GET /v1/models returns the one model your key's allocation can call. Source: https://cairva.xyz/docs/models Lists the models your key can call. Because a key is bound to one allocation and an allocation covers one model, the list has exactly one entry. ```endpoint GET /v1/models ``` Requires the `inference` scope. ## Example ```bash curl curl https://cairva.xyz/v1/models \ -H "Authorization: Bearer $CAIRVA_API_KEY" ``` ```javascript JavaScript const models = await client.models.list(); for (const model of models.data) console.log(model.id, model.context_length); ``` ```python Python for model in client.models.list(): print(model.id, model.model_extra["context_length"]) ``` ## Response ```json { "object": "list", "data": [ { "id": "gemma4:e2b", "object": "model", "created": 1793433600, "owned_by": "cairva", "context_length": 8192 } ] } ``` | Field | Meaning | | --- | --- | | `id` | The value to send as `model` in chat completions | | `created` | When the model was added to Cairva's catalog, in Unix seconds | | `owned_by` | Always `cairva` | | `context_length` | The model's context limit in tokens, from Cairva's catalog | The `context_length` above is an example. Read the value for your model from the response. Calling chat completions with any other `model` fails with `403 model_not_permitted`, and the message names the model your key can call. --- # Current allocation > GET /v1/allocations/current: quota, usage, holds, remaining allowance, window and enforced limits. Source: https://cairva.xyz/docs/allocation-status Returns the live state of the allocation your key belongs to: entitlement, what's used, what's held by running requests, what remains, the window and the enforced limits. ```endpoint GET /v1/allocations/current ``` Requires the `usage:read` scope. ## Example ```bash curl curl https://cairva.xyz/v1/allocations/current \ -H "Authorization: Bearer $CAIRVA_API_KEY" ``` ```javascript JavaScript const res = await fetch("https://cairva.xyz/v1/allocations/current", { headers: { Authorization: `Bearer ${process.env.CAIRVA_API_KEY}` }, }); const allocation = await res.json(); console.log(allocation.remaining, allocation.window.expires_at); ``` ```python Python import os import requests res = requests.get( "https://cairva.xyz/v1/allocations/current", headers={"Authorization": f"Bearer {os.environ['CAIRVA_API_KEY']}"}, ) allocation = res.json() print(allocation["remaining"], allocation["window"]["expires_at"]) ``` ## Response ```json { "id": "ALC-4K7Q2M", "agreement": "AGR-9T3D1X", "state": "active", "service_mode": "sandbox", "capacity_type": "METERED_ALLOWANCE", "model": "gemma4:e2b", "window": { "starts_at": "2026-11-01T00:00:00.000Z", "expires_at": "2026-12-01T00:00:00.000Z" }, "quota": { "input_tokens": 1000000, "output_tokens": 500000 }, "used": { "input_tokens": 31, "output_tokens": 18, "requests": 1 }, "held": { "input_tokens": 0, "output_tokens": 0 }, "remaining": { "input_tokens": 999969, "output_tokens": 499982 }, "limits": { "requests_per_minute": 60, "tokens_per_minute": 100000, "max_concurrency": 4 } } ``` The numbers above are an example. Your allocation's values come from its agreement. ## Fields | Field | Meaning | | --- | --- | | `id` | The allocation reference shown in the workspace, such as `ALC-4K7Q2M` | | `agreement` | The agreement reference | | `state` | `reserved`, `ready`, `active`, `paused`, `depleted`, `expired` or `terminated`. See [Allocations](https://cairva.xyz/docs/allocations#states) | | `service_mode` | `live` or `sandbox` | | `capacity_type` | `METERED_ALLOWANCE`, `RESERVED_THROUGHPUT` or `DEDICATED_WINDOW` | | `model` | The model id this allocation covers | | `window` | Start and end of delivery, ISO 8601 in UTC | | `quota` | Contracted input and output tokens | | `used` | Booked tokens and the number of completed requests | | `held` | Tokens held by requests that are running or waiting on usage | | `remaining` | `quota` minus `used` minus `held`, never below zero | | `limits` | The three rate limits the gateway enforces | > [!NOTE] > `id` here is the human-readable reference. The `allocation_id` in a chat completion's `cairva` object is the allocation's UUID. Both identify the same allocation. `requests` counts requests that finished, including ones still waiting on usage. Failed requests aren't counted. --- # Usage events > GET /v1/usage: one metered event per request, with cursor pagination and time filters. Source: https://cairva.xyz/docs/usage Returns one event per metered request on your key's allocation, newest first. These are the same events the supplier sees on their side of the agreement. ```endpoint GET /v1/usage ``` Requires the `usage:read` scope. ## Query parameters | Parameter | Type | Notes | | --- | --- | --- | | `from` | ISO 8601 | Only events at or after this time | | `to` | ISO 8601 | Only events before this time | | `limit` | integer | Page size. Defaults to 50, capped at 100 | | `cursor` | string | `next_cursor` from the previous page | A `from` or `to` that isn't a valid date is ignored. ## Example ```bash curl curl "https://cairva.xyz/v1/usage?from=2026-11-01T00:00:00Z&to=2026-11-02T00:00:00Z&limit=100" \ -H "Authorization: Bearer $CAIRVA_API_KEY" ``` ```javascript JavaScript async function allUsage(from, to) { const events = []; let cursor = null; do { const url = new URL("https://cairva.xyz/v1/usage"); url.search = new URLSearchParams({ from, to, limit: "100", ...(cursor ? { cursor } : {}) }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.CAIRVA_API_KEY}` }, }); const page = await res.json(); events.push(...page.data); cursor = page.next_cursor; } while (cursor); return events; } ``` ```python Python import os import requests def all_usage(start, end): events, cursor = [], None while True: params = {"from": start, "to": end, "limit": 100} if cursor: params["cursor"] = cursor page = requests.get( "https://cairva.xyz/v1/usage", params=params, headers={"Authorization": f"Bearer {os.environ['CAIRVA_API_KEY']}"}, ).json() events += page["data"] cursor = page["next_cursor"] if not cursor: return events ``` ## Response ```json { "object": "list", "data": [ { "id": "0f6b8a52-3c1e-4e0b-9a77-1d2c3b4a5e6f", "occurred_at": "2026-11-01T09:14:02.511Z", "request_id": "7d0c2f4e-6a51-4c43-9f0e-2b8f5a1c9e30", "upstream_request_id": "chatcmpl-582", "client_request_id": "ticket-4812", "input_tokens": 31, "output_tokens": 18, "source": "provider_reported", "finality": "observed" } ], "next_cursor": null } ``` `next_cursor` is `null` on the last page. ## Fields | Field | Meaning | | --- | --- | | `id` | The usage event id | | `occurred_at` | When the usage was booked | | `request_id` | Matches `cairva.request_id` on the completion | | `upstream_request_id` | The provider's id for the request, when it sent one | | `client_request_id` | The `x-request-id` header you sent, if any | | `input_tokens`, `output_tokens` | Booked token counts | | `source` | Where the counts came from | | `finality` | How settled the counts are | ### source | Value | Meaning | | --- | --- | | `provider_reported` | The model provider returned the counts with the response | | `reconciliation` | Cairva operations booked the counts later from provider metering, with evidence | | `sandbox_estimate` | A stand-in sandbox route that calls no model estimated the counts | ### finality Events start as `observed`. Events booked by reconciliation are `confirmed`. The field also defines `corrected` and `disputed`. Nothing sets either today, but handle them so your code doesn't break if it starts to. ## What isn't here A request appears only once its usage is booked. Failed requests never appear and never consume allowance. Requests waiting on usage (`usage_source: "pending"` on the response) appear after operations reconcile them. --- # Errors > The OpenAI error envelope, every Cairva product code, its HTTP status and what to do about it. Source: https://cairva.xyz/docs/errors Gateway errors use the OpenAI error envelope, so OpenAI SDKs raise them as normal API errors. Branch on `code`: it's a stable Cairva product code. The `message` is for people and can change. ```json { "error": { "message": "allocation_exhausted: 120 input and 0 output tokens remain", "type": "invalid_request_error", "code": "allocation_exhausted" } } ``` `type` follows the status: `authentication_error` for 401, `rate_limit_error` for 429, `api_error` for 5xx, and `invalid_request_error` for everything else. ## Codes | Status | Code | When it happens | What to do | | --- | --- | --- | --- | | 400 | `validation_failed` | The body isn't JSON, or a field fails validation. The message names the field | Fix the request. See [Chat completions](https://cairva.xyz/docs/chat-completions#request-body) | | 401 | `unauthenticated` | The key is missing, malformed, unknown or revoked | Check the key. See [Authentication](https://cairva.xyz/docs/authentication#failures) | | 402 | `allocation_exhausted` | Not enough input or output allowance remains to admit this request. The message says what's left | Shorten the prompt, wait for running requests to settle, or buy more capacity | | 402 | `allocation_depleted` | The allocation used its full allowance and closed | Buy more capacity | | 403 | `scope_missing` | The key doesn't have the scope this endpoint needs | Use a key with `inference` or `usage:read` | | 403 | `key_unscoped` | The key isn't bound to an allocation | Create a key for an allocation | | 403 | `model_not_permitted` | `model` isn't the one model this key's allocation covers. The message names it | Use the id from `GET /v1/models` | | 403 | `allocation_reserved` | The agreement behind the allocation hasn't activated yet | Wait for activation | | 403 | `allocation_not_started` | The window hasn't opened. The message includes the start time | Retry after the start time | | 403 | `allocation_expired` | The delivery window has ended | Buy new capacity | | 403 | `allocation_paused` | The supplier or operations paused the allocation. The message carries the reason when one was given | Read the reason in the workspace | | 403 | `allocation_terminated` | The allocation was terminated and won't serve more requests | Buy new capacity | | 429 | `rate_limited` | A requests-per-minute, tokens-per-minute or concurrency limit was hit | Wait for `Retry-After` seconds. See [Rate limits](https://cairva.xyz/docs/rate-limits) | | 502 | `upstream_error` | The provider didn't complete the request. The hold is released and nothing is charged | Retry with backoff | | 502 | `route_disabled` | Operations paused the route behind this allocation | Check the [status page](https://cairva.xyz/status) | | 502 | `route_unconfigured` | The route has no provider credentials configured | Contact Cairva | | 500 | `internal_error` | Something failed inside Cairva | Retry with backoff. Quote the request id if it persists | ## Retry-After Every `429` carries a `Retry-After` header in seconds: `60` for the requests-per-minute and tokens-per-minute limits, `2` for the concurrency limit. The OpenAI SDKs read it when they retry. ## Retrying safely A request that failed never consumes allowance. Its hold is released when Cairva records the failure. Retrying a `502 upstream_error` or a `429` can't double-charge you. `4xx` codes other than `429` won't succeed on retry without a change: a new key, a different `model`, or a new allocation. ## Errors inside a stream A failure after a stream has opened arrives as one event, `{"error": {"code": "upstream_error", "message": "..."}}`, and then the stream closes. See [Streaming](https://cairva.xyz/docs/streaming#errors-during-the-stream). --- # Rate limits and holds > Requests per minute, tokens per minute, concurrency, and how allowance is held before a request runs. Source: https://cairva.xyz/docs/rate-limits Every allocation carries three rate limits from its agreement, and every request holds allowance before it runs. Both are checked centrally under a lock on the allocation, so parallel workers can't jointly go over. ## The three limits | Limit | What's counted | Over the limit | | --- | --- | --- | | `requests_per_minute` | Requests started in the trailing 60 seconds, including ones that later failed | `429 rate_limited`, `Retry-After: 60` | | `tokens_per_minute` | Input plus output tokens booked in the trailing 60 seconds | `429 rate_limited`, `Retry-After: 60` | | `max_concurrency` | Requests in flight at once | `429 rate_limited`, `Retry-After: 2` | Read your limits from [`GET /v1/allocations/current`](https://cairva.xyz/docs/allocation-status). They're the numbers in your agreement, and the supplier can't change them without a new agreement. `tokens_per_minute` counts usage once it's booked, so a burst of long requests can start together and only count against the limit as they finish. ## Admission order Before a request reaches the provider, the gateway checks, in order: 1. The allocation is in its window and `active`. A `ready` allocation becomes `active` here if its window has opened. 2. Requests per minute. 3. Concurrency. 4. Tokens per minute. 5. Enough allowance to hold the request. The first check that fails is the error you get. ## How allowance is held Each request places a **hold** on your allowance before it runs: - **Input:** an estimate from the prompt length, about one token per three characters of message content plus a small fixed overhead. It's deliberately generous. - **Output:** the request's `max_tokens` (or `max_completion_tokens`), 1,024 if you didn't set one. If `max_tokens` is more than your remaining output allowance, it's lowered to fit, so a single request can't overspend the agreement. If no output allowance is left, or the input estimate is more than the input left, the request fails with `402 allocation_exhausted`. While a request runs, its hold counts against `remaining`. That's why `remaining` can drop and come back up while requests are in flight. ## When usage arrives When the provider reports token counts, Cairva releases the hold and books the real counts in one transaction. Each provider response is booked exactly once, keyed on the allocation and the provider's request id, so the same response can't be booked twice. When the full input or output allowance has been used, the allocation becomes `depleted`, its agreement completes, and later requests get `402 allocation_depleted`. ## When usage doesn't arrive If the provider completes a request but doesn't return token counts, Cairva doesn't guess. The request is marked `usage_pending`, its hold stays in place, and the response says `"usage_source": "pending"` with `usage: null`. Cairva operations then book the counts from provider metering with written evidence. The booked event has `source: "reconciliation"` and `finality: "confirmed"`, and the hold is released. A request that's still marked in flight after 10 minutes, for example because a process died mid-stream, is moved to `usage_pending` and reconciled the same way. It stops counting toward concurrency at that point. ## When a request fails If the provider errors, the hold is released right away and the request books nothing. Failed requests still count toward `requests_per_minute`. --- # OpenAI compatibility > What the gateway supports, what it rejects and what it silently ignores, before you migrate. Source: https://cairva.xyz/docs/compatibility Cairva implements OpenAI-style text chat. Everything outside that is either rejected with an error or silently ignored, and it's better to know which before you migrate. ## Supported - `POST /v1/chat/completions` with `system`, `user` and `assistant` messages - Message content as a string or an array of text parts - Streaming over server-sent events, with usage in the final chunk - `max_tokens`, `max_completion_tokens`, `temperature`, `top_p`, `stop`, `user` - `GET /v1/models`, scoped to your key - The OpenAI error envelope, and `Retry-After` on `429` ## Rejected with 400 | Input | Result | | --- | --- | | Image, audio or file content parts | `validation_failed` | | `tool`, `function` or `developer` roles | `validation_failed` | | Assistant messages with `null` content, as produced by tool calls | `validation_failed` | | More than 200 messages, or none | `validation_failed` | | `max_tokens` above 32,768 | `validation_failed` | | More than 4 stop sequences | `validation_failed` | ## Ignored These fields are accepted and dropped. Your request runs as if you hadn't sent them. | Field | What you get instead | | --- | --- | | `tools`, `tool_choice`, `functions` | No tool calls are ever returned | | `n` | Always one choice | | `response_format` | Plain text. Ask for JSON in the prompt and validate it yourself | | `logprobs`, `top_logprobs` | No log probabilities | | `seed`, `presence_penalty`, `frequency_penalty`, `logit_bias` | Not passed to the provider | | `stream_options` | Usage is always sent in the last streamed chunk | | Extra fields on messages, such as `name` | Dropped | ## Not available Embeddings, images, audio, files, assistants, batches, the Responses API and the legacy completions endpoint. Only the four endpoints in the [introduction](https://cairva.xyz/docs#the-api-in-one-minute) exist. ## Differences in responses - Every response has a `cairva` object. SDKs keep it as an extra field. See [SDKs](https://cairva.xyz/docs/sdks). - `usage` can be `null` when the provider didn't report counts. Code that reads `usage.total_tokens` without a check will throw. - Streamed deltas carry `content` only, with no initial `role` chunk. - `model` in the response is Cairva's model id, which is what you sent. - Reasoning isn't returned. See [Output tokens you don't see](https://cairva.xyz/docs/chat-completions#output-tokens-you-don-t-see). ## Migration checklist 1. Change the base URL to `https://cairva.xyz/v1` and the key to your Cairva key. 2. Set `model` to the id from `/v1/models`. 3. Remove tool calling and structured output, or handle them in your own code. 4. Guard every read of `usage` for `null`. 5. Set `max_tokens` deliberately, since it's held from your allowance. 6. Handle `402` and `403` allocation errors. They mean your agreement, not your code, needs attention. --- # API keys > Create, rotate and revoke keys for an allocation, and keep them out of client code. Source: https://cairva.xyz/docs/api-keys API keys are created in the workspace, one allocation at a time. Only admins of the buyer organization can create, rotate or revoke them. ## Create a key 1. Open **API keys** in the workspace. 2. Choose the allocation. It has to be activated: `ready`, `active` or `paused`. Keys can't be created for a `reserved` allocation, or for one that's `depleted`, `expired` or `terminated`. 3. Name the key, 2 to 60 characters. Use a name that says where it runs, such as `billing-worker-prod`. 4. Pick scopes: `inference`, `usage:read` or both. 5. Copy the secret. It's shown once. The key's mode follows the route: `cv_live_` for live allocations, `cv_test_` for sandbox ones. See [Authentication](https://cairva.xyz/docs/authentication#key-format). ## Store it - Put it in an environment variable or your secret manager, as `CAIRVA_API_KEY`. - Keep it on servers. Never ship it in a browser bundle or a mobile app. - Use separate keys for separate services, so you can revoke one without touching the others. - Give reporting jobs `usage:read` only. Cairva stores only a keyed hash of the full key, so nobody at Cairva can read it back to you. ## Rotate a key Rotating revokes the old key and issues a new one with the same name, allocation and scopes. The new secret is shown once, and the old key fails with `401` on its next request. For a change with no downtime, create a second key for the same allocation, deploy it, then revoke the first. ## Revoke a key Revoking needs a reason of 3 to 300 characters, which goes into the audit log. A revoked key gets `401 unauthenticated` with the message `This API key was revoked`. Cairva operations can also revoke a key, for example if it's found in public code. ## See what a key is doing The workspace lists each key's prefix, scopes, allocation and when it was last used. Each request also carries the key's allocation in its `cairva` object, and [usage events](https://cairva.xyz/docs/usage) show your `x-request-id`, so you can attribute spend to a service by sending a request id per caller. --- # Sandbox > The sandbox route that's open today, what's real in it and what isn't. Source: https://cairva.xyz/docs/sandbox The sandbox is how Cairva runs today. It's the full product on a development route: real requests, quotes, agreements, activation, keys and metering, with no money involved. ## What's open now | | | | --- | --- | | Model | `gemma4:e2b`, an open-weight model | | Runtime | Ollama, on Cairva's development infrastructure | | Supplier | Cairva Supply Desk, Cairva's first-party supply | | Region | `local-dev` | | Capacity class | Metered allowance only | | Settlement | Sandbox, no charge | | Keys | `cv_test_…` | This is the only route on Cairva right now. There's no live commercial supply listed yet, and nothing in these docs should be read as a promise that a particular model or supplier will be available. ## What's real - The deal flow: requests, deal rooms, versioned quotes, acceptance with a terms hash, countersignature and activation checks. - The gateway: authentication, scopes, admission, rate limits, holds and every error code. - The model: requests go to a real model server, and usage is the token count it reports, not an estimate. - The meter: usage events, remaining allowance and depletion work exactly as they would on a live route. ## What isn't - **Money.** Sandbox agreements settle as no-charge. Countersignature records a zero-amount settlement marked paid, and the agreement goes straight to activation checks. - **Guarantees.** There's no SLA, no throughput minimum and no promise the route stays up. It's development infrastructure. - **Capacity classes.** Sandbox routes can only offer metered allowance. ## How to recognise sandbox Sandbox is labelled in the workspace and on the market, keys start with `cv_test_`, settlement reads **Sandbox, no charge**, and every response has `"service_mode": "sandbox"` in its `cairva` object. ## Sandbox stand-in routes Cairva can also run a sandbox route on a built-in stand-in that calls no model. It replies with a fixed message saying no model was called, and labels its usage `sandbox_estimate`. The route open today isn't one of these. It calls the real model and reports `provider_reported` usage. ## Before you test The sandbox route is a small model on development hardware, so expect modest speed and output quality. If the model reasons before answering, those tokens count as output even though you don't see them. If answers come back empty with `finish_reason: "length"`, raise `max_tokens`. See [Chat completions](https://cairva.xyz/docs/chat-completions#output-tokens-you-don-t-see). Check route health on the [status page](https://cairva.xyz/status) first. --- # SDKs > Use the official OpenAI SDKs for JavaScript and Python against Cairva, including the cairva metering field. Source: https://cairva.xyz/docs/sdks Cairva doesn't ship its own SDK. Use the official OpenAI SDKs: the gateway speaks their format, so you change three things and keep the rest of your code. | Setting | Value | | --- | --- | | Base URL | `https://cairva.xyz/v1` | | API key | Your Cairva key, `cv_live_…` or `cv_test_…` | | Model | The id from `GET /v1/models` | ## JavaScript and TypeScript ```bash npm install openai ``` ```javascript import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.CAIRVA_API_KEY, baseURL: "https://cairva.xyz/v1", }); const completion = await client.chat.completions.create({ model: "gemma4:e2b", messages: [{ role: "user", content: "Summarise this ticket in one line." }], max_tokens: 200, }); console.log(completion.choices[0].message.content); ``` The SDK keeps fields it doesn't know about on the object, so `completion.cairva` holds Cairva's metering details. In TypeScript, read it through a narrow type of your own: ```typescript type CairvaMeta = { request_id: string; allocation_id: string; service_mode: "live" | "sandbox"; usage_source: "provider_reported" | "sandbox_estimate" | "pending"; remaining: { input: number; output: number }; }; function cairvaMeta(response: object): CairvaMeta | undefined { return "cairva" in response ? (response.cairva as CairvaMeta) : undefined; } console.log(cairvaMeta(completion)?.remaining.output); ``` ## Python ```bash pip install openai ``` ```python import os from openai import OpenAI client = OpenAI( api_key=os.environ["CAIRVA_API_KEY"], base_url="https://cairva.xyz/v1", ) completion = client.chat.completions.create( model="gemma4:e2b", messages=[{"role": "user", "content": "Summarise this ticket in one line."}], max_tokens=200, ) print(completion.choices[0].message.content) print(completion.model_extra["cairva"]["remaining"]) ``` The Python SDK puts extra response fields in `model_extra`, so the metering object is `completion.model_extra["cairva"]`. On a streamed chunk it's `chunk.model_extra["cairva"]` on the final chunk. ## Errors in the SDKs Cairva errors arrive as the SDK's normal API errors. The product code is on the error: ```javascript JavaScript try { await client.chat.completions.create({ model: "gemma4:e2b", messages }); } catch (err) { if (err instanceof OpenAI.APIError && err.code === "allocation_exhausted") { // Out of allowance: alert someone, don't retry. } throw err; } ``` ```python Python import openai try: client.chat.completions.create(model="gemma4:e2b", messages=messages) except openai.APIStatusError as err: if err.code == "allocation_exhausted": pass # Out of allowance: alert someone, don't retry. raise ``` Both SDKs retry `429` and `5xx` responses on their own and honour `Retry-After`. Leave that on. Don't retry `402` or `403`. See [Errors](https://cairva.xyz/docs/errors). ## Other clients Any HTTP client works. Send `Authorization: Bearer ` and JSON bodies to the endpoints in the [API reference](https://cairva.xyz/docs/chat-completions). Tools built on the OpenAI SDKs usually accept a custom base URL and key the same way. Check that they don't depend on tool calling or structured output, which Cairva doesn't support. See [OpenAI compatibility](https://cairva.xyz/docs/compatibility). --- # Become a supplier > Apply, get verified, and have a route attached before you list capacity. Source: https://cairva.xyz/docs/become-a-supplier Suppliers sell inference capacity they're authorized to sell. Cairva verifies every supplier before they can quote, and only lists capacity from verified suppliers. ## Who can supply You need a deployment that serves a model through an OpenAI-compatible chat completions endpoint, such as vLLM, SGLang, TGI or Ollama, and the right to sell access to it. Many providers make API credits non-transferable, so reselling credits bought elsewhere doesn't qualify. ## Apply 1. [Sign in](https://cairva.xyz/sign-in?next=/onboarding%3Fkind%3Dsupplier) and create a supplier organization. 2. Open **Verification** in the workspace and fill in the application. Only an organization admin can submit it. 3. Cairva operations review it and decide. The application asks for: | Field | Notes | | --- | --- | | Legal name, jurisdiction | The entity that will sign agreements | | Registration number | Optional | | Website | | | Contact name and email | Who Cairva talks to about the application | | Models | What you can serve | | Regions | Where it runs. At least one | | Capacity provenance | Where the capacity comes from: your own hardware, a reserved cloud deployment, and so on | | Authorization evidence | Why you're allowed to sell it | | Beneficiary | Who gets paid under agreements | ## Verification states | State | Meaning | | --- | --- | | `applied` | The organization exists, no application yet | | `reviewing` | Submitted and waiting for operations | | `verified` | Approved. You can list lots and quote | | `rejected` | Not approved. The reason is shown, and you can update the application and resubmit | | `suspended` | Verification withdrawn. Your active lots are paused | You can edit the application until you're verified. Every decision is recorded with the reviewer's reason. ## Routes A route connects a model on your deployment to Cairva's gateway. Cairva operations create routes for verified suppliers. The endpoint credentials are configured on Cairva's servers, are never shown in the workspace and never leave the gateway. Each route has a model, the model id your endpoint expects, a region, and a mode: `live` or `sandbox`. A route is checked for health before any agreement on it activates. See [Delivery and metering](https://cairva.xyz/docs/delivery-and-metering). Once you have a route, list capacity on it as [inventory lots](https://cairva.xyz/docs/inventory). --- # Inventory lots > List capacity on your route, get it reviewed, and quote buyers from it. Source: https://cairva.xyz/docs/inventory An inventory lot is a block of capacity on one of your routes that you're prepared to sell. Buyers see active lots on the [market](https://cairva.xyz/market), and every quote you issue is drawn from a lot. ## Create a lot Open **Inventory** in the workspace. Only verified suppliers can create lots, and only on their own routes. | Field | Notes | | --- | --- | | Route | One of your routes. It fixes the model and region | | Title | 3 to 120 characters | | Capacity class | Metered allowance, reserved throughput or dedicated window. Sandbox routes allow metered allowance only. See [Capacity classes](https://cairva.xyz/docs/capacity-classes) | | Availability | `available_now` or `quote_only` | | Window | Start and end of the capacity you're offering | | Input and output tokens | The total you're offering in the window | | Minimum input tokens | The smallest order you'll take, 0 for none | | Requests / min, tokens / min, max concurrency | The most any single agreement on this lot can get | | Public pricing | Optional. When on, set both indicative input and output prices per 1M tokens | | Permission reference | Optional pointer to your authorization to sell this capacity | ## Review A new lot starts in `pending_review`. Cairva operations review it and set it to `active`, `paused` or back to `draft` with a note. Only lots from verified suppliers can go active. Editing a lot that isn't a draft sends it back to `pending_review`. You can't lower its tokens below what agreements have already reserved. ## Lot states | State | Meaning | You can move it to | | --- | --- | --- | | `draft` | Not submitted | `pending_review`, `retired` | | `pending_review` | Waiting for operations | `retired` | | `active` | Listed and quotable | `paused`, `retired` | | `paused` | Not quotable | `pending_review`, `retired` | | `retired` | Closed for good | | ## What buyers see The market lists lots that are `active`, from verified suppliers, on enabled routes, with a window that hasn't ended. Buyers can request terms on a lot, which sends a request to you. ## Quoting from a lot When you issue a quote, the terms have to fit the lot: same model and class, a window inside the lot's, limits at or under the lot's, the route's region, and enough unreserved tokens. Sandbox lots settle as no-charge and live lots by external invoice. See [Requests and quotes](https://cairva.xyz/docs/requests-and-quotes#how-a-quote-is-built). When a buyer accepts, the contracted tokens are reserved on the lot so they can't be sold twice. Cancelling the agreement before activation releases them. --- # Delivery and metering > Route health checks, what you see about each buyer's usage, and pausing an allocation. Source: https://cairva.xyz/docs/delivery-and-metering Once an agreement on your lot activates, Cairva's gateway sends the buyer's requests to your route and meters them against the allocation. Here's what that looks like from the supplier side. ## How requests reach you The gateway calls your endpoint's chat completions API with the model id configured on the route. It forwards only the buyer's messages and the supported sampling fields: `max_tokens`, `temperature`, `top_p` and `stop`. Streaming requests ask your endpoint for usage in the stream with `stream_options.include_usage`. `max_tokens` is always set, and never more than the buyer's remaining output allowance. ## Usage reporting Your endpoint's reported token counts are what the buyer is billed against. Report `prompt_tokens` and `completion_tokens` in the response, and in the final streamed chunk. If your endpoint doesn't report usage, the request is marked `usage_pending` and Cairva operations reconcile it from your metering with written evidence. Reconciled events are labelled `reconciliation` in usage data. Inline reporting is faster and less work for everyone. ## Health checks Cairva checks each route by calling your endpoint's `/models` list and confirming the route's model id is in it. The route's health is `healthy` when it is, and `down` when the call fails or the model is missing. Checks run on a schedule from Cairva's background worker, and once more whenever an agreement on the route tries to activate. An agreement can't activate on a route that isn't enabled or didn't pass that check. The public [status page](https://cairva.xyz/status) shows each route's health. See [Status and incidents](https://cairva.xyz/docs/status-and-incidents). ## What you can see In **Deliveries** you see each allocation on your lots: quota, used, held and remaining, request counts, latency and the usage events. They're the same events the buyer sees through [`GET /v1/usage`](https://cairva.xyz/docs/usage). You don't see prompts or completions. Cairva doesn't store them. ## Pausing an allocation You can pause a `ready` or `active` allocation with a written reason, for example to deal with an incident. The buyer is notified with your reason, and their requests fail with `allocation_paused` until you resume. Resuming returns it to `active`, or `ready` if its window hasn't started. A pause doesn't change the agreement. If you can't deliver what was agreed, the buyer can open an `under_delivery` dispute. See [Agreements](https://cairva.xyz/docs/agreements#disputes). ## Route disabled Cairva operations can disable a route, with a reason. Requests to allocations on it fail with `502 route_disabled` until it's enabled again. --- # Privacy and data > What Cairva stores about requests, what it doesn't, and who can see requests, quotes and usage. Source: https://cairva.xyz/docs/privacy-and-data Cairva is built so the deal stays between the two parties and the request content stays between the buyer and the route that serves it. This page lists what's stored and who can see what. The [privacy policy](https://cairva.xyz/legal/privacy) is the binding document. ## Prompts and completions Prompts and model outputs aren't stored. They pass through the gateway to the supplier route that serves your allocation and back to you. Cairva's logs redact fields that carry message content, keys and secrets. For each request Cairva stores metadata only: | Stored | Not stored | | --- | --- | | Allocation, key, model, whether it streamed | Messages | | Your `x-request-id` and the provider's request id | Completion text | | Status, error code, latency, timestamps | Reasoning or any other model output | | Tokens held, and tokens booked as a usage event | | The supplier's own systems receive your messages, because that's how inference is delivered. Their handling is governed by your agreement with them. Use the request's privacy requirements field to state what you need before you accept a quote. ## Requests and quotes | Who | Sees | | --- | --- | | Your organization | Your requests, every deal room on them, your agreements | | An invited supplier | The request it was invited to, and its own deal room. Never other suppliers' quotes | | Other suppliers and the public | Nothing | | Cairva operations | Everything, to run the market and resolve disputes | Drafts stay private to your organization until you send them. A supplier can quote without disclosing its name until you accept. ## Usage The buyer and the supplier on an agreement see the same usage events and the same remaining allowance. Nobody else outside Cairva operations does. ## API keys Only a keyed hash of each key is stored. The secret is shown once when it's created and can't be retrieved later. ## Audit trail State changes on requests, quotes, agreements, allocations, keys, lots, routes and suppliers write an audit event with the actor, time, the state before and after, and any reason. The agreement page shows its history to both parties. Cairva operations can see the full log. ## Public pages The [market](https://cairva.xyz/market) shows active lots, not buyers or deals. The [status page](https://cairva.xyz/status) shows route health, request counts and incidents, not who made the requests. --- # Settlement > Invoices, payment confirmation by operations, and sandbox agreements that never involve money. Source: https://cairva.xyz/docs/settlement Settlement is how an agreement gets paid before its allocation activates. Today there are two methods, and the route decides which one applies. | Method | Used for | Money moves | | --- | --- | --- | | External invoice, confirmed by Cairva operations | Live routes | Yes, outside Cairva | | Sandbox, no charge | Sandbox routes | No | Prices are in USD. A quote can't use the other method: a live lot quoted as no-charge, or a sandbox lot quoted for money, is rejected. ## External invoice 1. The supplier countersigns. Cairva issues an invoice (`INV-…`) for the agreement price, and the agreement moves to `awaiting_payment`. 2. The buyer pays the invoice outside Cairva and records the payment reference in the workspace. The invoice moves to `pending_confirmation`. 3. Cairva operations check the payment and either confirm it with an evidence note, or reject it with a note saying why. A rejected payment puts the invoice back to `issued` and the buyer sees the note. 4. Once confirmed, the invoice is `paid`, the agreement moves to `provisioning`, and activation checks run. The allocation can't activate until the invoice is paid. ## Sandbox, no charge When the supplier countersigns a sandbox agreement, Cairva records a zero-amount settlement, marked paid, with the note "Sandbox agreement. No money moves." The agreement goes straight to activation checks. ## Price The agreement price is fixed at acceptance: input tokens times the input price per million, plus output tokens times the output price per million. Overage is always a hard stop: the price doesn't grow with usage, and requests stop when the allowance runs out. ## Cancellation and voided invoices If an agreement is cancelled before activation, any invoice that's still issued or awaiting confirmation is voided. After activation, cancellation follows the terms written into the quote. See [Agreements](https://cairva.xyz/docs/agreements#cancelling). --- # Status and incidents > How route health is checked and what the public status page shows. Source: https://cairva.xyz/docs/status-and-incidents The public [status page](https://cairva.xyz/status) shows the health of every route and any incidents Cairva operations have posted. It only shows facts backed by Cairva's own records, with the time window stated. ## Route health Cairva's background worker checks each route on a schedule by calling the route's `/models` endpoint and confirming its model id is listed. Each check records: | Field | Meaning | | --- | --- | | Health | `healthy` if the model is listed, `down` if the call failed or the model is missing, `unknown` before the first check | | Last checked | When the latest check ran | | Latency | How long that check took | A route is also checked once more whenever an agreement on it tries to activate, and the agreement only activates if that check passes. For each route the status page also shows the model, region, mode (`live` or `sandbox`), whether operations have it enabled, and how many requests it handled in the last 24 hours and how many of those succeeded. ## Incidents Cairva operations post incidents when something affects delivery. | Field | Values | | --- | --- | | Severity | `minor`, `major`, `critical` or `maintenance` | | Status | `investigating`, `monitoring` or `resolved` | Updates are appended to the incident with a UTC timestamp, so the history stays readable. An incident can be tied to one route. ## When a route is down - Requests can fail with `502 upstream_error`. The hold is released and nothing is charged. - If operations disable the route, requests fail with `502 route_disabled`. - Agreements on the route can't activate until it's healthy again. If an outage cost you capacity you'd agreed to, open an `availability` dispute on the agreement. See [Agreements](https://cairva.xyz/docs/agreements#disputes). --- # Glossary > Plain definitions of the terms used in Cairva and in these docs. Source: https://cairva.xyz/docs/glossary ## Agreement The contract created when a buyer accepts one supplier-issued quote version. It holds the accepted terms, the price, a contract hash and its own state. See [Agreements](https://cairva.xyz/docs/agreements). ## Allocation The runnable form of an agreement: contracted input and output tokens, a delivery window and rate limits, carved out for one buyer. API keys are bound to it and every request is metered against it. See [Allocations](https://cairva.xyz/docs/allocations). ## Activation checks The conditions that must all be true for an agreement to go live: countersigned, settled, supplier verified, lot active, route enabled and passing a live health check, and the window not over. ## Capacity class What kind of capacity a lot or quote promises: metered allowance, reserved throughput or dedicated window. See [Capacity classes](https://cairva.xyz/docs/capacity-classes). ## Countersignature The supplier's confirmation of an agreement the buyer accepted. It issues the invoice, or the no-charge record for sandbox. ## Deal room The private space where one buyer and one supplier exchange messages and quote versions on a request. ## Depleted The allocation state reached when the input or output allowance is fully used. Its agreement completes. ## Gateway Cairva's OpenAI-compatible API under `/v1`. It authenticates keys, admits requests against the allocation, calls the route and meters the result. ## Hold Allowance reserved for a running request: an input estimate from the prompt plus `max_tokens` of output. Released when real usage is booked or the request fails. See [Rate limits and holds](https://cairva.xyz/docs/rate-limits). ## Inventory lot A block of capacity a supplier lists on one of its routes, with a window, token amounts and limits. Quotes are drawn from lots. See [Inventory lots](https://cairva.xyz/docs/inventory). ## Live A route, lot or allocation backed by commercial supply and settled by invoice. Keys start with `cv_live_`. No live supply is listed today. ## Metered allowance A prepaid token allowance on a shared route, with rate limits but no dedicated hardware or throughput minimum. ## Quote version One immutable set of proposed terms in a deal room, with a number, an expiry and a SHA-256 hash of the terms. See [Negotiation](https://cairva.xyz/docs/negotiation). ## Reconciliation Booking usage for a request whose provider didn't report token counts inline. Done by Cairva operations from provider metering, with written evidence. ## Request (RFQ) A buyer's private description of the capacity they need, sent to suppliers they choose. See [Requests and quotes](https://cairva.xyz/docs/requests-and-quotes). ## Route The connection between a model on a supplier's deployment and Cairva's gateway, with a region and a mode. Created by Cairva operations for verified suppliers. ## Sandbox A route, lot or allocation used for development and integration. The flow is real, no money moves, and there are no guarantees. Keys start with `cv_test_`. See [Sandbox](https://cairva.xyz/docs/sandbox). ## Scope What a key may do: `inference` to call models, `usage:read` to read the allocation and usage. ## Terms hash The SHA-256 hash of a quote version's terms, price and expiry. The buyer echoes it on acceptance, so they accept exactly what they reviewed. ## Usage event One booked record of input and output tokens for a request, visible to both buyer and supplier. See [Usage events](https://cairva.xyz/docs/usage). ## Usage pending A request that completed without reported token counts. Its hold stays until reconciliation. ## Verified supplier A supplier organization whose application Cairva operations approved. Only verified suppliers can receive requests, list lots and quote. --- # FAQ > Short answers to the questions developers and buyers ask first. Source: https://cairva.xyz/docs/faq ## Is Cairva live? Cairva is in its Genesis phase. The full product runs today on one sandbox route serving `gemma4:e2b` through Ollama. No live commercial supply is listed yet and sandbox agreements don't involve money. See [Sandbox](https://cairva.xyz/docs/sandbox). ## Do I need a new SDK? No. Use the official OpenAI SDK for JavaScript or Python with Cairva's base URL and your Cairva key. See [SDKs](https://cairva.xyz/docs/sdks). ## Can I just buy credits? No. Cairva sells negotiated capacity, not credits. You agree a model, a token allowance, a window and limits with a supplier, and the gateway enforces that agreement. ## Can one key call several models? No. A key is bound to one allocation, and an allocation covers one model. Use one key per allocation. ## Why did my request fail with model_not_permitted? The `model` field has to be exactly the id from `GET /v1/models` for your key. The error message names the model your key can call. ## Why is `usage` null? The provider didn't return token counts for that request. Cairva doesn't estimate, so the request is marked pending and reconciled by Cairva operations from provider metering. See [Rate limits and holds](https://cairva.xyz/docs/rate-limits#when-usage-doesn-t-arrive). ## Why did I get an empty answer? Usually `max_tokens` ran out while the model was reasoning. Reasoning counts as output tokens but isn't returned. Check for `finish_reason: "length"` and raise `max_tokens`. See [Chat completions](https://cairva.xyz/docs/chat-completions#output-tokens-you-don-t-see). ## Am I charged for failed requests? No. A failed request releases its hold and books no usage. It does count toward requests per minute. ## Am I charged if I disconnect from a stream? Yes, for what the model generated. Cairva reads the stream to the end so the provider's usage report arrives. Lower `max_tokens` to cap it. ## Does Cairva support tool calling or JSON mode? No. Those fields are ignored and plain text is returned. See [OpenAI compatibility](https://cairva.xyz/docs/compatibility). ## Does Cairva store my prompts? No. Prompts and outputs pass through to the supplier route and back. Cairva stores request metadata and token counts. See [Privacy and data](https://cairva.xyz/docs/privacy-and-data). ## Can other suppliers see my request? No. Only the suppliers you invite, your organization and Cairva operations can see it. ## How do I pay? Live agreements are paid by external invoice, and Cairva operations confirm payment before activation. Sandbox agreements are free. See [Settlement](https://cairva.xyz/docs/settlement). ## What happens when my allowance runs out? The allocation becomes `depleted`, its agreement completes, and requests fail with `402 allocation_depleted`. Overage is always a hard stop. ## Can I resell API credits I bought somewhere else? No. Many providers make their credits non-transferable. Cairva only lists capacity whose supplier is authorized to sell it. ## Is there a status page? Yes, at [/status](https://cairva.xyz/status). It shows route health, recent request counts and incidents. ## Where are these docs as Markdown? Every page has a **View as Markdown** link, and you can add `.md` to any page URL, such as [/docs/quickstart.md](https://cairva.xyz/docs/quickstart.md). The introduction is at [/docs/introduction.md](https://cairva.xyz/docs/introduction.md). The whole set is at [/llms.txt](https://cairva.xyz/llms.txt) as an index and [/llms-full.txt](https://cairva.xyz/llms-full.txt) in one file.