Skip to main content
This page is the map; the per-namespace pages are the territory. The method index below is generated from the client source and checked against the constructed client, so it cannot name a method the package does not export — and every namespace links to a page documenting each method’s signature, route, and semantics. TypeScript only; there is no Python package.
The build bundles the private @vetta/core into dist/, so the installed package resolves against nothing but zod. Inside this repository it resolves as a workspace dependency, which is how apps/cli and the dashboard consume it. Everything documented below is the real, current surface of packages/sdk. @usenaive-sdk/vetta is the one place that knows how to talk to the control plane over HTTP. apps/cli and the dashboard both go through it, which is the whole reason it exists: the bearer key, the Vetta-Version pin, the Idempotency-Key on every mutating verb, and the error envelope are decided once, in src/http.ts, instead of twice and slightly differently.

What it is not

It is deliberately thinner than a product SDK:
  • No retry policy. A caller that wants retries owns that decision, because the CLI and the dashboard want different ones.
  • No pagination magic. list returns one page and the cursor. No auto-paging iterator.
  • No client-side taxonomy. ApiError.type and ApiError.status are read off the response. The server owns the code → (type, status) table; a second copy here would be a second taxonomy that could drift.
  • No wire shapes of its own. Responses parse with the zod schemas from @vetta/core. Where a body is not yet modelled there — shell exec, billing, the per-op fs replies — the client says so explicitly with a local schema or RawSchema, rather than inventing a type.

Constructing a client

Everything the client cannot decide for itself is injected. That is not ceremony: it is what lets the test suite assert exact traffic against a recording fetch, and what lets the CLI own the real one.
string
required
Control-plane origin. Trailing slashes are trimmed.
string
required
Sent as Authorization: Bearer …. One client per credential — vetta login has none until the sign-in reply comes back.
typeof fetch
required
Injected so tests can record and the CLI can wrap it (it names the host in connection errors).
() => string
required
Called once per mutating verb. Injected so a test gets a deterministic key.

Coverage

The method table is the route table. The API serves 132 routes across 95 paths; the client reaches 89 of those 95 paths from exactly one method each, across twenty namespaces: Cross-cutting behavior has its own pages too: Errors, Pagination, and Streaming. Two methods are parameterized over a family rather than split per route, because splitting them would produce five near-identical methods with no added type safety:
  • computers.fs(id, op, body) reaches all five /v1/computers/{id}/fs/* routes. The reply differs per op ({content}, {entries}, {created}…), so it comes back as RawSchema.
  • stream(sessionId, options) is the one consumer of GET /v1/sessions/{id}/stream.
Six routes are deliberately absent, and they are the whole difference between 95 paths and 89: That accounting is generated, not asserted by hand — scripts/interface-parity rebuilds it from the route table, the client source and this page.
Do not write those five paths on this page with an HTTP verb in front of them. apps/api/test/drift.e2e.ts scans every published docs/**.mdx for METHOD /v1/… to learn what the docs promise a customer. Pairing a verb with one of these paths would register a deliberately-unpublished route as documented and quietly retire the allowance that keeps it honest.

Invariants worth knowing before your first call

Money is integer micro-USD, always

Every monetary field on every signature is z.number().int(). The client never exposes a float and never converts to dollars — formatting is a presentation decision, and doing it here once cost a 10⁶ error in an earlier audit. credits.topup takes amountMicroUsd; sessions.setBudget takes capMicroUsd.

The vault has no read method, by contract

vaults exposes create, list, get, delete and credentials.{create,list,delete} — and no reveal. The control plane serves no reveal route and VaultCredentialSchema has no value field, so list could not return one even if a handler tried. Do not add one. Rotation is create-new + delete-old rather than an update, so last_injected_at stays attributable to exactly one secret.

The two skill pushes are not interchangeable

skills.push({ slug, content }) hits POST /v1/skills and is create-or-version by slug: unknown slug creates the skill. skills.pushVersion(ref, { content }) hits POST /v1/skills/{id}/versions and requires the skill to exist — an unknown ref is a 404, not a create. A release pipeline pinned to a skill id wants the second one, so a rename cannot silently mint a second skill. Both accept an optional sha256. It is an integrity claim the server checks against its own digest, never the digest of record. A mismatch is rejected.

Worked examples

Every block below was run against staging on 2026-08-22. Replies are shown as returned, except the two marked as abridged.

Reading the balance

79416537 micro-USD is $79.416537. The client hands you the integer; you format it.

Pushing a skill, then a second version of it

Note id and slug on the reply. They are what makes the chain above work, and they are the reason pushes parse with SkillPushSchema (core’s SkillVersionSchema extended) rather than the bare version schema, which would strip them.

Why pushVersion is not just push

push with an unknown slug would have created a skill. That is the entire difference.

The integrity claim is checked, not trusted

ApiError.param names the offending field, which is what the CLI prints.

Listing versions

Abridged: the digests are full 64-character hexes. Newest first. The cursor is the version number, not an id — versions have none of their own.

The page envelope

Abridged: the two agent objects are full agents. { data, has_more, next_cursor } on every list. There is no auto-paging helper; pass after: next_cursor.

Deleting

Every delete answers the same { id, object, deleted } acknowledgement.

Handling errors

Every non-2xx throws ApiError. There is one shape:
requestId is the single most useful field in a report — it is the server’s own correlation id for that call.

Streaming

stream is a resumable async generator over session events. afterSeq is exclusive, so resuming from the last seq you saw does not replay it:
The dashboard does not call this directly. It re-serves the SSE body same-origin using the send escape hatch, so the control-plane key never reaches a browser.

The same operation in the other two interfaces

Every method below has a counterpart in the CLI and a page in the API reference, named the same way. The three surfaces are generated against one route table, so a gap in any of them is a build failure rather than a discovery.

Method index

Every method on the client, and the route it calls — 179 methods across 26 namespaces. Generated from the client source by scripts/sdk-index.mjs; the drift gate fails if it falls behind.

agents

apps

auditLogs

auth

board

computers

connections

credits

deployments

domains

emails

files

harnesses

identities

keys

media

models

orgs

phones

proxy

sessions

skills

social

stream

vaults

webhooks

Plan

An organization needs an active plan before it can be used. These two methods are open even when it does not have one, so a client can always read the state and start a checkout.
Every other call on an organization without a plan answers 402 with code subscription_required.