@parallel-protocol/x402 0.6.0 → 0.7.1
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 +177 -4
- package/dist/{chunk-TLCEPWQI.js → chunk-AENXD4OC.js} +232 -79
- package/dist/chunk-AFRDHGDT.js +36 -0
- package/dist/chunk-JPMK5YBU.cjs +42 -0
- package/dist/{chunk-K6QETMQA.cjs → chunk-POU7EH3E.cjs} +254 -99
- package/dist/express.cjs +53 -4
- package/dist/express.d.cts +2 -1
- package/dist/express.d.ts +2 -1
- package/dist/express.js +50 -1
- package/dist/fastify.cjs +37 -12
- package/dist/fastify.d.cts +2 -1
- package/dist/fastify.d.ts +2 -1
- package/dist/fastify.js +33 -8
- package/dist/hono.cjs +21 -4
- package/dist/hono.d.cts +2 -1
- package/dist/hono.d.ts +2 -1
- package/dist/hono.js +18 -1
- package/dist/index.cjs +37 -24
- package/dist/index.d.cts +39 -5
- package/dist/index.d.ts +39 -5
- package/dist/index.js +2 -1
- package/dist/llm.cjs +486 -0
- package/dist/llm.d.cts +203 -0
- package/dist/llm.d.ts +203 -0
- package/dist/llm.js +473 -0
- package/dist/next.cjs +35 -14
- package/dist/next.d.cts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/next.js +29 -8
- package/dist/{types-CBzm5CtM.d.cts → types-q_gu5wjz.d.cts} +56 -2
- package/dist/{types-CBzm5CtM.d.ts → types-q_gu5wjz.d.ts} +56 -2
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -193,13 +193,14 @@ interface PaymentMiddlewareConfig {
|
|
|
193
193
|
|
|
194
194
|
| Field | Type | Required | Description |
|
|
195
195
|
|-------|------|----------|-------------|
|
|
196
|
-
| `price` | `string \| bigint` | Yes | Amount required. A string like `"0.01"` is parsed with each accepted token's own decimals (or with `decimals` when set explicitly). Pass a `bigint` to skip parsing. |
|
|
196
|
+
| `price` | `string \| bigint \| (ctx) => string \| bigint` | Yes | Amount required. A string like `"0.01"` is parsed with each accepted token's own decimals (or with `decimals` when set explicitly). Pass a `bigint` to skip parsing. A function prices each request from its content, see [Per-request pricing](#per-request-pricing). |
|
|
197
197
|
| `decimals` | `number` | No | Override for the decimals used to parse a string price. By default each accepted token is priced with its own decimals from the `@parallel-protocol/chains` catalog — a `"0.01"` price is correct whether paid in USDp (18) or USDC (6), no configuration. Only needed for a custom token outside the catalog; without it, an unknown token raises `X402ConfigError` rather than being silently guessed. |
|
|
198
198
|
| `network` | `string` | Yes | Chain slug: `"ethereum"`, `"base"`, `"avalanche"`, `"hyperevm"`. |
|
|
199
199
|
| `payTo` | `Address` | Yes | The merchant's receiving address. |
|
|
200
|
-
| `acceptedTokens` | `Address[]` | No | List of accepted token addresses. Defaults to `[USDp, USDC, sUSDp]` for known networks (see [Default tokens](#default-tokens)). |
|
|
200
|
+
| `acceptedTokens` | `Address[]` | No | List of accepted token addresses. Defaults to `[USDp, USDC, sUSDp]` for known networks (see [Default tokens](#default-tokens)). On an `upto` route it defaults to USDp alone, and any other token is rejected at construction: metered payments are USDp only. |
|
|
201
201
|
| `description` | `string` | No | Human-readable description of the resource, included in the 402 body. |
|
|
202
202
|
| `scheme` | `"exact" \| "upto"` | No | `exact` (default) charges `price` as invoiced. `upto` invoices `price` as a **cap** the payer signs; the handler names the actual amount to charge and the rest is refunded on-chain — see [Metered routes](#metered-routes-upto). |
|
|
203
|
+
| `meter` | `(ctx) => string \| bigint \| undefined` | No | `upto` routes only: reads the amount to charge from the handler's response, see [Reading the amount from the response](#reading-the-amount-from-the-response-meter). |
|
|
203
204
|
| `maxTimeoutSeconds` | `number` | No | Settlement window the payer signs for (default 300). Metered routes announce how long their handler may take; the facilitator caps it at 3600 (one hour). Express, Hono and Next.js derive the handler timeout from it; Fastify does not time handlers out, set the server's `connectionTimeout` / `requestTimeout` yourself. |
|
|
204
205
|
|
|
205
206
|
Route matching uses **longest prefix wins**: `/api/v1/users` will match a `/api/v1` route config before a `/api` one.
|
|
@@ -363,7 +364,7 @@ The `access-control-expose-headers` header is set automatically so browsers can
|
|
|
363
364
|
|
|
364
365
|
## Metered routes (`upto`)
|
|
365
366
|
|
|
366
|
-
Some resources only know their cost after the work is done — an LLM completion is priced by the tokens it generates. A route declared with `scheme: "upto"` invoices `price` as a **cap**: the payer signs an authorization for that cap, the handler serves the request and names the amount it actually charges, and the facilitator settles that amount through Parallel's escrow contract, refunding the remainder to the payer in the same transaction. Metered payments are made in USDp.
|
|
367
|
+
Some resources only know their cost after the work is done — an LLM completion is priced by the tokens it generates. A route declared with `scheme: "upto"` invoices `price` as a **cap**: the payer signs an authorization for that cap, the handler serves the request and names the amount it actually charges, and the facilitator settles that amount through Parallel's escrow contract, refunding the remainder to the payer in the same transaction. Metered payments are made in USDp: `acceptedTokens` defaults to the network's USDp and may not list any other token.
|
|
367
368
|
|
|
368
369
|
```typescript
|
|
369
370
|
routes: {
|
|
@@ -373,7 +374,6 @@ routes: {
|
|
|
373
374
|
maxTimeoutSeconds: 600, // the handler may take up to ~9 minutes
|
|
374
375
|
network: "base",
|
|
375
376
|
payTo: "0xYourAddress",
|
|
376
|
-
acceptedTokens: [PARALLEL_TOKENS.base.usdp],
|
|
377
377
|
},
|
|
378
378
|
},
|
|
379
379
|
```
|
|
@@ -399,10 +399,136 @@ The middleware strips the header before the response leaves, whatever the status
|
|
|
399
399
|
| a decimal up to `price` | That amount is settled; the receipt's `amountSettled` carries it. |
|
|
400
400
|
| malformed, more decimals than the token, or above `price` | The response is discarded and the client gets 402 `INVALID_SETTLE_AMOUNT` — a handler bug never charges the payer, and a positive amount the token cannot represent is never silently served for free. Round your cost to the token's precision before setting the header. |
|
|
401
401
|
|
|
402
|
+
### Reading the amount from the response: `meter`
|
|
403
|
+
|
|
404
|
+
Instead of a header the handler sets, a route may carry a `meter` function that reads the amount from the response itself, so the handler stays a plain handler. It receives `{ status, header(name), body, request }`: the response status and headers, its JSON body as the middleware parsed it (`undefined` when the response is not JSON), and the request as the price function saw it. It returns an amount in the same format as `price`, or nothing:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
"/v1/chat/completions": {
|
|
408
|
+
scheme: "upto",
|
|
409
|
+
price: "2",
|
|
410
|
+
network: "base",
|
|
411
|
+
payTo: "0xYourAddress",
|
|
412
|
+
meter: ({ body }) => (body as { usage?: { cost?: number } }).usage?.cost?.toFixed(6),
|
|
413
|
+
},
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
| `meter` result | What happens |
|
|
417
|
+
|---|---|
|
|
418
|
+
| an amount up to `price` | That amount is settled. |
|
|
419
|
+
| an amount above `price` | **The cap is settled**: an overrun is the merchant's loss on that request, not a refused response. Size the cap so it does not happen. |
|
|
420
|
+
| `"0"` | The request is served for free, nothing is settled. |
|
|
421
|
+
| `undefined` or `null` | The `payment-settle-amount` header decides, with the rules above (absent charges the cap). |
|
|
422
|
+
| it throws, or returns something that is not an amount | The response is discarded and the client gets 402 `INVALID_SETTLE_AMOUNT`. |
|
|
423
|
+
|
|
424
|
+
The response body is read only on routes that have a `meter` function, from a clone or from the buffered chunks the middleware already holds, so other routes are untouched. Reading it gets the handler's own time budget: a body that streams from a slow upstream and is not complete within the window is treated like a failing meter. Return a buffered body on metered routes. `meter` only applies to `upto` routes.
|
|
425
|
+
|
|
402
426
|
Timing: the payer's authorization expires after `maxTimeoutSeconds`, and settlement needs a minute at most. On Express, Hono and Next.js the handler timeout is raised to `maxTimeoutSeconds − 60 s` for metered routes; Fastify handlers are never timed out by the middleware, so size the server's own `connectionTimeout` and `requestTimeout` to the window. A handler that finishes after the window is served but cannot be settled: the client gets 402 `PAYMENT_SETTLEMENT_FAILED` and the merchant has done the work for nothing — keep a margin.
|
|
403
427
|
|
|
404
428
|
---
|
|
405
429
|
|
|
430
|
+
## Per-request pricing
|
|
431
|
+
|
|
432
|
+
Some resources are priced by what is asked of them: an LLM completion by the messages sent and the `max_tokens` allowed. Give such a route a `price` function instead of a fixed amount. It receives the request and returns a price in the same format as a fixed `price`:
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
routes: {
|
|
436
|
+
"/v1/chat/completions": {
|
|
437
|
+
scheme: "upto",
|
|
438
|
+
network: "base",
|
|
439
|
+
payTo: "0xYourAddress",
|
|
440
|
+
price: ({ body }) => {
|
|
441
|
+
const { max_tokens = 1024 } = body as { max_tokens?: number };
|
|
442
|
+
return (max_tokens * 0.000002).toFixed(6); // the cap for this request, in USDp
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
The function gets `{ method, path, url, header(name), body }`. `body` is the request body as the framework parsed it (`undefined` when there is none), so it is available where your framework parses it:
|
|
449
|
+
|
|
450
|
+
| Framework | `body` |
|
|
451
|
+
|-----------|--------|
|
|
452
|
+
| Fastify | Parsed by Fastify. Routes priced per request, or metered from their response, are gated at the `preHandler` stage (after parsing and schema validation); other routes keep gating at `onRequest`. |
|
|
453
|
+
| Express | `req.body`, so mount `express.json()` before `paymentMiddleware`. |
|
|
454
|
+
| Hono | Parsed with `c.req.json()` when the content type is JSON. Hono caches it, your handler can read it again. |
|
|
455
|
+
| Next.js | Parsed from a clone of the request when the content type is JSON; the handler keeps the original. |
|
|
456
|
+
|
|
457
|
+
The function runs twice per paid request: on the unpaid request, to build the 402, and on the paid retry, to size what the facilitator verifies. It must therefore be **pure**: same request, same price, no clock and no randomness. A retry whose body changed in between is priced again, and refused by the facilitator if the signed amount no longer covers the new price. It may be `async`.
|
|
458
|
+
|
|
459
|
+
A function that throws or returns something that is not a price is a merchant bug: the request fails with `X402ConfigError` rather than being served for free or mispriced. To refuse a request the route will not serve (an unsupported option, a resource that does not exist) throw `X402PriceRejectedError(code, message, status = 400)` instead: the client gets that status with `{ error: code, message }`, and nothing is invoiced or signed.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## LLM pricing (`@parallel-protocol/x402/llm`)
|
|
464
|
+
|
|
465
|
+
Everything a metered inference route needs, so the merchant writes no pricing or metering code: a price function that computes the cap from the request, a meter that reads the actual cost from the provider's response, and a relay to the provider.
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
import {
|
|
469
|
+
chatCompletionCap,
|
|
470
|
+
chatCompletionMeter,
|
|
471
|
+
forwardChatCompletion,
|
|
472
|
+
openRouterPrices,
|
|
473
|
+
} from "@parallel-protocol/x402/llm";
|
|
474
|
+
|
|
475
|
+
await app.register(
|
|
476
|
+
paymentMiddleware({
|
|
477
|
+
facilitator: { url: "https://agents.parallel.best" },
|
|
478
|
+
routes: {
|
|
479
|
+
"/v1/chat/completions": {
|
|
480
|
+
scheme: "upto",
|
|
481
|
+
network: "base",
|
|
482
|
+
payTo: "0xYourAddress",
|
|
483
|
+
maxTimeoutSeconds: 600,
|
|
484
|
+
price: chatCompletionCap({
|
|
485
|
+
prices: openRouterPrices(), // live prices, or a fixed table (below)
|
|
486
|
+
maxOutputTokens: 4096, // the largest completion this route pays for
|
|
487
|
+
models: ["deepseek/deepseek-chat", "anthropic/claude-sonnet-4.5"],
|
|
488
|
+
}),
|
|
489
|
+
meter: chatCompletionMeter(), // the provider's usage.cost, or tokens at the model's prices
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
}),
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
app.post("/v1/chat/completions", async (request, reply) => {
|
|
496
|
+
const upstream = await forwardChatCompletion(request.body, {
|
|
497
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
498
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
499
|
+
maxOutputTokens: 4096, // the same bound as the cap
|
|
500
|
+
});
|
|
501
|
+
return reply.status(upstream.status).send(upstream.body);
|
|
502
|
+
});
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
The same handler on the other frameworks: Express `res.status(upstream.status).json(upstream.body)`, Hono `c.json(upstream.body, upstream.status)`, Next.js `NextResponse.json(upstream.body, { status: upstream.status })`.
|
|
506
|
+
|
|
507
|
+
`forwardChatCompletion(body, { baseUrl, apiKey, maxOutputTokens, outputTokensField, timeout, headers, fetch, fields })` posts to `${baseUrl}/chat/completions` with only the fields the cap accounts for (`FORWARDED_FIELDS`: model, messages, tools, sampling parameters, reasoning options), `stream: false` and the completion bounded by `maxOutputTokens` (sent as `max_tokens`, or `max_completion_tokens` for providers that want it; a `reasoning.max_tokens` budget is bounded the same way), and returns the provider's `{ status, body }`: a 2xx and the statuses about the client's request (400, 404, 413, 422, 429 and the like) are handed back as they are, so nothing is settled on an error; a status about the merchant's account with the provider (401, 402, 403, 5xx) is folded into 502 `UPSTREAM_ERROR` with a fixed body, so the client never sees the merchant's provider details; a provider that cannot be reached answers 502 `UPSTREAM_UNAVAILABLE`, a timeout 504 `UPSTREAM_TIMEOUT` (five minutes by default).
|
|
508
|
+
|
|
509
|
+
`chatCompletionMeter({ prices? })` reads the amount from the response: `usage.cost` in dollars when the provider reports one (OpenRouter does), otherwise `usage.prompt_tokens` and `usage.completion_tokens` at the model's prices when `prices` is given, otherwise nothing, which charges the cap. So a provider that reports no cost (DeepSeek, most direct APIs) needs the same `prices` as the cap, or every request settles at the cap with nothing refunded and no error to show it. A zero cost serves the request for free. Cached input tokens are charged at the full input price in the token-count path.
|
|
510
|
+
|
|
511
|
+
The cap of a request is a bound on its input tokens at the model's input price, plus the largest completion the request may produce (`max_tokens`, bounded by `maxOutputTokens`, which also stands in for it when absent) at its output price. One USDp per dollar. The cap must cover the real count whichever tokenizer the provider uses, or the response is refused and the merchant has paid the provider for nothing, so the built-in bound counts one token per UTF-8 byte of the messages and tools, which no tokenizer exceeds. English prose runs at about four characters per token, so the cap is loose there: the payer only pays the provider's real count and the rest is refunded. A merchant who wants a tighter cap plugs a real tokenizer through `estimateInputTokens`.
|
|
512
|
+
|
|
513
|
+
| Option | Default | Description |
|
|
514
|
+
|--------|---------|-------------|
|
|
515
|
+
| `prices` | required | `openRouterPrices()` or a table `{ [model]: { input, output, maxOutputTokens? } }` in USD per million tokens. |
|
|
516
|
+
| `maxOutputTokens` | `4096` | Bound on the completion the cap pays for. **Forward the same bound to the provider**: a request may still ask for more. |
|
|
517
|
+
| `margin` | `0` | Extra margin on the input bound, for a merchant who replaced it with a tighter estimate. |
|
|
518
|
+
| `models` | all known | Models this route serves; others are refused with `MODEL_NOT_ALLOWED`. |
|
|
519
|
+
| `allowStreaming` | `false` | Streamed requests are refused unless set. A streamed response cannot carry the actual cost, so it is charged the cap. |
|
|
520
|
+
| `estimateInputTokens` | built-in | Replace the bound with a real tokenizer. |
|
|
521
|
+
|
|
522
|
+
A handler that does not use `forwardChatCompletion` should still forward only the fields the cap accounts for, never the raw body: options billed outside the token model are refused (`plugins`, `web_search_options`, `modalities` other than text) but a provider may add others. With `chatCompletionMeter`, a cost above the cap settles the cap; a handler that names more than the cap in `payment-settle-amount` instead has its response refused with `INVALID_SETTLE_AMOUNT`.
|
|
523
|
+
|
|
524
|
+
`openRouterPrices({ ttl: "24h", timeout: "10s" })` loads the public models listing (no API key) on first use, serves it from memory and refreshes it in the background once older than `ttl`, so no request waits for OpenRouter after the first one; a load is abandoned after `timeout`, a refresh that fails keeps the current list, and a failed load is not retried for five seconds. With no list at all, requests are refused with a 503 `PRICING_UNAVAILABLE`. A model the list does not know may have been released since the list was loaded: once the list is more than five minutes old, such a request refreshes it in the background and is refused with an `UNKNOWN_MODEL` message that says so, and the next request finds the model. A fixed table never touches the network: `prices: { "deepseek-flash": { input: 0.30, output: 1.20 } }`.
|
|
525
|
+
|
|
526
|
+
Requests the route cannot meter are refused before anything is invoiced, with a 400 and a JSON body: an unknown model (`UNKNOWN_MODEL`), a model outside `models` (`MODEL_NOT_ALLOWED`), more than one choice (`n`), non-text content, plugins, web search, image or audio output, or streaming (`UNSUPPORTED_OPTION`), a malformed body (`INVALID_REQUEST`). Live prices are the one accepted exception to the purity rule of price functions: a list refreshed between the unpaid request and the paid retry may re-price the retry, which is then refused and re-invoiced.
|
|
527
|
+
|
|
528
|
+
Without `meter`, what is charged after the response is the handler's call: set the `payment-settle-amount` header to the provider's cost, see [Metered routes](#metered-routes-upto).
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
406
532
|
## Payment routes
|
|
407
533
|
|
|
408
534
|
The facilitator supports 13 settlement routes. The route is selected automatically based on the signed payload:
|
|
@@ -440,6 +566,9 @@ import {
|
|
|
440
566
|
// Middleware (framework-agnostic)
|
|
441
567
|
createPaymentGate,
|
|
442
568
|
createPaymentMiddleware,
|
|
569
|
+
resolveCharge,
|
|
570
|
+
resolveSettleAmount,
|
|
571
|
+
SETTLE_AMOUNT_HEADER,
|
|
443
572
|
|
|
444
573
|
// Facilitator client
|
|
445
574
|
FacilitatorClient,
|
|
@@ -447,6 +576,8 @@ import {
|
|
|
447
576
|
// Errors
|
|
448
577
|
X402ConfigError,
|
|
449
578
|
X402RuntimeError,
|
|
579
|
+
X402PriceRejectedError,
|
|
580
|
+
isPriceRejection,
|
|
450
581
|
X402_ERROR_CODES,
|
|
451
582
|
type X402ErrorCode,
|
|
452
583
|
|
|
@@ -459,6 +590,8 @@ import {
|
|
|
459
590
|
type FacilitatorConfig,
|
|
460
591
|
type FacilitatorResponse,
|
|
461
592
|
type HTTPAdapter,
|
|
593
|
+
type MeterContext,
|
|
594
|
+
type MeterFunction,
|
|
462
595
|
type MiddlewareResult,
|
|
463
596
|
type PassResult,
|
|
464
597
|
type PaymentConfirmation,
|
|
@@ -467,6 +600,8 @@ import {
|
|
|
467
600
|
type PaymentMiddlewareConfig,
|
|
468
601
|
type PaymentRequired,
|
|
469
602
|
type PaymentRequirements,
|
|
603
|
+
type PriceContext,
|
|
604
|
+
type PriceFunction,
|
|
470
605
|
type ResourceInfo,
|
|
471
606
|
type RouteConfig,
|
|
472
607
|
type RunHandlerResult,
|
|
@@ -490,6 +625,33 @@ import {
|
|
|
490
625
|
|
|
491
626
|
Each subpath also exports its adapter class (`ExpressAdapter`, `NextAdapter`, `FastifyAdapter`, `HonoAdapter`) for use with [`createPaymentGate`](#advanced-createpaymentgate).
|
|
492
627
|
|
|
628
|
+
### LLM pricing (`@parallel-protocol/x402/llm`)
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
import {
|
|
632
|
+
chatCompletionCap, // the ready-made price function, see LLM pricing
|
|
633
|
+
chatCompletionMeter, // the ready-made meter: usage.cost, or tokens at the model's prices
|
|
634
|
+
forwardChatCompletion, // the relay to an OpenAI-compatible provider
|
|
635
|
+
FORWARDED_FIELDS, // the request fields the relay forwards
|
|
636
|
+
openRouterPrices, // live prices from OpenRouter, cached
|
|
637
|
+
staticPrices, // a fixed table in USD per million tokens
|
|
638
|
+
estimateInputTokens, // the built-in input token bound of a body
|
|
639
|
+
estimateTokens, // the bound of a piece of text
|
|
640
|
+
parseOpenRouterModels, // the models listing to a price table
|
|
641
|
+
parseDuration,
|
|
642
|
+
formatMicroUsd, // micro-dollars to an amount string
|
|
643
|
+
microFromUsd, // dollars to whole micro-dollars, rounded up
|
|
644
|
+
type ChatCompletionCapOptions,
|
|
645
|
+
type ChatCompletionMeterOptions,
|
|
646
|
+
type ChatCompletionBody,
|
|
647
|
+
type ForwardChatCompletionOptions,
|
|
648
|
+
type ForwardedResponse,
|
|
649
|
+
type ModelPrice,
|
|
650
|
+
type PriceSource,
|
|
651
|
+
type PriceTable,
|
|
652
|
+
} from "@parallel-protocol/x402/llm";
|
|
653
|
+
```
|
|
654
|
+
|
|
493
655
|
---
|
|
494
656
|
|
|
495
657
|
## Advanced: `createPaymentGate`
|
|
@@ -577,6 +739,17 @@ A single-call `client.pay(paymentHeader, requirements)` (`POST /x402/pay`) also
|
|
|
577
739
|
|-------------|------|
|
|
578
740
|
| `X402ConfigError` | Invalid config at construction (bad `payTo`, bad facilitator URL, empty `routes`, non-positive `price`) — plus, on the first request to a route, an unknown network with no `acceptedTokens`, or an accepted token whose decimals are neither in the catalog nor set via `decimals` |
|
|
579
741
|
| `X402RuntimeError` | Runtime failure; `.code` is one of the SDK codes below, or a facilitator error code propagated verbatim (see next table) |
|
|
742
|
+
| `X402PriceRejectedError` | Thrown by a price function to refuse a request before anything is invoiced; the client gets `.status` (400 by default) with `{ error: .code, message }`. `isPriceRejection(err)` recognises one by shape. |
|
|
743
|
+
|
|
744
|
+
Refusals `chatCompletionCap` makes, all before payment:
|
|
745
|
+
|
|
746
|
+
| `error` | Status | Meaning |
|
|
747
|
+
|---------|--------|---------|
|
|
748
|
+
| `INVALID_REQUEST` | 400 | No JSON object body, no `model`, no `messages`, or a `max_tokens` that is not a positive integer |
|
|
749
|
+
| `UNKNOWN_MODEL` | 400 | The price source has no price for this model |
|
|
750
|
+
| `MODEL_NOT_ALLOWED` | 400 | The model is outside the route's `models` list |
|
|
751
|
+
| `UNSUPPORTED_OPTION` | 400 | More than one choice, non-text content, plugins, web search, image or audio output, or streaming on a route that does not allow it |
|
|
752
|
+
| `PRICING_UNAVAILABLE` | 503 | Live prices could not be loaded yet; retry shortly |
|
|
580
753
|
|
|
581
754
|
| `X402RuntimeError.code` | Meaning |
|
|
582
755
|
|-------------------------|---------|
|