Base URL
All requests go to a single, versioned base URL. Every path in this reference is relative to it.Versioning
The API carries two version coordinates, designed in from day one:- A path version (
/v1) that changes only for a wholesale redesign. There is no reflexive/v2— breaking changes are managed by the date version below, not the path. - A date version sent per request in the
Vetta-Versionheader (formatYYYY-MM-DD), which pins the exact wire contract your integration was built against.
Omitting the header pins your organization to the version in effect when its first key was created. Additive changes (new fields, new endpoints) are made in place within a date version; treat unknown response fields as forward-compatible and ignore them.
Deprecation policy
Backwards-incompatible changes only ever ship behind a new date version — never silently. When a field or behavior is deprecated we publish it in the changelog, keep the old behavior available for a minimum 6-month sunset window, PinVetta-Version in production so a new default version can never break you.
There is no deprecation response header today. Deprecations are announced in the changelog only; do not write a client that keys off a header the API does not send.
Authentication
Authenticate with an organization-scoped API key in theAuthorization header using the Bearer scheme.
Request & response conventions
- Request and response bodies are JSON. Send
Content-Type: application/jsonon any request with a body. - Timestamps are RFC 3339 strings in UTC (e.g.
2026-08-20T17:00:00Z), suffixed_at. Fields that need client-side arithmetic are also exposed as epoch millis, suffixed_at_ms. - Object ids are prefixed by type — one prefix per type (e.g.
agt_agents,ses_sessions,cmp_computers,key_keys,led_ledger entries). The random suffix is Crockford base32. - Money is always an integer count of micro-USD in fields suffixed
_micro_usd(1 USD = 1,000,000 micro-USD). Never floating-point dollars on the wire — clients convert for display only. See Credits. - The single latency/price knob is the completion window:
immediate | priority | loose. These exact strings travel on the wire everywhere; the default isimmediate.
Request ids
Every response — success or error — carries anx-request-id header. Log it; quoting it in a support request lets us trace the exact call. On errors the same value is mirrored inside the error envelope as error.request_id.
Idempotency
Every mutatingPOST accepts an Idempotency-Key header (also honored on the RPC-style verbs like top-ups, session messages, and computer creation). Reusing the same key replays the original response instead of performing the operation twice, making retries safe.
- Same key, same body → the original response is replayed. A replay carries an
Idempotency-Replayed: trueresponse header so you can tell a fresh result from a cached one. - Same key, different body →
409(validation_failed). A key binds to the exact request that first used it; reusing it with a changed payload is rejected rather than silently ignored. - Concurrent requests with the same key → the first wins and the rest return
409while it is in flight; retry after it settles to receive the replayed result.
Keys are scoped to your organization and retained for 24 hours. Use a fresh UUID per logical operation. Purely idempotent verbs (
GET) ignore the header.Pagination
List endpoints are cursor-paginated withlimit and after, returning { data, has_more, next_cursor }. The session events log is a separate, oldest-first seq-cursor contract. See Pagination.
Machine-readable description
GET /v1/openapi.json returns an OpenAPI 3.1 description of this API, generated from the server’s own route table — so it lists exactly the routes the deploy you are talking to serves, no more and no less. It needs no API key.
Each operation carries the scope a caller must hold as x-vetta-scope, and the shared error envelope as its default response. Request and response bodies are not described yet; use the resource pages below for those.
Errors
Errors use the single typed JSON envelope with standard HTTP status codes, and always include arequest_id. See Errors.
Rate limits & concurrency
Two independent limits apply per organization:- Request rate: 6000 requests/minute per organization across the control plane (list, read, and mutate calls), and 3000 requests/minute per API key, so one key cannot spend the whole organization’s budget. Fixed 60-second window.
- Session concurrency: 50 sessions in the
runningstate at once. Starting a session beyond the cap queues it inqueueduntil a slot frees.
429 with a rate_limited error and a Retry-After header (seconds to wait). Back off and retry after that delay. Rate-limit state is also returned in response headers:
RateLimit-* describes whichever of the two limits is closest to binding. The API also answers 429 with rate_limited and a Retry-After when the platform itself is momentarily at capacity — it never answers a capacity problem with a 500, so a 429 is always safe to back off on and retry.
Concurrency limits are raised on request. The request-rate and concurrency numbers above are the default org tier; your organization’s effective limits are reflected in the
RateLimit-* headers.Resources
Every page above is one row of the same route table the TypeScript SDK and the CLI are generated against, so an operation you find here has a method and a command with the same name.
TypeScript SDK
npm i @usenaive-sdk/vetta — one typed method per route, with the full method index.CLI
npm i -g @usenaive-sdk/vetta-cli — the same surface from a terminal or CI.