API Conventions
Every Vagary Voice capability's HTTP surface follows one set of house conventions — implemented once
as the apimw middleware library in the Python SDK, and mirrored on the wire by
Node and other-language capabilities. These raise the platform from "each capability invents its own
retries / paging / versioning" to one baseline — the analog of what Stripe and Twilio ship as a
platform standard.
1. Idempotency keys (Idempotency-Key)
A client that retries a mutating request after a timeout must not double-execute it (double charge, double webhook, double row).
- A client MAY send
Idempotency-Key: <opaque unique string>on any mutating request (POST / PUT / PATCH / DELETE). Generate one key per logical operation (a UUIDv4 is fine); reuse it verbatim on every retry of that same operation. - The server caches the first response keyed by (org, key) and, on any replay of the same key,
returns the byte-identical stored response plus
Idempotent-Replayed: true, without re-executing. - Same key + a different operation (method/path/query) →
422(idempotency_key_reuse). - Same key while the first request is still in flight →
409(idempotency_conflict). - Records expire after a TTL (default 24h); after that a key may be reused.
- GET / HEAD are already idempotent and pass through untouched. A missing key passes through (opt-in),
unless a capability sets
require_key=True.
from apimw import IdempotencyMiddleware
app.add_middleware(IdempotencyMiddleware) # in-memory store, 24h TTL, org from X-Org-Id
2. Cursor pagination (opaque keyset cursors)
OFFSET/LIMIT degrades on deep pages and skips or duplicates rows under concurrent inserts. Keyset (seek) pagination is O(1) per page and stable.
- List endpoints return an opaque
next_cursor(base64url) alongside the page of results. - The client passes it back as
?cursor=<opaque>to fetch the next page — it never constructs the cursor itself. - The cursor encodes the keyset position, not an offset, so a page stays stable even as rows are inserted concurrently.
3. API versioning
- A client SHOULD pin the API version it was built against.
- The server negotiates the requested version (via
APIVersionMiddleware/negotiate_version) and serves the matching behavior, so a new platform release never silently breaks an existing client.
Tenancy
Every request is scoped to an organization. An org's data — including its idempotency keys and list cursors — never collides with another org's. Tenant scope is mandatory, not optional.
For the reference implementation and the full protocol detail, see docs/api-conventions.md in the
vagary-core repository and the apimw/ package in the Python SDK.