What this surface is
/v1/teams addresses work by a pair: a team and a tenant.
GET /v1/teams is scoped to that pair. tenantUserId is a
tenant_users.id — the same id POST /v1/users returns and the same id the
legacy /v1/users/{user_id}/… routes take.
{team} is validated as a routing segment and echoed back in the response. It is
not used as a storage key in this build, because a declared team’s name has no
storage this API may read. Where that changes an answer, the response says so.
One credential, one host. These routes use the same
Authorization: Bearer nv_sk_... key and the same base URL as every other
endpoint. There is no separate runtime credential and no second hostname.Authentication and gating
-
Auth: session cookie or API key (
requireSessionOrAuth). -
Idempotency: the mount carries the idempotency middleware. Read
GET /v1/limitsbefore relying on it — the store is an in-process Map with no cross-replica sharing and no body fingerprint, and the API prints that fact rather than advertising a guarantee it does not have. -
AccountKit primitive gate: the
tasksprimitive, applied inside the router. There is nogatePrimitive(...)on the mount, and there is no registeredteamsprimitive to gate on — registering one would oblige three mirrors, and gating on an unregistered slug produces an inert gate, which reads like a real one and enforces nothing. So the check is made where the tenant is resolved instead: every operation addressed to a(team, tenant)pair callsassertPrimitiveEnabled(kit, "tasks")— the same predicategatePrimitiveapplies first, on the primitive whose data this surface serves. A kit that disablestasksis refused here exactly as it is at/v1/users/{id}/tasks:The one exception is403GET /v1/teams, which is company-scoped, resolves no tenant and therefore reads no kit. It answers501regardless.
Runtime provider is reported on every answer
A tenant runs on one of two runtimes, and mixed mode is the normal state. Every response that could be aggregated carriesprovider:
An aggregate that omits
provider lets a reader average a durable tenant and a
hermes tenant into one number. None of these responses omit it.
The 32 operations
Legend, and it has three values now because a binary one could not be true:- live — serves real rows on BOTH runtimes (from different stores where the store differs: the runtime’s own board for a durable tenant, the legacy mirror for a hermes one).
- durable — served for a durable tenant,
501for a hermes one. - 501 — refused on both, with
error.details.missingnaming the absent dependency for that tenant’s runtime.
Company-scoped
The team at a tenant
The board
Runs
Approvals, cost, sessions, effects
How a refusal reads
501
501 is the code these use on purpose. 404 would say the address is wrong,
403 would say you are not allowed, and 200 with an empty list would say you
have none — a claim about your tenant rather than about the build. Only 501
says “the request is well-formed and the server does not implement it”.
Fields that are null on purpose
Several responses carry a null beside a sibling *_unavailable_because string.
That pairing is the surface’s core convention: no field on this API is filled
with a plausible-looking placeholder.
A
0, an [] or a synthesised digest in any of these places would be
indistinguishable downstream from a real one. That is the failure the convention
exists to prevent.
Related
- Governance API — policy, grants, limits, spend
- Brain API — the company knowledge surface
- Orchestration API — the frozen legacy runtime, still answering