Buyer Endpoints
All require authentication (API key or session cookie).
Keys
Everything you can do to a key in the dashboard, you can do here over the API. Create, list, read, rename, revoke, and configure per-key routing preferences, with no browser. This is the same API the dashboard itself calls. Authenticate with any active buyer key (inf_…) or a session cookie; the tenant is resolved from your auth, never sent in the body.
Changing keys needs an unrestricted key or a session. A restricted key (one with a scope, trusted-only provider setting, budget, or expiry, or any team-workspace key) gets 403 restricted_key when it tries to create, rename, configure, or revoke a key, or to change /v1/buyer/preferences or /v1/buyer/providers. It can still list and read keys, and revoke itself.
Create a key (up to 25 active per wallet). Requires an Idempotency-Key header. The inf_… secret is returned in the key field once. Store it immediately; afterwards only its id and key_prefix are recoverable.
List keys, redacted (id, key_prefix, label, created_at, plus last_used_at / revoked_at when set). last_used_at is kept current as the key is used. Add ?workspace_id= to narrow a team org's keys to one workspace.
Read one key's metadata (never the secret).
Rename a key's label.
Revoke a key. Irreversible; returns 204.
POST /v1/buyer/keys
Idempotency-Key: 9f8c1e2b-... # required
{ "label": "Cursor" }
# → {
# "id": "01JQ8G7YV3M4N5P6Q7R8S9T0AB",
# "key": "inf_9f8c1e2b3a4d5e6f7a8b9c0d1e2f3a4b", # shown once
# "key_prefix": "inf_9f8c1e2b",
# "label": "Cursor",
# "created_at": 1713312000000
# }GET /v1/buyer/keys
# → { "items": [ {
# "id": "01JQ8G7YV3M4N5P6Q7R8S9T0AB", "key_prefix": "inf_9f8c1e2b",
# "label": "Cursor", "created_at": 1713312000000, "last_used_at": 1713398400000
# } ] }GET /v1/buyer/keys/{id}PATCH /v1/buyer/keys/{id}
{ "label": "Cursor (prod)" }DELETE /v1/buyer/keys/{id}Per-key routing preferences
Read a key's stored routing preferences and limits, plus secret_masking_available.
Configure a key. The body is { preferences, limits }; limits is reserved for future spend caps, so send {}.
preferences is merged, not replaced. A field you leave out keeps its stored value, null clears that one field, and any other value sets it. This call used to replace preferences wholesale, so { "preferences": {} } cleared everything; it now changes nothing. The same rule applies to PUT /v1/buyer/preferences and to PATCH /v1/keys/{id}, where provider_trust: "default" now clears only the trust setting and leaves mask_secrets as it was.
allow_untrusted(boolean, nullable):truelets this key route to providers outside the trusted-domain allowlist;nullor not set inherits the account default (trusted-only).mask_secrets(boolean, nullable, beta):trueturns secret masking on for every request made with this key.falseandnullboth leave it to the account. A key cannot turn off masking its account has turned on.
The response echoes the stored values and secret_masking_available: true when masking is available on the service, false when masking is paused on the service (a request that asks for it is then sent without masking). A write that turns mask_secrets on also returns secret_masking_effective_at, the ISO-8601 time from which it applies: about 31 seconds after the write, or about 70 seconds when masking has only just been turned on for the service. Send X-SI-Mask-Secrets: on to be covered before then.
Set the account-wide defaults with GET / PUT /v1/buyer/preferences: the same { preferences, limits } body, the same merge and the same two masking fields, with the account's stored values in preferences_account. A key without its own allow_untrusted inherits the account's, and an account mask_secrets: true covers every key.
If another write changes the same preferences at the same moment, the server re-reads and merges again; after three lost races it answers 409 concurrent_update, which is safe to retry.
GET /v1/buyer/keys/{id}/preferences
# → { "preferences": { "allow_untrusted": true }, "limits": {},
# "secret_masking_available": true }PUT /v1/buyer/keys/{id}/preferences
{
"preferences": { "mask_secrets": true },
"limits": {}
}
# → { "preferences": { "allow_untrusted": true, "mask_secrets": true },
# "limits": {}, "secret_masking_available": true,
# "secret_masking_effective_at": "2026-10-13T12:00:31.000Z" }- Account preferenceYour baseline
- Key preferencenull = inherit
- Resolved valueWhat the request uses
mask_secrets is the one exception to that override. A key can turn masking on for itself, but a key's false does not override an account's true: a request is masked when its key or its account says so (or it sends X-SI-Mask-Secrets: on).
Profile
Balance, allowance, usage stats, per-model breakdown.
Spend, savings and pace for a range in one call. range = 7d, 30d (default), or 90d, trailing and including today (UTC). Returns totals and previous (the equal range before it) with the same measures as /v1/analytics/spend/summary, plus kpis, daily (one entry per day with traffic), by_model (top models; 7 and 30 days only), distinct_models, avg_daily_spend_micro (your 7-day spending pace, integer micro-USD per day), cache_routing_mode, and computed_at. workspace_id picks a workspace of your org, or all for the org rollup (needs audit access on a team org); without it you get your active workspace.
coverage: false means the figures for that range aren't available yet. Every figure is then null or empty, never a zero you could mistake for no spend.
Savings vs direct provider pricing (all-time). Add ?period=daily or ?period=weekly for savings bucketed by day or week.
cache_saved_usdc is what prompt caching saved you (integer micro-USD), counted from cache_saved_since (epoch ms), so show it as "since" that date rather than as a lifetime figure. It is signed per request and floored at zero on the total. Both are null until a request has been measured.
Legacy on-chain allowance status. Not part of funding an account - use deposited USDC or fiat credits.
GET /v1/buyer/me
GET /v1/buyer/overview?range=7d GET /v1/buyer/overview?range=30d&workspace_id=all
GET /v1/buyer/savings
GET /v1/buyer/savings?period=daily GET /v1/buyer/savings?period=weekly
GET /v1/buyer/approve-status
Usage Export
CSV export of all usage records.
Columns: request_id, created_at, model, input_tokens, output_tokens, buyer_cost_usd, direct_cost_usd, settlement_status, tx_hash. Up to 50,000 rows.
GET /v1/buyer/usage/export?format=csv&from=2026-01-01&to=2026-04-13
Providers (BYOK)
Register your own provider keys as buyer-owned providers. A provider scoped to a specific model is tried first for that model (priority - the marketplace is overflow only). A provider with model: null is a catch-all fallback used for any model when all marketplace sellers fail.
Register a priority provider for a specific model, or a catch-all fallback (used when all marketplace sellers fail).
List providers. Each carries last_used_at (when the key last served a request, epoch ms) and uses_this_week (requests it served in the last 7 UTC days, today included). A provider added since these counters shipped reads uses_this_week: 0 until its first use; on an older one both are null until they can be stated in full, never a partial count.
Read one provider (health, active, masked key hint, last_used_at, uses_this_week).
Update a provider: rotate its api_key, change base_url or label, or enable/disable it with active.
Remove a provider.
Routing order: model-scoped priority provider → marketplace sellers → catch-all (model: null) provider → universal fallback.
POST /v1/buyer/providers
{ "model": "claude-opus-4.6", "api_key": "sk-...", "base_url": "https://api.venice.ai/api/v1" }POST /v1/buyer/providers
{ "model": null, "api_key": "sk-...", "base_url": "https://api.venice.ai/api/v1" }GET /v1/buyer/providers
PATCH /v1/buyer/providers/{id}
{ "active": false }DELETE /v1/buyer/providers/{id}