Valta Docs
Proxy API
Two kinds of credential are involved:
| Credential | Looks like | Used for |
|---|---|---|
| Valta API key | sk_valta_… | Managing an agent's provider key and virtual keys (/api/v1/agents/:id/...). Live mode only. |
| Valta virtual key | vk_live_… | Calling the proxy (/v1/chat/completions). Held by the agent. Only works against Valta. |
Management routes accept the Valta API key as x-api-key: <key> or Authorization: Bearer <key>. Base URL for management: https://valta.co/api/v1. Base URL for the proxy: https://valta.co/v1.
Provider keys, virtual keys, and Cap limits all belong to one agent (:id). The agent id is any id you use for that agent in Valta — the same one its dashboard page uses (/dashboard/agents/<id>).
Store an OpenAI key
POST /api/v1/agents/:id/provider-keys — requires write.
curl -X POST https://valta.co/api/v1/agents/support-bot/provider-keys \
-H "x-api-key: $VALTA_API_KEY" -H "Content-Type: application/json" \
-d '{"provider":"openai","apiKey":"sk-proj-..."}'
201
{
"success": true,
"providerKey": { "keyId": "pk_…", "provider": "openai", "last4": "a1B2", "createdAt": "2026-09-24T19:55:04.618Z" }
}
- Encrypted at rest. Only
last4is ever returned. - Replaces the agent's previous OpenAI key in the same transaction. The next proxied call uses the new key.
- Valta doesn't call OpenAI to validate it — an unfunded key saves fine.
400 INVALID_PROVIDER_KEYif it doesn't look likesk-…or is a Valta virtual key.400 LIVE_MODE_REQUIREDwith a test-mode API key.
GET /api/v1/agents/:id/provider-keys — requires read. Returns { success, providerKeys: [{ keyId, provider, last4, createdAt }] }.
Mint a virtual key
POST /api/v1/agents/:id/virtual-keys — requires write. Body: { "name"?: string }.
201
{
"success": true,
"virtualKey": {
"keyId": "vk_…",
"agentId": "support-bot",
"name": "production",
"prefix": "vk_live_3f9a1c…",
"status": "active",
"createdAt": "…",
"lastUsedAt": null
},
"secret": "vk_live_<64 hex>",
"note": "Store this secret now — it will not be shown again."
}
secretis returned once. Valta stores only its SHA-256.- The plan limit counts active keys across the account, not per agent. Over it:
402
{
"error": "Your Free plan allows 1 active virtual key, and this account already has 1 (on agent wallet-guardian). Use that key, revoke it on its agent's page, or upgrade to Builder (10 active virtual keys) at https://valta.co/dashboard/subscriptions.",
"code": "PLAN_VIRTUAL_KEY_LIMIT",
"activeKeyAgents": ["wallet-guardian"],
"plan": "FREE",
"limit": "1 active virtual key",
"upgradeTo": "Builder",
"upgradeUrl": "https://valta.co/dashboard/subscriptions"
}
GET /api/v1/agents/:id/virtual-keys — requires read. Returns { success, virtualKeys: [...] } (metadata only, never secrets, newest first).
Revoke a virtual key
DELETE /api/v1/agents/:id/virtual-keys/:keyId — requires write. Returns { success: true }, or 404 if it doesn't exist or is already revoked. The next proxy call with that key gets 401.
Chat Completions through Valta
POST https://valta.co/v1/chat/completions
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer vk_live_… | Yes | Or x-api-key: vk_live_…. |
Content-Type: application/json | Yes | |
X-Valta-Run-Id | Recommended | 1–200 chars of A-Z a-z 0-9 . _ : -. Calls with the same run id share the per-run limit. Without it, each call is its own run. |
Body: the OpenAI Chat Completions request, unchanged. model and a non-empty messages are required. stream: true returns 400 stream_not_supported.
What Valta does, in order:
- Resolves the virtual key (
401if unknown or revoked). - Checks the plan's active-key count (
402 plan_key_limit) and the per-key rate limit (429 rate_limited, withRetry-After: 60). Every request on the key counts toward the rate limit, including denied ones. - Checks an OpenAI key is stored for the agent (
403 no_provider_key). - Estimates the cost: prompt size at ~4 characters per token, plus
max_completion_tokens/max_tokens(default 1,024 if neither is set), timesn, at the model's OpenAI list price. Models Valta has no price for are estimated at the most expensive tier. - Runs Cap: freeze, Cap enabled, per-run / daily / monthly limits, then the plan's Cap agent count and tracked-USD budget — the same checks and ledger as
cap.allow(). - Approved: one request to
https://api.openai.com/v1/chat/completionswith the stored key, 100-second timeout, no retries by Valta. - Settles the ledger to the real cost from OpenAI's
usage(up or down). OpenAI 4xx → the estimate is released. Timeout or 5xx → the estimate stays charged, since OpenAI may have run it.
Approved response: OpenAI's status and body, unchanged, plus:
| Header | Value |
|---|---|
X-Valta-Decision | approved |
X-Valta-Allow-Id | The Cap decision id (allow_…). |
X-Valta-Run-Id | The run id used (yours, or a generated req_…). |
X-Valta-Estimated-Usd | Pre-call estimate. |
X-Valta-Actual-Usd | Real cost from OpenAI's usage (when known). |
If OpenAI returns an error after approval, it's passed through with OpenAI's status; any echoed key fragments (sk-…) are removed first.
Denied response: 402, 403, 429, or 503 with headers X-Valta-Decision: denied and X-Valta-Reason: <reason>, and:
{
"approved": false,
"reason": "plan_limit",
"id": "allow_…",
"message": "Your Valta Free plan's tracked spend for this month ($50) is used up. No request was sent to OpenAI. Upgrade to Builder ($500 tracked per month) at https://valta.co/dashboard/subscriptions. Tracked spend resets on the 1st of the month (UTC).",
"plan": "FREE",
"limit": "$50 tracked per month",
"upgradeTo": "Builder",
"upgradeUrl": "https://valta.co/dashboard/subscriptions",
"error": { "message": "…", "type": "valta_denied", "code": "plan_limit" }
}
plan, limit, upgradeTo, and upgradeUrl appear only on plan denies. id is the Cap decision id, or a deny_… id for refusals before Cap runs; either one can be looked up by Valta support.
reason | HTTP | Cause |
|---|---|---|
per_run_limit | 402 | This call would push the run (X-Valta-Run-Id) over its per-run cap. |
daily_limit / monthly_limit | 402 | Over the agent's daily / monthly cap (UTC). |
plan_limit | 402 | The account's tracked USD for the month would pass the plan's limit. |
plan_agent_limit | 402 | A new agent, and the plan's Cap agents for the month are all in use. |
plan_key_limit | 402 | This key is beyond the plan's active-key count (oldest keys win). |
frozen | 403 | The agent is frozen. |
cap_disabled | 403 | Cap isn't enabled on the agent. |
no_provider_key | 403 | No OpenAI key stored for the agent. |
rate_limited | 429 | Over the key's requests per minute. Retry-After: 60. |
valta_unavailable | 503 | Valta couldn't decide. See below. |
What to do about each: Error codes.
Fail closed
Valta forwards a request only after an explicit approve. Anything else — a deny, a database error, a plan lookup that fails, a stored key that can't be decrypted — returns 503 valta_unavailable (or the deny above) and nothing is sent to OpenAI. There is no bypass flag and no fail-open mode.
Run spend lives in Valta, not in your process. If the agent crashes and restarts with the same X-Valta-Run-Id, the run is still charged for everything it already spent, so the next call over the cap is still 402 per_run_limit.
The OpenAI SDKs don't auto-retry 402 or 403. They do retry 429 and 5xx by default; set max_retries=0 if you don't want that.
Health
GET https://valta.co/v1/health — no auth.
200
{
"status": "ok",
"failClosed": true,
"decisions": "available",
"endpoints": ["POST /v1/chat/completions"],
"streaming": false,
"upstream": "api.openai.com",
"latencyMs": 72
}
503 with "status": "degraded" means Valta can't reach its database — every proxy call is being denied (503 valta_unavailable), none forwarded.
Freeze
POST /api/v1/agents/:id/freeze and POST /api/v1/agents/:id/unfreeze work for proxy agents too, including ids that exist only as a Cap policy or virtual key. A frozen agent's next proxy call gets 403 frozen. See Kill switch.
Plan limits
| Free | Builder ($29/mo) | Startup ($99/mo) | Enterprise | |
|---|---|---|---|---|
| Active virtual keys (per account) | 1 | 10 | 50 | Unlimited |
| Proxy requests / minute / key | 10 | 120 | 600 | 3,000 |
| Cap agents / month | 1 | 10 | 50 | Unlimited |
| Tracked USD / month (Cap + proxy, all agents) | $50 | $500 | $5,000 | Unlimited |
What's paid, and to whom:
- Tokens are billed by OpenAI to your own OpenAI account, on the key you stored. Valta doesn't resell or mark them up, and never charges for tokens.
- The Valta plan is what you pay Valta. Free is $0 and gets the whole proxy, with the limits above — 1 virtual key and $50 of tracked spend a month, so there's no unlimited free proxy. Builder ($29/mo) and Startup ($99/mo) raise the key count, request rate, and tracked spend. Going over the plan's tracked spend is a
402 plan_limitdeny, never a silent pass. - Tracked USD is Valta's estimate settled to OpenAI's reported cost, summed across all your agents, for Cap
allow()and the proxy combined. It resets on the 1st (UTC).