Menu

SDK conformance

The checklist every server library must satisfy

What a server library for this API must do to be called one. App and web SDKs, which run in browsers and apps with a client key and a user token, follow their own interface instead. The Node.js library (@kirora/node) is the reference implementation: when this document and that code disagree, the code is right and this document is a bug.

Pick the reference first and have every other language follow its shape. The alternative — answering “what should this method be called” once per language — means somebody arbitrates naming forever.

What volunteers are given

The contract The API reference: every operation with an operationId, its fields and its responses
The error catalogue Errors
The behaviour behind every endpoint The guides
The shape The Node.js reference library
This checklist Below

Naming

  • One namespace per tag: client.contacts, client.campaigns.
  • One method per operationId, named by dropping the resource: listContacts → contacts.list(), sendCampaign → campaigns.send(). Adapt casing to the language’s conventions and nothing else.
  • Put the operationId in the method’s doc comment so the mapping back to the reference survives.
  • operationId is the stable identity across every SDK, the reference pages and the changelog. It does not change; neither should your method name.

The checklist

Configuration

  • API key required at construction; fail loudly if absent.
  • Base URL defaults to Kirora’s API host and can be overridden, so tests can point at a mock server. It is the host alone: the library adds /v1 to each path, because its methods and types are written for that version.
  • Injectable HTTP client or transport, so callers can add proxies and tests can stub.
  • Configurable timeout, with a sane default.
  • Never log the API key, including in debug output and error messages.

Requests

  • Authorization: Bearer <key> on every request.
  • Never send an organization id. The key determines the organization.
  • Path parameters URL-encoded.
  • Unwrap the data envelope so callers do not write .data everywhere. Keep meta reachable on list calls.
  • 204 returns nothing rather than attempting to parse a body.

Pagination

  • Expose a single-page call and an auto-paginating iterator; make the iterator the natural choice.
  • Stop on nextCursor === null, not on an empty page.
  • Stream items; never buffer the whole collection before yielding.
  • Treat the cursor as opaque — never construct, decode or inspect one.
  • No page or offset parameter anywhere in the surface.

Errors

  • One error type carrying code, message, status, details and a request id.
  • code covers every code in the catalogue.
  • An unrecognised code must not crash. New codes are additive; fall back to a generic error carrying the raw string.
  • Keep raw details reachable — VALIDATION_ERROR field errors live there.
  • Distinguish transport failures from API errors; a connection reset has no code.
  • Never branch on message.

Retries

  • Retry 429 always: the server refused it before doing anything.
  • Retry 5xx and transport failures only for a request safe to repeat: GET, PUT, DELETE, any request carrying an Idempotency-Key, and the writes the idempotency guide lists as safe. A create without a key may have happened before the failure, and repeating it makes a second one.
  • Retry nothing else.
  • Honour Retry-After when present: never retry sooner (jitter adds to it, never takes from it), and surface the error rather than wait past the delay ceiling. Exponential backoff when it is absent.
  • Jitter every wait. Workers limited in the same second otherwise wake in the same second.
  • Bounded attempts, configurable, disable-able.
  • Surface the final failure; never fail silently or retry forever.

Idempotency

  • Accept a caller-supplied Idempotency-Key on transactional send, push send, event tracking and campaign triggers. The API requires one on all four.
  • Never generate a key inside the retry loop. The key must be fixed before the first attempt or it protects nothing.
  • Document which operations are safe to retry — see Idempotency.

Partner tenants

  • Let a partner key act in a tenant: a client-wide workspace and a per-call override, sent as the Kirora-Workspace header (the tenant’s externalId).

Webhooks

  • Ship verifyWebhookSignature(secret, rawBody, signature, timestamp).
  • HMAC-SHA256 over ${timestamp}.${raw body}, compared in constant time, returning false rather than throwing on a length mismatch. Reject a timestamp outside the tolerance window, and accept any of the comma-separated signatures sent while a secret rotation overlaps.
  • Document the raw-body requirement prominently — framework body parsing is the usual reason verification fails in the field.
  • Expose a helper to read the X-Webhook-* headers, including the event id used for deduplication.
  • Keep the event type open; a * subscription receives types added later.

User tokens

  • Ship createUserToken(tokenSecret, userId, { expiresInSeconds }): the token a customer’s server hands Kirora’s app and web SDKs at sign-in. HS256 over { sub, iat, exp }, defaulting to one hour. It needs no API key and makes no call.
  • Check nothing the server checks: Kirora refuses a token valid for more than 24 hours, and that rule stays there.

Forward compatibility

  • Deserialize permissively; unknown response fields are ignored, never fatal.
  • Treat every enum as open.
  • Take API changes deliberately: read the changelog, then update.

Proving it

The reference library’s conformance tests exercise it against these claims: the error catalogue matches the API’s, pagination stops on the right signal, 429 is retried and 400 is not, retries are bounded, and the webhook verifier agrees with the server’s own signer byte for byte, and a signed user token is accepted by Kirora. Port those tests along with the shape — an SDK that has not demonstrated the retry and webhook behaviour has not demonstrated the two things most likely to be wrong.

Scope of the reference

The reference implements the full cross-cutting machinery, and demonstrates the resource pattern on the operations that exercise each distinct behaviour: cursor pagination, identity upsert, state-based idempotency, and key-based idempotency. The remaining tags are mechanically identical — one line per operation against client.request — and deliberately left to the SDK authors rather than pre-empted here.

Request and response types are named models. A new namespace types its methods with them, not open objects.