ClocknextHelp
DocumentationAPI ReferenceMCP Tools
Dashboard

Get started

OverviewQuickstart

Core concepts

The response envelopeErrorsPaginationAsync, flushing & reliability

API reference

Record Signals POSTRecord usage logs POST
List customers GETCreate a customer POSTGet a customer GETUpdate a customer PATCHDelete a customer DELETEGet a customer's balances GETGet a customer's current plan GETList a customer's wallet transactions GETAdd a wallet transaction POSTAdjust a customer's credit balance POSTAdjust a customer's outcome balance POSTAdjust a customer's unit balance POST
List a customer's members GETAdd a member POSTUpdate a member PATCHRemove a member DELETE
List purchases GETCreate a purchase POSTGet a purchase GETUpdate a purchase's auto-payment setting PATCHCancel a purchase POST
List invoices GETGet an invoice GETMint a hosted pay link GET
List payments GETGet a payment GET
Mint a customer-portal access token POST

API Reference

Errors

Every status code the API returns, what it means, and how to handle it.

When a request fails, the HTTP status and statusCode agree, statusDetail.status is ERROR, result is an empty object, and the reason is in statusDetail.message:

{
  "statusCode": 400,
  "statusDetail": { "status": "ERROR", "message": "email must be a valid email" },
  "result": {}
}

Status codes

StatusMeaningWhat to do
400 Bad RequestThe request was malformed — invalid JSON, a bad query parameter, a field that failed validation (wrong type, out-of-range, or missing required), or a rejected business rule (e.g. an inactive plan, a past billing date).Read statusDetail.message for the offending field or rule and fix the request.
401 UnauthorizedThe API key is missing, unknown, or expired.Send a valid Authorization: Bearer cnk_… header. See Settings → API Keys.
403 ForbiddenAn explicit organizationId on /portal/token doesn't match the key's organisation.Send the correct organizationId, or omit it.
404 Not FoundThe record doesn't exist — or belongs to another organisation (the two are masked as one).Check the id. A foreign id looks identical to a missing one by design.
409 ConflictThe request clashes with current state — e.g. a duplicate employee id, a usage signal against an archived customer, or a pay link for an invoice that isn't OPEN.Reconcile with the current state, then retry.
422 Unprocessable EntityThe body is well-formed but can't be applied to the current state — raised by the usage/units flows when the customer has no active plan, the credit/outcome/unit agent key isn't in your workspace, the model isn't enabled, or the customer is out of allowance. (Field-level validation is 400, not 422.)Read statusDetail.message; fix the referenced entity or top up allowance. Not retryable unchanged.
500 Internal Server ErrorSomething went wrong on our side.Retry with backoff; if it persists, contact support.

Why foreign ids are 404, not 403

A key is scoped to one organisation, and referencing an id from another organisation returns 404 everywhere — the same as an id that doesn't exist. That's deliberate: it stops a key probing for valid foreign ids by telling "forbidden" apart from "not found". The only endpoint that distinguishes them is /portal/token.

Handling errors

Treat any non-2xx status as a failure and read statusDetail.message for the reason. 4xx codes mean your request needs to change and won't succeed on a blind retry; 500 is transient and safe to retry.

const res = await fetch(url, init);
const body = await res.json();

if (body.statusDetail.status !== "SUCCESS") {
  // body.statusCode        → 400 | 401 | 403 | 404 | 409 | 422 | 500
  // body.statusDetail.message → human-readable reason
  throw new Error(`${body.statusCode}: ${body.statusDetail.message}`);
}

Typed errors in the SDK

The official @clocknext/sdk client throws a typed error for each status, all extending ClocknextError (with .status and .retryable), so you can branch on the failure mode instead of parsing the message:

import { AuthError, NotFoundError, AllowanceError } from "@clocknext/sdk";

try {
  await cnk.customers.get("cus_missing");
} catch (err) {
  if (err instanceof NotFoundError) {/* 404 */}
  else if (err instanceof AuthError) {/* 401 */}
  else if (err instanceof AllowanceError) {/* 422 — out of allowance */}
}

Retries

Only retry codes that can succeed unchanged:

  • Retry 500 (and network/timeout failures) with exponential backoff.
  • Do not retry 4xx — the request itself is the problem; fix it first.

Be careful retrying non-idempotent writes (a POST that creates a record). If a POST /customers times out you can't tell whether it landed, so a blind retry risks a duplicate. Prefer to look the record up before retrying, or use the @clocknext/sdk client, which only auto-retries replay-safe calls.

The response envelope

Every endpoint — success or error — returns the same predictable envelope. Here's the contract.

Pagination

Page through list endpoints with limit and cursor.

On this page

Status codesHandling errorsRetries