# 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](/docs/api/errors) |
| The behaviour behind every endpoint | The [guides](/docs/api) |
| 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](/docs/api/errors).
- [ ] **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](/docs/api/idempotency) 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](/docs/api/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.