Skip to main content

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