@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 +255 -0
- package/dist/src/cost.d.ts +1 -1
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +1 -1
- package/dist/src/cost.js.map +1 -1
- package/dist/src/index.d.ts +7 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/paid-endpoints.d.ts +46 -0
- package/dist/src/paid-endpoints.d.ts.map +1 -0
- package/dist/src/paid-endpoints.js +53 -0
- package/dist/src/paid-endpoints.js.map +1 -0
- package/dist/src/paygate-cli.d.ts +3 -0
- package/dist/src/paygate-cli.d.ts.map +1 -0
- package/dist/src/paygate-cli.js +97 -0
- package/dist/src/paygate-cli.js.map +1 -0
- package/dist/src/paygate.d.ts +160 -0
- package/dist/src/paygate.d.ts.map +1 -0
- package/dist/src/paygate.js +336 -0
- package/dist/src/paygate.js.map +1 -0
- package/dist/src/pricing/kie.d.ts.map +1 -1
- package/dist/src/pricing/kie.js +22 -8
- package/dist/src/pricing/kie.js.map +1 -1
- package/dist/src/with-paid-gate.d.ts +27 -0
- package/dist/src/with-paid-gate.d.ts.map +1 -0
- package/dist/src/with-paid-gate.js +91 -0
- package/dist/src/with-paid-gate.js.map +1 -0
- package/package.json +4 -1
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)
|
package/dist/src/cost.d.ts
CHANGED
package/dist/src/cost.d.ts.map
CHANGED
|
@@ -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,
|
|
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
package/dist/src/cost.js.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
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
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
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 {
|
|
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
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
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 @@
|
|
|
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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"}
|