@clocknext/sdk 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -23
- package/dist/index.cjs +554 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +773 -57
- package/dist/index.d.ts +773 -57
- package/dist/index.js +555 -54
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @clocknext/sdk
|
|
2
2
|
|
|
3
3
|
Official ClockNext SDK for Node/TypeScript. Record **usage signals** and manage
|
|
4
|
-
**customers** from your
|
|
4
|
+
**plans, purchases, invoices, customers, catalogs, and billing** from your
|
|
5
|
+
backend — full coverage of the ClockNext `/api/v1` surface.
|
|
5
6
|
|
|
6
7
|
> **Server-side only.** Your `cnk_…` API key has org-wide write access — never
|
|
7
8
|
> ship it to a browser. For embedding the customer portal in a browser, mint a
|
|
@@ -13,7 +14,9 @@ Official ClockNext SDK for Node/TypeScript. Record **usage signals** and manage
|
|
|
13
14
|
npm install @clocknext/sdk
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
Requires Node ≥ 18 (native `fetch`). Zero runtime dependencies.
|
|
17
|
+
Requires Node ≥ 18 (native `fetch`). Zero runtime dependencies. Uses only
|
|
18
|
+
Web-standard `fetch` and `crypto`, so it also runs on edge runtimes (Cloudflare
|
|
19
|
+
Workers, Vercel Edge, Deno, Bun).
|
|
17
20
|
|
|
18
21
|
## Quick start
|
|
19
22
|
|
|
@@ -25,7 +28,7 @@ const cnk = new ClockNext({ apiKey: process.env.CLOCKNEXT_API_KEY! });
|
|
|
25
28
|
await cnk.signals.credit({
|
|
26
29
|
customerId: "cus_123",
|
|
27
30
|
model: "llama-3.3-70b-versatile", // catalog model id (OrgModel.modelId)
|
|
28
|
-
|
|
31
|
+
agentKey: "api_credit", // the credit's stable agent key
|
|
29
32
|
member: "alice@acme.com",
|
|
30
33
|
tokens: { input: 1200, output: 340, cache: 0 },
|
|
31
34
|
custom: { feature: "summarize" },
|
|
@@ -42,29 +45,31 @@ a typed helper that enforces the right required fields at compile time.
|
|
|
42
45
|
|
|
43
46
|
`model` is always the **catalog model id** enabled in your org (matched
|
|
44
47
|
case-insensitively against `OrgModel.modelId`). Credits and outcomes are
|
|
45
|
-
referenced by their **stable `
|
|
48
|
+
referenced by their **stable `agentKey`** (assigned when you create the credit /
|
|
46
49
|
outcome step), not by display name.
|
|
47
50
|
|
|
48
51
|
```ts
|
|
49
|
-
// Credit — metered against a credit (by
|
|
50
|
-
await cnk.signals.credit({ customerId, model: "llama-3.3-70b-versatile",
|
|
52
|
+
// Credit — metered against a credit (by agentKey); charged at the model's real cost + your margin
|
|
53
|
+
await cnk.signals.credit({ customerId, model: "llama-3.3-70b-versatile", agentKey: "api_credit", tokens: { input: 1200, output: 340 } });
|
|
51
54
|
|
|
52
55
|
// Wallet — debits the customer's USD wallet at model cost
|
|
53
56
|
await cnk.signals.wallet({ customerId, model: "openai/gpt-oss-120b", tokens: { input: 800, output: 5000 } });
|
|
54
57
|
|
|
55
|
-
// Outcome — advances one step of a workflow (
|
|
56
|
-
await cnk.signals.outcome({ customerId, model: "llama-3.3-70b-versatile",
|
|
58
|
+
// Outcome — advances one step of a workflow (agentKey = the OutcomeStep's agent key)
|
|
59
|
+
await cnk.signals.outcome({ customerId, model: "llama-3.3-70b-versatile", agentKey: "resolved_ticket.triage" });
|
|
57
60
|
```
|
|
58
61
|
|
|
59
|
-
> Usage `/api/v1/usage` responses use the envelope
|
|
60
|
-
> `{ statusCode, statusDetail, result }`; customer endpoints currently use
|
|
61
|
-
> `{ ok, … }`. The SDK unwraps both for you.
|
|
62
|
-
|
|
63
62
|
### Async vs sync
|
|
64
63
|
|
|
65
64
|
By default the client is in **`async`** mode: `track()` buffers the signal and
|
|
66
|
-
returns immediately (`{ queued: true }`), and a background flusher
|
|
67
|
-
on a size/interval trigger. This keeps billing telemetry off your LLM hot
|
|
65
|
+
returns immediately (`{ queued: true }`), and a background flusher drains the
|
|
66
|
+
buffer on a size/interval trigger. This keeps billing telemetry off your LLM hot
|
|
67
|
+
path.
|
|
68
|
+
|
|
69
|
+
> **"Batching" here means timing, not a bulk request.** Each signal is still its
|
|
70
|
+
> own `POST /api/v1/usage`; the flusher just groups *when* sends happen and caps
|
|
71
|
+
> how many run at once (`batch.maxConcurrency`). There is no single bulk-ingest
|
|
72
|
+
> call, so size your request budget per signal.
|
|
68
73
|
|
|
69
74
|
To get the computed result back (and any real-time allowance rejection), either:
|
|
70
75
|
|
|
@@ -82,15 +87,23 @@ const cnk = new ClockNext({ apiKey, mode: "sync" });
|
|
|
82
87
|
|
|
83
88
|
### Reliability
|
|
84
89
|
|
|
85
|
-
- **Idempotency:** every signal carries an idempotency key (auto-generated once,
|
|
86
|
-
reused across retries) so a transport retry can't double-count. Pass your own
|
|
87
|
-
`idempotencyKey` for deterministic, cross-process dedup. *(Server-side dedup
|
|
88
|
-
enforcement is rolling out — see the SDK plan.)*
|
|
89
90
|
- **Retries:** transient failures (network, timeout, `408/409/429/5xx`) are
|
|
90
|
-
retried with exponential backoff + jitter, honoring `Retry-After`.
|
|
91
|
-
`4xx` (`400/401/404/422`) are never retried.
|
|
91
|
+
retried with exponential backoff + equal jitter, honoring `Retry-After`.
|
|
92
|
+
Deterministic `4xx` (`400/401/404/422`) are never retried.
|
|
93
|
+
- **Replay safety:** reads (`GET`), idempotent replaces (`PUT`, e.g.
|
|
94
|
+
`customers.setCurrency`), and `DELETE` are retried automatically. Metering
|
|
95
|
+
signals (`signals.*`) opt into retries too, since metering is best-effort.
|
|
96
|
+
Other non-idempotent writes — e.g. `customers.create`, `plans.create`,
|
|
97
|
+
`purchases.create` — are *not* auto-retried, so a transient blip can never
|
|
98
|
+
create a duplicate.
|
|
92
99
|
- **Flushing:** call `await cnk.flush()` before a serverless return, and
|
|
93
100
|
`await cnk.close()` on graceful shutdown to drain the buffer.
|
|
101
|
+
- **Durability:** the async buffer is **in-memory**. A signal already accepted
|
|
102
|
+
by the server is durable on the server side, but signals still buffered when a
|
|
103
|
+
process crashes (before send) are lost. For zero-loss-before-receipt, either
|
|
104
|
+
run in `sync` mode (or `{ wait: true }`) so the send completes before your
|
|
105
|
+
code continues, or call `flush()`/`close()` at the right boundaries. A
|
|
106
|
+
pluggable before-send persistence store is not yet available.
|
|
94
107
|
|
|
95
108
|
## Customers
|
|
96
109
|
|
|
@@ -111,11 +124,109 @@ await cnk.customers.plan(c.id);
|
|
|
111
124
|
await cnk.customers.revenue(c.id, { range: "30d" });
|
|
112
125
|
```
|
|
113
126
|
|
|
127
|
+
### Members
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
await cnk.customers.addMember(c.id, { name: "Alice", role: "ADMIN", email: "alice@acme.com" });
|
|
131
|
+
const members = await cnk.customers.listMembers(c.id);
|
|
132
|
+
await cnk.customers.updateMember(c.id, members[0].id, { name: "Alice R." });
|
|
133
|
+
await cnk.customers.removeMember(c.id, members[0].id);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Wallet & billing config
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
// wallet
|
|
140
|
+
await cnk.customers.wallet(c.id, { limit: 50 }); // { balanceUsd, transactions }
|
|
141
|
+
await cnk.customers.addWalletEntry(c.id, { type: "CREDIT", amount: 25 }); // top up (DEBIT to spend)
|
|
142
|
+
|
|
143
|
+
// billing config
|
|
144
|
+
await cnk.customers.setCurrency(c.id, "EUR");
|
|
145
|
+
await cnk.customers.setCreditTypes(c.id, ["cr_1", "cr_2"]);
|
|
146
|
+
|
|
147
|
+
// grant / claw back balances (delta is signed)
|
|
148
|
+
await cnk.customers.adjustCredit(c.id, { creditId: "cr_1", delta: 100 });
|
|
149
|
+
const { remaining } = await cnk.customers.adjustOutcome(c.id, { outcomeId: "out_1", delta: -1 });
|
|
150
|
+
await cnk.customers.adjustUnit(c.id, { unitId: "un_1", delta: 5 });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Plans & purchases
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
// define a plan
|
|
157
|
+
const plan = await cnk.plans.create({
|
|
158
|
+
name: "Pro",
|
|
159
|
+
billingCycle: "MONTHLY",
|
|
160
|
+
currencyCode: "USD",
|
|
161
|
+
components: [
|
|
162
|
+
{ type: "WALLET", billingMode: "ADVANCE", amount: 100 },
|
|
163
|
+
{ type: "CREDIT", billingMode: "ADVANCE", creditId: "cr_1", quantity: 10_000 },
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
await cnk.plans.list({ active: true });
|
|
167
|
+
await cnk.plans.setActive(plan.id, false);
|
|
168
|
+
|
|
169
|
+
// subscribe a customer (auto-cancels their existing active/scheduled purchase)
|
|
170
|
+
const purchase = await cnk.purchases.create({ customerId: c.id, planId: plan.id });
|
|
171
|
+
await cnk.purchases.setAutoPayment(purchase.id, false, { pauseReason: "trial" });
|
|
172
|
+
await cnk.purchases.cancel(purchase.id);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Invoices & payments (read-only)
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const invoices = await cnk.invoices.list({ customerId: c.id, status: "OPEN" });
|
|
179
|
+
const { invoice, plan } = await cnk.invoices.get(invoices[0].id);
|
|
180
|
+
const { url } = await cnk.invoices.payLink(invoices[0].id); // hosted pay link (OPEN only)
|
|
181
|
+
|
|
182
|
+
const payments = await cnk.payments.list({ customerId: c.id });
|
|
183
|
+
await cnk.payments.get(payments[0].id); // id = the paid invoice id
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Catalogs: credits, outcomes, units
|
|
187
|
+
|
|
188
|
+
Define the billable *types*; record usage against them with `signals.*`.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
// credit type — signals.credit({ agentKey }) meters against it
|
|
192
|
+
await cnk.credits.create({ name: "API", agentKey: "api_credit", basePrice: 1, marginPercent: 20, pricePerCredit: 0.01 });
|
|
193
|
+
await cnk.credits.list({ active: true });
|
|
194
|
+
|
|
195
|
+
// outcome type — a multi-step workflow; each step has its own agentKey
|
|
196
|
+
await cnk.outcomes.create({
|
|
197
|
+
name: "Resolved ticket",
|
|
198
|
+
basePrice: 1, marginPercent: 20, pricePerOutcome: 2,
|
|
199
|
+
steps: [{ name: "Triage", agentKey: "resolved_ticket.triage", basePrice: 0.5 }],
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// unit type — a quantity-priced meter (FLAT / SLAB / VOLUME)
|
|
203
|
+
await cnk.units.create({ name: "Seats", pricingType: "FLAT", flatPrice: 5 });
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Unit metering
|
|
207
|
+
|
|
208
|
+
Record a unit event (no tokens) and read a customer's unit balances:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
await cnk.signals.unit({ customerId: c.id, unit: "Seats" });
|
|
212
|
+
await cnk.signals.unitUsage({ customerId: c.id });
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Webhooks
|
|
216
|
+
|
|
217
|
+
Threshold alerts — at most one per kind (`CREDIT` / `OUTCOME` / `WALLET`):
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
await cnk.webhooks.upsert({ kind: "WALLET", threshold: 10, url: "https://acme.com/hooks/clocknext" });
|
|
221
|
+
await cnk.webhooks.list();
|
|
222
|
+
await cnk.webhooks.delete("WALLET");
|
|
223
|
+
```
|
|
224
|
+
|
|
114
225
|
## Customer portal token
|
|
115
226
|
|
|
116
227
|
```ts
|
|
117
228
|
const { token, expiresAt } = await cnk.portal.createToken({ customerId: "cus_123", ttlSeconds: 600 });
|
|
118
|
-
// render <iframe src={`https://
|
|
229
|
+
// render <iframe src={`https://payments.clocknext.com/portal/embed?token=${token}`} />
|
|
119
230
|
```
|
|
120
231
|
|
|
121
232
|
## Errors
|
|
@@ -137,14 +248,14 @@ try {
|
|
|
137
248
|
`ValidationError (400)` · `AuthError (401)` · `NotFoundError (404)` ·
|
|
138
249
|
`PlanError (422)` · `AllowanceError (422)` · `ConflictError (409)` ·
|
|
139
250
|
`RateLimitError (429)` · `ServerError (5xx)` · `NetworkError` — all extend
|
|
140
|
-
`ClockNextError` (`.status`, `.retryable
|
|
251
|
+
`ClockNextError` (`.status`, `.retryable`).
|
|
141
252
|
|
|
142
253
|
## Configuration
|
|
143
254
|
|
|
144
255
|
```ts
|
|
145
256
|
new ClockNext({
|
|
146
257
|
apiKey: "cnk_…", // required
|
|
147
|
-
baseUrl: "https://
|
|
258
|
+
baseUrl: "https://payments.clocknext.com",
|
|
148
259
|
mode: "async", // "async" | "sync"
|
|
149
260
|
timeoutMs: 10_000,
|
|
150
261
|
batch: { maxSize: 20, maxIntervalMs: 2000, maxConcurrency: 5, maxQueueSize: 10_000 },
|
|
@@ -156,6 +267,9 @@ new ClockNext({
|
|
|
156
267
|
});
|
|
157
268
|
```
|
|
158
269
|
|
|
270
|
+
Numeric options are validated: a negative or non-finite value (e.g. a bad
|
|
271
|
+
`timeoutMs`) falls back to its default rather than silently breaking the client.
|
|
272
|
+
|
|
159
273
|
## License
|
|
160
274
|
|
|
161
275
|
MIT
|