API Reference
Your own customer ids
Give a customer an externalId — the id your product already uses — and name it by that instead of ours.
Every customer has our id (customerId, e.g. cus_abc123). If your own
product already has an id for that customer — a user id, an account number — you
can store it on the customer as its externalId. After that, signals and
new purchases can name the customer by externalId instead of customerId,
and you can fetch or update the customer by it.
The rules
- 1–32 characters: letters, numbers and
-only (acme-42,user-9f21). Anything else is a400:externalId must be 1–32 characters: letters, numbers and "-" only. - Matched exactly —
Acme-42andacme-42are different ids. - Unique among your active customers. Using one another active customer
already holds is a
409:Another customer already uses external id "acme-42".An archived customer's id can be reused. - Optional. A customer without one returns
externalId: null.
Setting it
Send externalId when you create
or update a customer. On update,
null clears it. Every customer object returns it.
curl -X POST https://payments.clocknext.com/api/v1/customers \
-H "Authorization: Bearer $CLOCKNEXT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Acme Inc", "email": "ops@acme.com", "externalId": "acme-42" }'import { ClockNext } from "@clocknext/sdk";
const cnk = new ClockNext({ apiKey: process.env.CLOCKNEXT_API_KEY! });
const customer = await cnk.customers.create({
name: "Acme Inc",
email: "ops@acme.com",
externalId: "acme-42",
});
// Later: change it, or clear it with null.
await cnk.customers.update(customer.id, { externalId: null });Using it instead of customerId
These request bodies take an externalId field in place of customerId:
all signals (credit, outcome,
outcome complete, unit, wallet, composite complete) and
create a purchase.
Everything else still takes our customerId only — including
mint a portal token, the
customer usage log, and the
customerId filter on list invoices,
list payments and
list purchases. To use those
with your own id, look the customer up first with
GET /customers/ext/{externalId}
and use its id.
If you send both, customerId wins
When a request carries both customerId and externalId, customerId is
used and externalId is ignored — they are not checked against each other.
Send neither and you get a 400: customerId: Provide customerId or externalId.
An externalId that matches no active customer is a 404:
No customer with external id "acme-42".
# A signal, naming the customer by your id
curl -X POST https://payments.clocknext.com/api/v1/signal/unit \
-H "Authorization: Bearer $CLOCKNEXT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "externalId": "acme-42", "agentKey": "seats", "quantity": 3 }'
# A purchase
curl -X POST https://payments.clocknext.com/api/v1/purchases \
-H "Authorization: Bearer $CLOCKNEXT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "externalId": "acme-42", "planId": "plan_growth" }'await cnk.signals.unit({ externalId: "acme-42", agentKey: "seats", quantity: 3 });
await cnk.purchases.create({ externalId: "acme-42", planId: "plan_growth" });
// Endpoints that take customerId only: look the customer up first.
const customer = await cnk.customers.getByExternalId("acme-42");
const invoices = await cnk.invoices.list({ customerId: customer.id });Signals are checked later
Signals are processed in the background, so a signal whose externalId
matches no customer is still accepted (202). It then sits as Pending
with the 404 reason on the Signals page. Create the customer (or set its
externalId) and retry it.
Addressing a customer by it
These routes take the external id right in the path. Each behaves exactly like
its /customers/{id} twin — same body, same response — and is documented on
the same page, in the "using external Id" section:
| Route | Page |
|---|---|
GET /customers/ext/{externalId} | Get a customer |
PATCH /customers/ext/{externalId} | Update a customer |
DELETE /customers/ext/{externalId} | Delete a customer |
GET /customers/ext/{externalId}/balances | Get a customer's balances |
GET /customers/ext/{externalId}/plan | Get a customer's current plan |
PUT /customers/ext/{externalId}/recurring-grant | Change a recurring grant |
POST /customers/ext/{externalId}/credits/adjust | Manage a customer's credit balance |
POST /customers/ext/{externalId}/outcomes/adjust | Manage a customer's outcome balance |
POST /customers/ext/{externalId}/units/adjust | Manage a customer's unit balance |
POST /customers/ext/{externalId}/composites/adjust | Manage a customer's composite balance |
POST /customers/ext/{externalId}/wallet/adjust | Manage a customer's wallet balance |
A malformed {externalId} is a 400; one matching no active customer is a
404. Every other /customers/{id}/… route — the wallet read and members —
still takes our id only.
curl https://payments.clocknext.com/api/v1/customers/ext/acme-42 \
-H "Authorization: Bearer $CLOCKNEXT_API_KEY"
curl -X POST https://payments.clocknext.com/api/v1/customers/ext/acme-42/credits/adjust \
-H "Authorization: Bearer $CLOCKNEXT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "agentKey": "deep-research", "delta": 500 }'const customer = await cnk.customers.getByExternalId("acme-42");
await cnk.customers.updateByExternalId("acme-42", { currencyCode: "USD" });
await cnk.customers.updateRecurringGrantByExternalId("acme-42", {
kind: "wallet",
quantity: 10000,
});
await cnk.customers.adjustCreditByExternalId("acme-42", { agentKey: "deep-research", delta: 500 });
await cnk.customers.adjustWalletByExternalId("acme-42", { delta: 250 });Reading it back
Customer objects carry externalId (or null when unset). Invoice, payment
and purchase rows do not — they carry the customer's customerId.