@apicity/cost 0.2.0-alpha.0 → 0.2.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 CHANGED
@@ -142,6 +142,261 @@ Maintenance is manual: re-fetch each upstream's pricing page, edit `pricing.ts`,
142
142
  | `kie` | `per-unit-table` | priced per second of video / per image |
143
143
  | `free` | `free` | always $0 |
144
144
 
145
+ ## Paid endpoint guard (OTP pay gate)
146
+
147
+ Some endpoints have a direct marginal compute cost (e.g. video generation).
148
+ The cost package maintains a small, explicit **paid-endpoint registry**.
149
+ Endpoints that are **not** in the registry are assumed free and require no
150
+ caller changes.
151
+
152
+ Paid endpoints require a **single-use OTP** (one-time password) minted from a
153
+ shared **HMAC secret**. The gate is fail-closed and does **no** cost
154
+ estimation — it is pure authorization: a paid call cannot fire unless the
155
+ provider was constructed with the secret **and** the caller presents a valid,
156
+ request-bound OTP. The autonomous caller never holds the secret, so it cannot
157
+ self-approve; only the human or the code client that holds the secret can mint.
158
+
159
+ There are **no environment variables and no key files** — the secret is passed
160
+ in via factory options (or the MCP server's `--paygate-secret-file`).
161
+
162
+ ### Registry model
163
+
164
+ - `PAID_ENDPOINTS` is the canonical list. Every entry is an exact triple of
165
+ `(provider, method, dotPath)` — there is no regex, prefix, wildcard, or
166
+ inferred matching.
167
+ - Unlisted endpoints are free and pass through without OTP or configuration.
168
+ - Listed endpoints block unless a valid OTP is supplied.
169
+
170
+ ### Token format
171
+
172
+ OTP tokens are a dependency-free compact envelope:
173
+
174
+ ```
175
+ <base64url(payloadJson)>.<base64url(HMAC-SHA256(payloadSegment, secret))>
176
+ ```
177
+
178
+ The signature is HMAC-SHA256 over the exact base64url payload segment bytes,
179
+ verified in constant time. Payload schema:
180
+
181
+ ```ts
182
+ interface PayGateOtpPayload {
183
+ v: 1; // version
184
+ jti: string; // random 128-bit hex (unique token id)
185
+ provider: string; // e.g. "kie"
186
+ method: string; // e.g. "POST"
187
+ dotPath: string; // e.g. "api.v1.jobs.createTask"
188
+ requestHash: `sha256:${string}`; // sha256 of canonical request JSON
189
+ iat: number; // issued-at unix seconds
190
+ exp: number; // expiration unix seconds
191
+ }
192
+ ```
193
+
194
+ ### Configuration
195
+
196
+ The code client supplies a `PayGateConfig` via factory options:
197
+
198
+ ```ts
199
+ interface PayGateConfig {
200
+ secret: string; // shared HMAC secret (the code client holds it)
201
+ replayStore?: ReplayStore; // defaults to an in-process Set, per provider instance
202
+ now?: () => number; // clock injection for tests; defaults to Date.now
203
+ }
204
+
205
+ interface ReplayStore {
206
+ has(jti: string): boolean;
207
+ add(jti: string): void;
208
+ }
209
+ ```
210
+
211
+ ```ts
212
+ import { createKie } from "@apicity/kie";
213
+
214
+ const provider = createKie({
215
+ apiKey: process.env.KIE_API_KEY!,
216
+ paygate: { secret: loadSecret() }, // from your secret manager / config
217
+ });
218
+ ```
219
+
220
+ ### Minting OTPs
221
+
222
+ `mintOtp` is pure and env-free — the secret is passed explicitly and the OTP
223
+ binds to the exact request via its canonical hash:
224
+
225
+ ```ts
226
+ import { mintOtp } from "@apicity/cost";
227
+
228
+ const otp = mintOtp(secret, {
229
+ dotPath: "api.v1.jobs.createTask", // provider/method resolved from the registry
230
+ request: payload, // bound by canonical hash
231
+ ttl: "10m", // seconds or "10m" / "1h" / "1d"; defaults to 10m
232
+ });
233
+ ```
234
+
235
+ ### Request canonicalization
236
+
237
+ Before hashing, the request payload is canonicalized: serialized to JSON with
238
+ **sorted object keys** (recursive), preserving array order, rejecting non-JSON
239
+ values (functions, undefined, circular references). The canonical string is
240
+ SHA-256 hashed and prefixed with `sha256:`. Change any byte of the request and
241
+ verification fails.
242
+
243
+ ### Replay protection
244
+
245
+ Each OTP `jti` is single-use. The default `ReplayStore` is an in-process Set
246
+ scoped to one provider instance (no files, no `XDG_STATE_HOME`). Pass a custom
247
+ `replayStore` for cross-process or persistent protection. The `jti` is consumed
248
+ **before** dispatch — see [Retry semantics](#retry-semantics).
249
+
250
+ ### Public interface
251
+
252
+ ```ts
253
+ interface PayGateApproval {
254
+ otp: string;
255
+ }
256
+
257
+ async function dispatchWithPaidGate<T>(
258
+ provider: string,
259
+ method: string,
260
+ dotPath: string,
261
+ payload: Record<string, unknown>,
262
+ approval: PayGateApproval | undefined,
263
+ dispatch: () => Promise<T>,
264
+ config?: PayGateConfig
265
+ ): Promise<T>;
266
+ ```
267
+
268
+ Paid endpoint APIs accept the approval as a second options object:
269
+
270
+ ```ts
271
+ const task = await provider.post.api.v1.jobs.createTask(
272
+ { model: "kling-3.0/video", input: { prompt: "...", duration: "5" } },
273
+ { otp }
274
+ );
275
+ ```
276
+
277
+ ### Guard behavior
278
+
279
+ 1. **Preflight** — if the endpoint is not in `PAID_ENDPOINTS`, dispatch runs
280
+ immediately.
281
+ 2. **Configuration** — a paid endpoint with no `paygate.secret` throws
282
+ `PayGateError` (`paygate-not-configured`).
283
+ 3. **OTP presence** — paid endpoints require `approval.otp`; if omitted the call
284
+ throws `PayGateError` (`otp-missing`).
285
+ 4. **Signature** — the payload segment's HMAC is verified (constant-time)
286
+ against the secret; mismatch throws `PayGateError` (`otp-invalid-signature`).
287
+ 5. **Expiration** — `exp` in the past throws `PayGateError` (`otp-expired`).
288
+ 6. **Request binding** — `provider`, `method`, `dotPath`, and `requestHash` must
289
+ match the actual call, else `PayGateError` (`otp-mismatched-request`).
290
+ 7. **Replay check** — a `jti` already in the store throws `PayGateError`
291
+ (`otp-replayed`).
292
+ 8. **Consume + dispatch** — the `jti` is recorded, then the HTTP request fires.
293
+
294
+ ```ts
295
+ import { PayGateError } from "@apicity/cost";
296
+
297
+ try {
298
+ await provider.post.api.v1.jobs.createTask({ ... }, { otp });
299
+ } catch (e) {
300
+ if (e instanceof PayGateError) {
301
+ // e.code: paygate-not-configured | otp-missing | otp-malformed
302
+ // | otp-invalid-signature | otp-expired
303
+ // | otp-mismatched-request | otp-replayed
304
+ } else throw e;
305
+ }
306
+ ```
307
+
308
+ ### Failure modes
309
+
310
+ | Condition | `PayGateError.code` |
311
+ | ---------------------------------- | ------------------------ |
312
+ | Provider built without a secret | `paygate-not-configured` |
313
+ | Paid endpoint without OTP | `otp-missing` |
314
+ | Malformed envelope | `otp-malformed` |
315
+ | Invalid HMAC signature | `otp-invalid-signature` |
316
+ | Expired OTP (`exp` < now) | `otp-expired` |
317
+ | Mismatched provider/method/dotPath | `otp-mismatched-request` |
318
+ | Mismatched request hash | `otp-mismatched-request` |
319
+ | Replayed OTP (`jti` seen) | `otp-replayed` |
320
+
321
+ ### CLI: minting OTPs
322
+
323
+ The `apicity-paygate` binary mints OTPs. The secret is read from a **file**
324
+ (never an env var); only the OTP is printed to stdout:
325
+
326
+ ```bash
327
+ apicity-paygate otp mint \
328
+ --secret-file ./paygate.secret \
329
+ --dot-path api.v1.jobs.createTask \
330
+ --payload-file request.json \
331
+ --ttl 10m
332
+ ```
333
+
334
+ ### Wiring the gate into a provider
335
+
336
+ Providers apply the gate once at the bottom of their factory using
337
+ `withPaidGate`. The walker descends the HTTP-method roots (`post`, `get`,
338
+ `delete`, `patch`, `put`) and routes every leaf whose `(provider, method,
339
+ dotPath)` is in `PAID_ENDPOINTS` through `dispatchWithPaidGate`. Free leaves
340
+ pass through unchanged; sub-providers, schema records, and other non-route
341
+ properties are returned by reference. The config is passed once and shared
342
+ (one replay store per provider instance):
343
+
344
+ ```ts
345
+ import { withPaidGate } from "@apicity/cost";
346
+
347
+ export function createKie(opts: KieOptions): KieProvider {
348
+ // ...build endpoint functions...
349
+ return withPaidGate(
350
+ "kie",
351
+ {
352
+ veo: createVeoProvider(...), // sub-provider, untouched
353
+ modelInputSchemas, // data, untouched
354
+ post: { api: { v1: { jobs: { createTask: Object.assign(createTask, { schema }) } } } },
355
+ get: { api: { v1: { jobs: { recordInfo } } } },
356
+ },
357
+ { config: opts.paygate }
358
+ );
359
+ }
360
+ ```
361
+
362
+ The gate is generic — `xai` and other providers opt in simply by adding a
363
+ `PAID_ENDPOINTS` entry and threading `{ config: opts.paygate }` through their
364
+ factory.
365
+
366
+ ### Retry semantics
367
+
368
+ The OTP `jti` is consumed **before** `dispatch()` runs. If dispatch later fails
369
+ (network error, upstream 5xx, abort), the `jti` stays consumed and the caller
370
+ must mint a fresh OTP to retry. This is intentional — without it, a hostile
371
+ caller could replay a single OTP on every transient failure. Treat each OTP as
372
+ single-use authority for one network attempt.
373
+
374
+ ### MCP server
375
+
376
+ The `@apicity/mcp-server` is the code client: start it with
377
+ `--paygate-secret-file <path>` and it holds the secret to **verify** OTPs (it
378
+ never mints). A human mints an OTP out-of-band (same secret) and the caller
379
+ passes it as the paid tool's `otp` argument — so an AI driving the tool cannot
380
+ self-approve.
381
+
382
+ ### Minimal operator workflow
383
+
384
+ 1. **Generate a secret** (one-time) and store it (secret manager / file).
385
+ 2. **Prepare a request** JSON file.
386
+ 3. **Mint an OTP**:
387
+ ```bash
388
+ apicity-paygate otp mint \
389
+ --secret-file ./paygate.secret \
390
+ --dot-path api.v1.jobs.createTask \
391
+ --payload-file request.json \
392
+ --ttl 10m
393
+ ```
394
+ 4. **Pass the OTP to the caller** (copy-paste, secrets manager, etc.).
395
+ 5. **Caller uses the OTP**:
396
+ ```ts
397
+ await provider.post.api.v1.jobs.createTask({ ... }, { otp: "<paste>" });
398
+ ```
399
+
145
400
  ## Out of scope
146
401
 
147
402
  - Anthropic prompt-cache pricing (rates are in the table but `estimate()` ignores them — assumes no caching)
@@ -1,3 +1,3 @@
1
1
  import type { CostProvider } from "./types";
2
- export declare function cost(): CostProvider;
2
+ export declare function createCost(): CostProvider;
3
3
  //# sourceMappingURL=cost.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/cost.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,SAAS,CAAC;AAE7D,wBAAgB,IAAI,IAAI,YAAY,CAInC"}
1
+ {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/cost.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,SAAS,CAAC;AAE7D,wBAAgB,UAAU,IAAI,YAAY,CAIzC"}
package/dist/src/cost.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { computeEstimate } from "./compute.js";
2
- export function cost() {
2
+ export function createCost() {
3
3
  return {
4
4
  estimate: (req) => computeEstimate(req),
5
5
  };
@@ -1 +1 @@
1
- {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/cost.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,MAAM,UAAU,IAAI;IAClB,OAAO;QACL,QAAQ,EAAE,CAAC,GAAoB,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;KACzD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/cost.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,QAAQ,EAAE,CAAC,GAAoB,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;KACzD,CAAC;AACJ,CAAC"}
@@ -1,4 +1,4 @@
1
- export { cost } from "./cost";
1
+ export { createCost } from "./cost";
2
2
  export { computeEstimate } from "./compute";
3
3
  export { PRICING, PRICING_AS_OF } from "./pricing/index";
4
4
  export { MODEL_SLUGS, MODEL_DISPLAY, modelSlug, modelDisplay } from "./slugs";
@@ -6,4 +6,10 @@ export type { SlugProviderId, SlugModelId } from "./slugs";
6
6
  export type { CostUnit, ModelPricing, PerUnitPricing, PricedProviderId, RateSource, TokenPricing, } from "./pricing/index";
7
7
  export type { CostBreakdown, CostEstimate, CostProvider, CostSource, EstimateRequest, } from "./types";
8
8
  export type { ExtractResult, TextExtract } from "./extract/types";
9
+ export { PAID_ENDPOINTS, lookupPaidEndpoint, isPaidEndpoint, } from "./paid-endpoints";
10
+ export type { PaidEndpointKey, PaidEndpointInfo, PaidEndpointEntry, } from "./paid-endpoints";
11
+ export { PayGateError, dispatchWithPaidGate, mintOtp, createReplayStore, canonicalizeJson, canonicalHash, parseOtp, parseTtl, verifyOtp, } from "./paygate";
12
+ export type { PayGateApproval, PayGateConfig, ReplayStore, OtpCall, PayGateOtpPayload, VerifyFailureCode, VerifyOtpInput, VerifyResult, } from "./paygate";
13
+ export { withPaidGate } from "./with-paid-gate";
14
+ export type { WithPaidGateOptions } from "./with-paid-gate";
9
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC9E,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,YAAY,GACb,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC9E,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,YAAY,GACb,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,eAAe,EACf,aAAa,EACb,WAAW,EACX,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,YAAY,GACb,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/src/index.js CHANGED
@@ -1,5 +1,8 @@
1
- export { cost } from "./cost.js";
1
+ export { createCost } from "./cost.js";
2
2
  export { computeEstimate } from "./compute.js";
3
3
  export { PRICING, PRICING_AS_OF } from "./pricing/index.js";
4
4
  export { MODEL_SLUGS, MODEL_DISPLAY, modelSlug, modelDisplay } from "./slugs.js";
5
+ export { PAID_ENDPOINTS, lookupPaidEndpoint, isPaidEndpoint, } from "./paid-endpoints.js";
6
+ export { PayGateError, dispatchWithPaidGate, mintOtp, createReplayStore, canonicalizeJson, canonicalHash, parseOtp, parseTtl, verifyOtp, } from "./paygate.js";
7
+ export { withPaidGate } from "./with-paid-gate.js";
5
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAsB9E,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAO1B,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,MAAM,WAAW,CAAC;AAYnB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Exact paid-endpoint registry.
3
+ *
4
+ * Only endpoints listed here are considered paid. All unlisted endpoints
5
+ * are assumed free and must preserve current behavior with no caller changes.
6
+ *
7
+ * Matching is exact: provider + method + dotPath. No regex, prefix,
8
+ * wildcard, path-family, generated broad match, or fallback-by-method logic.
9
+ */
10
+ export interface PaidEndpointKey {
11
+ provider: string;
12
+ method: string;
13
+ dotPath: string;
14
+ }
15
+ export interface PaidEndpointInfo {
16
+ /** Human-readable reason why this endpoint is paid. */
17
+ reason: string;
18
+ /** Optional estimator identifier for cost computation. */
19
+ estimatorId?: string;
20
+ /** Optional known cost notes (e.g. per-unit billing details). */
21
+ costNotes?: string;
22
+ }
23
+ export interface PaidEndpointEntry {
24
+ key: PaidEndpointKey;
25
+ info: PaidEndpointInfo;
26
+ }
27
+ /**
28
+ * The canonical list of paid endpoints. Add new entries here only after
29
+ * review. Keep the list small and explicit.
30
+ */
31
+ export declare const PAID_ENDPOINTS: readonly PaidEndpointEntry[];
32
+ /**
33
+ * Look up a paid endpoint by exact key match.
34
+ *
35
+ * Returns `PaidEndpointInfo` only when provider, method, and dotPath all match
36
+ * an entry in `PAID_ENDPOINTS` exactly. Returns `undefined` for every
37
+ * unlisted endpoint, which callers must treat as free.
38
+ */
39
+ export declare function lookupPaidEndpoint(provider: string, method: string, dotPath: string): PaidEndpointInfo | undefined;
40
+ /**
41
+ * Predicate: is this exact endpoint paid?
42
+ *
43
+ * Unlisted endpoints return `false` (assumed free).
44
+ */
45
+ export declare function isPaidEndpoint(provider: string, method: string, dotPath: string): boolean;
46
+ //# sourceMappingURL=paid-endpoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paid-endpoints.d.ts","sourceRoot":"","sources":["../../src/paid-endpoints.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,eAAe,CAAC;IACrB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,iBAAiB,EAetD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,gBAAgB,GAAG,SAAS,CAW9B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAET"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Exact paid-endpoint registry.
3
+ *
4
+ * Only endpoints listed here are considered paid. All unlisted endpoints
5
+ * are assumed free and must preserve current behavior with no caller changes.
6
+ *
7
+ * Matching is exact: provider + method + dotPath. No regex, prefix,
8
+ * wildcard, path-family, generated broad match, or fallback-by-method logic.
9
+ */
10
+ /**
11
+ * The canonical list of paid endpoints. Add new entries here only after
12
+ * review. Keep the list small and explicit.
13
+ */
14
+ export const PAID_ENDPOINTS = [
15
+ {
16
+ key: {
17
+ provider: "kie",
18
+ method: "POST",
19
+ dotPath: "api.v1.jobs.createTask",
20
+ },
21
+ info: {
22
+ reason: "Media generation task that incurs direct marginal compute cost per job",
23
+ estimatorId: "kie-per-unit",
24
+ costNotes: "Billed per unit (seconds/images/songs) based on model and resolution",
25
+ },
26
+ },
27
+ ];
28
+ /**
29
+ * Look up a paid endpoint by exact key match.
30
+ *
31
+ * Returns `PaidEndpointInfo` only when provider, method, and dotPath all match
32
+ * an entry in `PAID_ENDPOINTS` exactly. Returns `undefined` for every
33
+ * unlisted endpoint, which callers must treat as free.
34
+ */
35
+ export function lookupPaidEndpoint(provider, method, dotPath) {
36
+ for (const entry of PAID_ENDPOINTS) {
37
+ if (entry.key.provider === provider &&
38
+ entry.key.method === method &&
39
+ entry.key.dotPath === dotPath) {
40
+ return entry.info;
41
+ }
42
+ }
43
+ return undefined;
44
+ }
45
+ /**
46
+ * Predicate: is this exact endpoint paid?
47
+ *
48
+ * Unlisted endpoints return `false` (assumed free).
49
+ */
50
+ export function isPaidEndpoint(provider, method, dotPath) {
51
+ return lookupPaidEndpoint(provider, method, dotPath) !== undefined;
52
+ }
53
+ //# sourceMappingURL=paid-endpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paid-endpoints.js","sourceRoot":"","sources":["../../src/paid-endpoints.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsBH;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAiC;IAC1D;QACE,GAAG,EAAE;YACH,QAAQ,EAAE,KAAK;YACf,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,wBAAwB;SAClC;QACD,IAAI,EAAE;YACJ,MAAM,EACJ,wEAAwE;YAC1E,WAAW,EAAE,cAAc;YAC3B,SAAS,EACP,sEAAsE;SACzE;KACF;CACF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,MAAc,EACd,OAAe;IAEf,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;QACnC,IACE,KAAK,CAAC,GAAG,CAAC,QAAQ,KAAK,QAAQ;YAC/B,KAAK,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM;YAC3B,KAAK,CAAC,GAAG,CAAC,OAAO,KAAK,OAAO,EAC7B,CAAC;YACD,OAAO,KAAK,CAAC,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAgB,EAChB,MAAc,EACd,OAAe;IAEf,OAAO,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,SAAS,CAAC;AACrE,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=paygate-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paygate-cli.d.ts","sourceRoot":"","sources":["../../src/paygate-cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { mintOtp } from "./paygate.js";
5
+ function parseMintArgs(argv) {
6
+ const out = {};
7
+ for (let i = 0; i < argv.length; i++) {
8
+ const a = argv[i];
9
+ if (a === "--provider")
10
+ out.provider = argv[++i];
11
+ else if (a.startsWith("--provider="))
12
+ out.provider = a.slice(11);
13
+ else if (a === "--method")
14
+ out.method = argv[++i];
15
+ else if (a.startsWith("--method="))
16
+ out.method = a.slice(9);
17
+ else if (a === "--dot-path")
18
+ out.dotPath = argv[++i];
19
+ else if (a.startsWith("--dot-path="))
20
+ out.dotPath = a.slice(11);
21
+ else if (a === "--payload-file")
22
+ out.payloadFile = argv[++i];
23
+ else if (a.startsWith("--payload-file="))
24
+ out.payloadFile = a.slice(15);
25
+ else if (a === "--secret-file")
26
+ out.secretFile = argv[++i];
27
+ else if (a.startsWith("--secret-file="))
28
+ out.secretFile = a.slice(14);
29
+ else if (a === "--ttl")
30
+ out.ttl = argv[++i];
31
+ else if (a.startsWith("--ttl="))
32
+ out.ttl = a.slice(6);
33
+ else {
34
+ console.error(`[apicity-paygate] unknown arg: ${a}`);
35
+ }
36
+ }
37
+ const required = ["dotPath", "payloadFile", "secretFile"];
38
+ for (const key of required) {
39
+ if (out[key] === undefined || out[key] === null) {
40
+ throw new Error(`Missing required argument: --${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`);
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+ function printHelp() {
46
+ console.error([
47
+ "apicity-paygate — Mint OTPs for paid @apicity endpoints.",
48
+ "",
49
+ "Usage:",
50
+ " apicity-paygate otp mint \\",
51
+ " --secret-file <path> \\",
52
+ " --dot-path <api.path> \\",
53
+ " --payload-file <path> \\",
54
+ " [--provider <provider>] \\",
55
+ " [--method <HTTP method>] \\",
56
+ " [--ttl <duration>]",
57
+ "",
58
+ "Options:",
59
+ " --secret-file Path to a file containing the shared HMAC secret",
60
+ " --dot-path API dot-path (e.g. api.v1.jobs.createTask)",
61
+ " --payload-file Path to JSON request payload file",
62
+ " --provider Provider name (optional; resolved from the dot-path)",
63
+ " --method HTTP method (optional; resolved from the dot-path)",
64
+ " --ttl Time-to-live: 10m, 1h, 30s, 1d (default 10m)",
65
+ ].join("\n"));
66
+ }
67
+ async function main() {
68
+ const argv = process.argv.slice(2);
69
+ if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
70
+ printHelp();
71
+ process.exit(0);
72
+ }
73
+ if (argv[0] !== "otp" || argv[1] !== "mint") {
74
+ console.error("[apicity-paygate] only 'otp mint' is supported.");
75
+ printHelp();
76
+ process.exit(1);
77
+ }
78
+ const args = parseMintArgs(argv.slice(2));
79
+ const secret = readFileSync(args.secretFile, "utf8").trim();
80
+ const request = JSON.parse(readFileSync(args.payloadFile, "utf8"));
81
+ const otp = mintOtp(secret, {
82
+ provider: args.provider,
83
+ method: args.method,
84
+ dotPath: args.dotPath,
85
+ request,
86
+ ttl: args.ttl,
87
+ });
88
+ console.log(otp);
89
+ }
90
+ const __filename = fileURLToPath(import.meta.url);
91
+ if (process.argv[1] === __filename) {
92
+ main().catch((err) => {
93
+ console.error("[apicity-paygate] fatal:", err instanceof Error ? err.message : err);
94
+ process.exit(1);
95
+ });
96
+ }
97
+ //# sourceMappingURL=paygate-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paygate-cli.js","sourceRoot":"","sources":["../../src/paygate-cli.ts"],"names":[],"mappings":"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"}