Quickstart
Change one base URL and send one request: the headers that come back, the first receipt, and what an armed cap answers.
On this page
Recovea is an in-path gateway. You keep your provider account, your SDK, your models and your request shape; you change the base URL your SDK calls and the key it sends. Every request that goes through the gateway is metered and written as a receipt, and a cap you arm is enforced before your provider is called.
Before you start
- An organization. Sign up at platform.recovea.ai. The platform is platform.recovea.ai.
- A Recovea key. In the platform, Settings → Organization → API keys →
Mint key. The secret starts
rcv_live_and is shown once: "Recovea stores a hash of this key, never the secret itself." Put it in your environment asRECOVEA_API_KEY. - Your provider key, added once on the same page under Provider keys → Add provider key. The platform calls your provider with it before it is saved, and then it is "Sealed with envelope encryption at rest. Plaintext only in flight to your provider."
Your code sends the Recovea key. The gateway signs the call to your provider with the provider key it holds for your organization.
OpenAI-compatible SDKs
The OpenAI clients keep the /v1 they already use. The base URL is the only
change.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["RECOVEA_API_KEY"],
base_url="https://api.recovea.ai/v1", # the only change
)import OpenAI from "openai"
const client = new OpenAI({
apiKey: process.env.RECOVEA_API_KEY,
baseURL: "https://api.recovea.ai/v1", // the only change
})Send one request. -D - prints the response headers above the body.
curl -sS -D - https://api.recovea.ai/v1/chat/completions \
-H "Authorization: Bearer $RECOVEA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"ping"}]}'Every path under /v1 is forwarded as it arrives: /v1/chat/completions,
/v1/responses, /v1/embeddings and any endpoint your provider adds. One
change is made to one kind of request: a streaming /v1/chat/completions
request that says nothing about stream_options is sent with
"stream_options":{"include_usage":true} added, so the stream reports its token
counts. The usage chunk comes back to you as your provider sent it. A request
that sets stream_options in any form, including "include_usage": false, is
forwarded untouched.
Anthropic SDKs
The Anthropic clients take the bare host, with no /v1. Both wire formats are
served on the same host because their paths do not overlap: /v1/messages is
Anthropic's.
import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["RECOVEA_API_KEY"],
base_url="https://api.recovea.ai", # the only change
)The SDK sends the key in x-api-key, the way it does with Anthropic.
curl -sS -D - https://api.recovea.ai/v1/messages \
-H "x-api-key: $RECOVEA_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-sonnet-4-5","max_tokens":64,"messages":[{"role":"user","content":"ping"}]}'An integration written against https://api.recovea.ai/anthropic keeps
working. It is a deprecated alias for the same surface.
Any other OpenAI-compatible endpoint
A destination you declare is reached through its own prefix,
https://api.recovea.ai/upstream/v1. Traffic sent to /v1 never moves to it:
the destination is decided by the request path.
- In Settings → Organization → API keys, under Your upstream, give the
destination a name and its
httpsaddress, and save it. - Connect its key. The platform calls the destination with the key and keeps the key only if the destination answers.
- Start sending traffic. The section reads Live.
client = OpenAI(
api_key=os.environ["RECOVEA_API_KEY"],
base_url="https://api.recovea.ai/upstream/v1",
)Requests through it are metered and capped like the rest. The receipt carries
the cost your destination reported, or no cost where it reports none. Until a
destination is declared, requests to /upstream/v1 are refused.
What comes back
A served response is your provider's response, headers and body, byte for byte. The gateway adds one header of its own:
x-recovea-request-id: req_0192f3a4b5c6d7e8f9a0b1c2d3e4f5a6The id is req_ and 32 lowercase hex characters, and it is on every response,
refusals included. Keep it in your logs: it is the join between a response and
its receipt.
When an armed cap stands over the request, the response also names the kind of cap and its window:
x-recovea-budget-scope: organization
x-recovea-budget-window: monthx-recovea-budget-scope is organization, key or run, and
x-recovea-budget-window is day, week, month, year, lifetime or
run. They name the tightest armed cap the request is bound by. With no armed
cap, neither header is sent.
Nothing Recovea reads from your request reaches your provider: the
x-recovea-* request headers are removed before the call is forwarded.
The first receipt
The request is metered as it returns: its model, route, token counts and cost
are recorded with no prompt and no completion, and it is written as a receipt
in your organization's hash-chained ledger. The receipt's request_id is the
x-recovea-request-id your response carried. Receipts are on for every plan.
In the platform, the request is on the Requests page, and the Ledger page exports the period's receipts with everything needed to re-derive the chain yourself. Prove describes the receipt and the recipe.
When a cap refuses
An armed cap decides before your provider is contacted. At the cap, the request is refused and the provider is never called, so the refused request costs nothing. This is what a monthly cap on the organization answers:
HTTP/1.1 402 Payment Required
{
"error": {
"message": "Monthly budget reached. Traffic resumes at reset, or raise the cap.",
"type": "insufficient_quota",
"param": null,
"code": "budget_exceeded"
}
}The refusal carries x-recovea-budget-scope: organization and
x-recovea-budget-window: month. Branch on the status, error.type and
error.code; the message is prose and names the window that was reached.
Cap has every window's sentence.
When spend cannot be confirmed
An armed cap that cannot confirm the spend it is counting refuses rather than let spend run unwatched, and says so with its own status:
HTTP/1.1 503 Service Unavailable
retry-after: 1
x-recovea-budget-scope: organization
x-recovea-budget-window: month
{
"error": {
"message": "Monthly spend cannot be confirmed right now. Retry in a moment.",
"type": "insufficient_quota",
"param": null,
"code": "budget_unverifiable"
}
}It is 503 with Retry-After: 1, not 402: nothing was spent past a line,
and it clears on its own. Retry after the interval, and do not treat it as your
budget running out. Read the status and error.code together, because the
type is the same as the 402's.
If the gateway degrades
Designed to fail open: if the gateway degrades, traffic goes straight to your provider. The only stop is a cap you armed. Fail-open and fail-closed says which is which.
Next
Cap your AI spend sets a cap in the platform and shows the refusal it answers with.