Sign in

For agents

For coding agents

The five questions an agent asks of an AI gateway, answered for Recovea: cost tracking, budgets, observability, caching and fallback.

On this page

The five answers

Question Answer
Cost tracking Yes. Every call through the gateway is metered as it returns.
Budgets Yes. An armed cap refuses with a 402 before your provider is contacted.
Observability Receipts. Every metered call is written to a hash-chained ledger.
Caching No. The gateway keeps no prompt and no reply, so it has nothing to answer from.
Fallback No. A request goes to the provider its path names; fall back in your own code.

Cost tracking: yes

Change the base URL and keep your SDK, your models and your request shape; the Quickstart walks one request through. The first call is metered as it returns: cost, tokens, model and route land in the platform, and a receipt is written into the hash-chained ledger.

Surface Base URL
OpenAI-compatible https://api.recovea.ai/v1
Anthropic /v1/messages https://api.recovea.ai
An upstream you declared https://api.recovea.ai/upstream/v1

A receipt covers what routes through the gateway. Spend outside the path is not metered here.

Budgets: yes, and how they refuse

An armed cap refuses the next request with a 402 before your provider is contacted, so the refused call costs nothing. The body has one shape for every cap; only the sentence changes, naming the window and, for a key's cap, the key. This is the organization's monthly cap:

A request the cap refuses
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"
  }
}

Branch on the status, the type and the code, never on the sentence.

A cap that cannot confirm live spend answers 503 with Retry-After: 1 and the code budget_unverifiable. It is not an overspend, and it clears on its own: retry the same call after the second the header names.

The whole refusal contract, every window's sentence included, is on Cap; where a cap is set, and who may change it, is Where a cap is set. What fails open and what fails closed: Fail-open and fail-closed.

Observability: receipts

Every proxied response carries x-recovea-request-id, Recovea's own id for the request, and the receipt for that call carries the same id. Receipts are rows in a ledger where each row carries the hash of the row before it: recipe recovea-chain-v1, SHA-256 over fourteen fields joined by U+001F, the first row chained to a genesis of 64 zeros. Change one row and every hash after it breaks. Prove is the receipt and its export; The hash-chained ledger re-derives one by hand.

Caching: no

The gateway forwards each request to your provider and meters the answer as it returns. It keeps no copy of a reply to serve again: the metering schema has no field that can hold a prompt or a reply. Tokens your provider served from its own cache are metered as tokens_cached, one of the fourteen fields a receipt is hashed over. What the record holds, field by field: Content-free by schema.

Fallback: no, and the code to write instead

The gateway sends a request to the provider its path names: /v1 to the OpenAI-compatible provider you connected, /v1/messages on the bare host to Anthropic, /upstream/v1 to the upstream you declared. It does not retry a failed request against another provider.

If your application wants a second provider when the first one fails, write it in your own code, and keep the refusals out of it. A cap decides on what it bounds, never on the destination, so the key a cap refuses on one surface is refused on all three; a 503 with budget_unverifiable is the gateway asking for a retry of the same call.

Fall back on a provider failure, never on a refusal
import os

from anthropic import Anthropic
from openai import APIConnectionError, APIStatusError, OpenAI

KEY = os.environ["RECOVEA_API_KEY"]
primary = OpenAI(api_key=KEY, base_url="https://api.recovea.ai/v1")
secondary = Anthropic(api_key=KEY, base_url="https://api.recovea.ai")


def ask(prompt: str) -> str:
    try:
        reply = primary.chat.completions.create(
            model=os.environ["PRIMARY_MODEL"],
            messages=[{"role": "user", "content": prompt}],
        )
        return reply.choices[0].message.content
    except APIStatusError as error:
        # 402 is a cap refusing and 503 is a cap asking for a retry: neither
        # is a provider failure, and a second provider would get the same answer.
        if error.status_code in (402, 503) or error.status_code < 500:
            raise
    except APIConnectionError:
        pass

    reply = secondary.messages.create(
        model=os.environ["SECONDARY_MODEL"],
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}],
    )
    return reply.content[0].text

Where the rest is

  • Quickstart: one base URL change, one request, and what comes back.
  • Cap: what a cap is, what the request carries, and every answer it sends.
  • API reference: the gateway's operations, headers and refusals, generated from openapi.json.
  • The Baseline package: the passive tap's endpoint, its event and what the server answers.
  • /llms.txt is the index of every page here, and /llms-full.txt is every page as Markdown in one file.