Skip to content
Obol

The control plane for agent traffic.

One key, one policy, one bill across LLM providers, MCP servers, and REST APIs. Every model and tool call is authenticated, scoped, logged, and accountable — and your vendor credentials never reach the agent.

Design-partner access. No spam, no vendor keys asked for — ever.

  • /v1/* OpenAI-compatible
  • /mcp Streamable HTTP
  • docker compose up self-host
re_01J9X4…ALLOWpol_3ka… rev 14upstream re_3PqK…sample
Inbound credential ≠ outbound credential. The agent holds an Obol key; Stripe, GitHub, and model keys stay vaulted and are injected only on the outbound hop.

Your agents already act in production. Nothing is checking the receipt.

Provider keys in env files, OpenRouter for failover, MCP blocks pasted into IDE configs with live tokens, a handwritten Stripe wrapper — and no answer to “which agent called create_refund at 18:03, and under what authority?”

coding agentghp_••••support agentsk_live_••••ops runbooksk-••••StripeGitHubmodel providerinternal APIno decision pointno policy · no receipt
Every agent carries its own raw vendor secret and reaches every vendor directly. Nothing in the middle decides, and nothing writes down what happened.
{
  "mcpServers": {
    "stripe": {
      "command": "npx",
      "args": ["-y", "@stripe/mcp"],
      "env": { "STRIPE_SECRET_KEY": "sk_live_••••" }
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@github/mcp-server"],
      "env": { "GITHUB_TOKEN": "ghp_••••" }
    },
    "openai": {
      "command": "npx",
      "args": ["-y", "openai-mcp"],
      "env": { "OPENAI_API_KEY": "sk-••••" }
    }
  }
}
What an IDE config looks like today.
Secret sprawl
Agents hold raw sk_live_ keys and OAuth tokens in IDE configs and prompt context. One leak is the full vendor blast radius.
No argument-level authorization
API gateways ask “may this token hit this route?” Agents need “may this agent refund this amount in this environment — and has a human approved it?”
No verification
A model can claim “I refunded the customer” while the vendor returned requires_action. Nothing catches the lie.
No unified metering
Token spend and tool-call spend live in different dashboards. Budgets cannot fail closed across both.

Model routing is solved. The tool path is where the cost and the risk moved.

One key in. Vaulted credentials out.

Point your app at one OpenAI-compatible URL and your IDE at one MCP URL. Obol authenticates the key, filters what the agent can see, authorizes what it can do, injects the vendor credential on the way out, and writes a receipt.

gateway.tryobol.dev/v1 · gateway.tryobol.dev/mcpauthenticatesnapshotprefilterCELauthorizeCedarapprovalif destructiveinjectoutbound onlyyour app · your IDEob_live_…DENYnot granted by policyvaultOAuth · restricted keysBYOK model keys••••outbound hopStripeGitHubOpenAIreceiptALLOWpolicypol_…upstreamre_…vendor200 oksample values · native route, gateway-observed
The inbound key is an Obol key; the vendor credential is unwrapped in-process and joins only on the outbound hop. Denied calls never reach a vendor. Identifiers and statuses shown are samples.
Your app, unchanged except the base URL and the key
curl https://gateway.tryobol.dev/v1/chat/completions \
  -H "Authorization: Bearer ob_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "…"}]
  }'
  1. AuthenticateThe virtual key is hashed and looked up; revoke propagates in 60 seconds or less.
    Redis cached KeyContext, ≤60s revoke
  2. Budget & rateSpend and request caps are checked before anything leaves. Caps fail closed.
    spend / rpm fail closed
  3. RouteYour own provider key is unwrapped in-process and injected on the outbound hop.
    BYOK provider key injected
  4. StreamTokens stream back through the gateway. No database sits on the hot path.
    Postgres never on the path
  5. MeterTokens in, tokens out, model, and cost are recorded after the response.
    UsageEvent, async
Virtual key
An Obol-minted credential (ob_live_…) scoped to a workspace, models, tools, budget, rate limits, env, and expiry; the only credential an agent ever holds.
Connection
A workspace's instance of a connector: its OAuth tokens or restricted key, encrypted, plus scopes and status.
Snapshot
The compiled per-workspace bundle of keys, connections, tools, and Cedar policy that the gateway evaluates from; the only way state reaches the data plane.

The wrong tool cannot execute.

Cedar decides every tool call on principal, action, resource, and context — arguments included. The policy set compiles into the workspace snapshot and is evaluated in-process, default-deny. We never guarantee the model picks the right tool. We guarantee the wrong one does not run.

stripe.create_refund { amount_usd: 42 }sample
  1. satisfied
    prefiltercel · visible_to(key_7Hq…)

    listed in tools/list

  2. satisfied
    principalin Role::"support-agent"

    key_7Hq… → support-agent

  3. satisfied
    action== Action::"stripe.create_refund"

    stripe.create_refund

  4. satisfied
    resourcein Env::"prod"

    prod

  5. satisfied
    contextargs.amount_usd <= 50

    42

ALLOW
allow · pol_3ka… rev 14 · idt_4fd… minted

One permit matched — including the argument. The gateway mints the idempotency key before the request leaves it, injects the vaulted Stripe credential on the outbound hop, and writes the decision onto the receipt.

// Support agents refund up to $50 unattended.permit(  principal in Role::"support-agent",  action == Action::"stripe.create_refund",  resource in Env::"prod") when { context.args.amount_usd <= 50 }; // Destructive in prod: only with an approval.permit(  principal,  action in ActionGroup::"destructive",  resource in Env::"prod") when { context.approval.granted };

Permit one matched on all four: principal, action, resource, and the argument.

Cedar source and sample calls are illustrative. Policy is authored per workspace, validated against the schema in packages/cedar, compiled into the snapshot, and evaluated in-process — never a network hop per decision.
Default-deny
No permit, no call. Cedar is the authorization of record, and its decision, policy id, and revision are written onto the receipt before anything is dispatched. The policy set is compiled data evaluated in-process — never a network hop or a database read per decision.
Arguments, not just tool names
context.args.amount_usd <= 50 is a condition Cedar evaluates, not a convention a connector is trusted to honour. The request hash is bound to the decision, so the call that runs is the call that was authorized.
Held, not failed
A destructive tool in prod returns approval_required with an approval id and an inbox link — a distinguishable outcome, not an error. On approve, the same invocation and the same idempotency key resume, exactly once.
Invisible, not merely forbidden
The CEL prefilter runs before tools/list, so a tool this key may not call never appears in the model's context at all. A denied call and a nonexistent one return the same bytes, so the catalog cannot be enumerated by probing.

A receipt that says what it can prove.

Every authenticated tool call gets one gateway-minted receipt, keeping authorization, dispatch, and verification as three separate summaries. Evidence trust is assigned by Obol from the route the call actually took — never from what the response claims about itself. Only gateway_observed earns the word proves, and untrusted can never read verified.

One bounded HTTP request the gateway builds from the tool's schema and sends itself, with the vaulted credential injected on the outbound hop.

strongest

  • gateway_observedthe receipt rests here

    Obol built the request, injected the vaulted credential, opened the socket, and read the response itself. The only class that earns the word proves.

  • vendor_signed

    The vendor's own lifecycle webhook, terminating at Obol and checked against per-subscription connection material. The vendor's statement about the vendor's system.

  • connector_attested

    A field derived by a reviewed first-party worker. The worker normalises; it never holds a credential, never calls the vendor, and never assigns its own trust.

  • broker_attested

    A broker-signed statement over the execution, validated against a pinned workspace-scoped key. Non-repudiation, not correctness — a broker is a vendor, never an authority.

  • untrusted

    A result from a server Obol does not operate. Recorded in full, correlated, billed — and never, at validation time, enough to reach verified.

weakestEvidence trust is assigned by Obol from the configured route — never from what the response claims about itself.
obol gatewaybrokervendor

Obol's own socket terminated at Stripe, and the response bound to the assertions the tool declared. On this route the receipt proves what the call did.

rcp_01J9X4… · receipt_revision 2sample
invocation_id
inv_01J9X4…
tool
stripe.create_refund
request_hash
sha256:9f2c…e1
idempotency_key
idt_4fd… · propagated to the vendor

authorizationwhat policy allowed

decision
allow
policy_id
pol_3ka…
revision_id
rev 14

dispatchwhat the attempt observed

state
accepted
evidence_trust
gateway_observed
vendor_http_status
200
upstream_id
re_3PqK… (Stripe)

verificationwhat the evidence supports

state
verified
method
response_binding
evidence_trust
gateway_observed
evidence_digest
sha256:c41a…
latency_ms 412 · usd_micros 4000 · created_at 2026-08-29T11:04:18Z
Sample identifiers and values. Authorization, dispatch, and verification are three separate summaries on one gateway-minted receipt, so what policy allowed is never confused with what the vendor did.
Claimed and observed are different fields
The model's claim and the vendor's observed status are stored separately, and neither is rounded up. A disagreement resolves to contradicted; missing or ambiguous evidence resolves to inconclusive. There is no optimistic success anywhere in the vocabulary.
Idempotency is always Obol's
The gateway mints the key before the call leaves it and holds it across approval, transport retries, and safe redispatch. On a federated route the key may not reach the vendor at all — brokers offer no idempotency primitive, so their retries stay off — and the receipt records which guarantee actually applied.
One log for tokens and actions
Usage events and receipts share workspace, key, and policy ids. Filter by any of them and the model calls and the tool calls come back together, on one bill and one audit trail.
The gateway is the only receipt authority
No worker, no remote MCP server, and no catalog broker mints a receipt, assigns evidence trust, or declares a call verified. A broker is a credential-custody and catalog vendor. Making the least-trusted component its own auditor is the failure mode this design exists to avoid.

What ships in v1.

Scope is a promise. One line runs the length of it — what the agent can reach, what happens at the gate, what the team gets — and then it stops.

  1. What the agent can reach

    Surface area, not connector count.

  2. Unified access

    gateway

    One key works on /v1/chat/completions, /v1/responses, /v1/embeddings, /v1/messages, and /mcp (Streamable HTTP).

  3. Catalog without connector debt

    catalog

    OpenAPI import to a generic executor; remote MCP federation with namespacing (stripe.create_refund); a few hand-built workers where a vendor earns one.

    View catalog

    2,148 connectors mapped — coverage, not availability.

  4. What happens at the gate

    Every call, in-process, default-deny.

  5. Tool-aware authorization

    cedar

    A CEL prefilter decides visibility — a tool the key may not call never appears in tools/list. Cedar decides permission, with argument conditions like amount_usd <= 50. Compiled data, evaluated in-process: no network hop per decision, and the default is deny.

  6. Credential brokering

    vault

    OAuth, restricted vendor keys, BYOK. Per-connection DEK, KEK in your KMS; plaintext exists only inside the gateway process, on the outbound hop. Revoke propagates in ≤ 60 s. Never a pooled vendor account.

  7. Approvals

    control

    Destructive tools in prod return approval_required unless policy explicitly grants. A human approves in the inbox; a worker executes once, under one idempotency key.

  8. Verification & receipts

    receipt

    Every tool call gets a receipt: policy id, upstream id, request hash, idempotency key, vendor status, latency, cost — and the evidence class, so a gateway-observed call is never confused with a federated one.

  9. What the team gets

    One key, one policy, one bill.

  10. Metering & budgets

    control

    Usage events for tokens and tool calls; caps fail closed on both planes; one invoice.

  11. Self-host from day one

    infra

    One docker compose up, your KMS or a local key. OSS-core plus a hosted cloud.

  12. Not in v1, on purpose.

    • No marketplace
    • No 100 providers
    • No % of your payment volume
    • No pooled vendor accounts

Run it in your VPC. Same binaries, your KMS.

Dev starts with the local setup below. Hosted is the same compose with managed Postgres and Redis. Self-host is the same compose with your keys. The shape below never changes between them — only the labels do.

/v1/*/mcpgatewayconnectorswebrediscontrolpostgresKEKyour laptop×1containercontainerlocal age keyone compose file · six services

Postgres, Redis, gateway, control, connectors and web come up together. The key-encryption key is a local age key, so nothing needs a cloud account.

$ git clone <your-obol-repository> obol && cd obol$ make -C infra env$ make -C infra age-key         # install age first$ age-keygen -y infra/secrets/age.key         # public recipient$ vi infra/.env         # configure values below$ make -C infra prod-upgateway   listening on :8080  (/v1/*, /mcp)control   listening on :8000

Install Docker Compose, Make, and age. In infra/.env, set POSTGRES_PASSWORD, OBOL_SERVICE_JWT_SECRET, and OBOL_AGE_RECIPIENT to the public value printed above. Configure your Clerk instance keys and issuer for dashboard sign-in. Follow the setup guide for the remaining deployment settings.

Rust gateway · Python control plane · Postgres + Redis · your KMS (AWS/GCP) or age + SOPS

Bring your own app. BYOA uses a customer-owned OAuth app, so vendor tokens live in your cloud project rather than ours — the enterprise custody option, and never a pooled vendor account.

No seats. Metered calls. Never a cut of your volume.

The subscription is per workspace, or one for the whole startup on Team — never per seat. Usage is priced by what a call can do, and a call the gate refuses costs nothing.

Design partners now · self-serve at public launch

What we bill for

  • A subscription, never a seat per workspace, or one for the whole startup on Team. An agent is not a seat, and neither is the engineer watching it.

  • Dispatched tool calls metered per 1,000 and priced by what the call can do. A read costs less than a refund.

  • LLM requests through the gateway for governing the request — never a spread on your tokens.

What we never bill for

  • Your model tokens metered for your budgets, billed by your provider.

  • Your payment volume we are not a merchant of record, and never take a percentage.

  • Calls that never left the gate denials, retries, fallbacks, approval resumes, and verification probes are free.

Self-host

Available now

$0

the same binaries, your infrastructure

One compose file, your VPC, your keys.

  • Unlimited workspaces
  • Full policy engine and receipts
  • Your KMS, or age + SOPS
  • No usage metering, no phone home

Pro

Coming soon

$49

per month + usage

One workspace, writing to production.

  • Hosted gateway and control plane
  • ob_live_ keys with budgets
  • Cedar policies with argument conditions
  • Approvals inbox
  • 30 days of receipts

Team

Design partners now

$1,500

per month + usage

Dev, stage, and prod for the whole startup.

  • Every workspace your startup runs
  • SSO and workspace roles
  • One year of receipts
  • Grandfathered usage schedule
  • Assisted onboarding

Enterprise

Talk to us

From $30,000

per year, committed volume

Your VPC, your KMS, your auditors.

  • Self-host with support
  • Bring your own app (BYOA)
  • SCIM and audit streaming
  • MSA, DPA, and an SLA

Prices exclude your own vendor and provider bills, which you keep paying directly. Self-serve checkout is not open yet: Pro opens at public launch, and every other tier starts with the same conversation.

Per 1,000 dispatched calls

A read-only search costs us a policy evaluation and a receipt row. A destructive write costs an approval round-trip, a verification plan, webhook correlation, and the evidence retention that makes any of it worth having. The price says so.

Read-only toolsretrieve, list, search
$0.30
Idempotent writessafe to repeat under one key
$1.00
Destructive toolsapproval, verification, receipt revision
$3.00
LLM requestsgovernance, not inference
$0.10

These are the public rates. Design partners on Team are billed on a separate grandfathered schedule, not a volume discount from this list.

One logical call is metered once, whatever it took to make it. Internal retries, provider fallbacks, approval resumes, and receipt revisions never add a charge, and neither does a call policy refused before dispatch — so nothing here pays us to route more, retry more, or pick the costlier provider. A vendor error after dispatch is a governed call and is billed.

Questions engineers ask first.

Short answers, in the product's own terms. The long version is in the architecture and the ADRs.

  • No. Inbound credential is never the outbound credential. The agent holds an Obol virtual key; your vendor credentials are envelope-encrypted (a DEK per connection, the KEK in your KMS) and decrypted only inside the gateway process on the outbound hop. They never appear in logs, worker configs, or the dashboard.

Pay the toll once. Pass every gate.

We're onboarding design partners in small batches. Tell us what your agents touch — models, MCP servers, the APIs they write to — and we'll take it from there.

Design-partner access. No spam, no vendor keys asked for — ever.

Prefer to read first? How it works · Self-host · FAQ