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.
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.