@parallel-protocol/x402 0.5.3 → 0.7.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
@@ -40,6 +40,7 @@ Agent Merchant (this SDK) Facilitator
40
40
  - The on-chain settlement happens after the handler produces a 2xx response — the agent is not charged on errors.
41
41
  - If settlement fails (on-chain revert, facilitator timeout), the middleware returns `402` and discards the handler's response.
42
42
  - Handlers are bounded by a 30-second timeout on Express, Hono and Next.js — a slow handler returns `504` and the agent is not charged. (Fastify manages handler execution itself, so no timeout is applied there.)
43
+ - On paid Fastify routes, writing to `reply.raw` (the SSE/streaming pattern) fails loudly instead of sending: a raw response skips the settlement hooks and would go out unpaid. Streaming is not supported on paid routes.
43
44
  - CORS preflight (`OPTIONS`) is always passed through without challenge.
44
45
 
45
46
  ---
@@ -192,12 +193,15 @@ interface PaymentMiddlewareConfig {
192
193
 
193
194
  | Field | Type | Required | Description |
194
195
  |-------|------|----------|-------------|
195
- | `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). |
196
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. |
197
198
  | `network` | `string` | Yes | Chain slug: `"ethereum"`, `"base"`, `"avalanche"`, `"hyperevm"`. |
198
199
  | `payTo` | `Address` | Yes | The merchant's receiving address. |
199
- | `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. |
200
201
  | `description` | `string` | No | Human-readable description of the resource, included in the 402 body. |
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). |
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. |
201
205
 
202
206
  Route matching uses **longest prefix wins**: `/api/v1/users` will match a `/api/v1` route config before a `/api` one.
203
207
 
@@ -299,12 +303,12 @@ interface PaymentRequired {
299
303
  mimeType: "application/json";
300
304
  };
301
305
  accepts: Array<{ // one entry per accepted token
302
- scheme: "exact";
306
+ scheme: "exact" | "upto"; // the route's scheme ("exact" unless configured)
303
307
  network: string; // EIP-155 format: "eip155:8453"
304
308
  asset: string; // token address
305
- amount: string; // smallest unit of `asset`, per its own decimals
309
+ amount: string; // smallest unit of `asset`, per its own decimals — the cap for "upto"
306
310
  payTo: string;
307
- maxTimeoutSeconds: 300;
311
+ maxTimeoutSeconds: number; // the route's settlement window (default 300)
308
312
  extra: { decimals: number }; // decimals of `asset` — payers need not guess
309
313
  }>;
310
314
  }
@@ -350,6 +354,7 @@ interface PaymentConfirmation {
350
354
  chain: string; // "base"
351
355
  gasSponsored: boolean;
352
356
  networkId: string; // "eip155:8453"
357
+ amountSettled?: string; // metered routes: what was actually charged (smallest unit)
353
358
  }
354
359
  ```
355
360
 
@@ -357,9 +362,176 @@ The `access-control-expose-headers` header is set automatically so browsers can
357
362
 
358
363
  ---
359
364
 
365
+ ## Metered routes (`upto`)
366
+
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.
368
+
369
+ ```typescript
370
+ routes: {
371
+ "/v1/chat/completions": {
372
+ scheme: "upto",
373
+ price: "2", // the cap, in USDp
374
+ maxTimeoutSeconds: 600, // the handler may take up to ~9 minutes
375
+ network: "base",
376
+ payTo: "0xYourAddress",
377
+ },
378
+ },
379
+ ```
380
+
381
+ The handler reports the amount to charge through the `payment-settle-amount` response header, in whole token units like `price`:
382
+
383
+ ```typescript
384
+ import { SETTLE_AMOUNT_HEADER } from "@parallel-protocol/x402";
385
+
386
+ fastify.post("/v1/chat/completions", async (request, reply) => {
387
+ const completion = await runInference(request.body);
388
+ reply.header(SETTLE_AMOUNT_HEADER, completion.costInUsdp); // e.g. "0.0137"
389
+ return completion;
390
+ });
391
+ ```
392
+
393
+ The middleware strips the header before the response leaves, whatever the status code: a handler that prices the request and then fails never tells the client what it was about to charge. The value must follow the same format as `price` (a plain decimal such as `"0.0137"`, no leading zeros on the integer part, no exponent, no unit) and must not carry more decimals than the token has. Rules on a `2xx` response:
394
+
395
+ | Header value | What happens |
396
+ |---|---|
397
+ | absent | The cap is charged. |
398
+ | `"0"` (an explicit zero) | The request is served for free — nothing is settled, no `payment-response`. |
399
+ | a decimal up to `price` | That amount is settled; the receipt's `amountSettled` carries it. |
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
+
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
+
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.
427
+
428
+ ---
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
+
360
532
  ## Payment routes
361
533
 
362
- The facilitator supports 12 settlement routes. The route is selected automatically based on the signed payload:
534
+ The facilitator supports 13 settlement routes. The route is selected automatically based on the signed payload:
363
535
 
364
536
  | Route | Credential method(s) | tokenIn (agent pays) | tokenOut (merchant receives) | Gas est. | Notes |
365
537
  |-------|--------------------------|---------------------|------------------------------|----------|-------|
@@ -375,6 +547,7 @@ The facilitator supports 12 settlement routes. The route is selected automatical
375
547
  | **J** | `transferWithAuthorization` | sUSDp | sUSDp | ~65K | Direct sUSDp share transfer |
376
548
  | **K** | `redeemWithAuthorization` + `swapExactOutputWithAuthorization` | sUSDp | non-USDC backing (exact) | ~180K | Same as I but for other Parallelizer collaterals (atomic multicall3) |
377
549
  | **L** | `partialRedeemWithAuthorization` | sUSDp (partial) + USDp | USDp or backing collateral | ~180K | Partial sUSDp redeem combined with existing USDp; delivers to merchant (atomic multicall3) |
550
+ | **U** | `uptoWithAuthorization` | USDp (cap) | USDp (actual) | ~130K | Metered payment: the escrow pulls the signed cap, pays the merchant the settled amount and refunds the rest atomically (scheme `upto`) |
378
551
 
379
552
  **Chain requirements:**
380
553
  - Routes B, D, E, G, I, K, L require the **Parallelizer** to be deployed on the target chain (Ethereum, Base, Avalanche, HyperEVM).
@@ -393,6 +566,9 @@ import {
393
566
  // Middleware (framework-agnostic)
394
567
  createPaymentGate,
395
568
  createPaymentMiddleware,
569
+ resolveCharge,
570
+ resolveSettleAmount,
571
+ SETTLE_AMOUNT_HEADER,
396
572
 
397
573
  // Facilitator client
398
574
  FacilitatorClient,
@@ -400,6 +576,8 @@ import {
400
576
  // Errors
401
577
  X402ConfigError,
402
578
  X402RuntimeError,
579
+ X402PriceRejectedError,
580
+ isPriceRejection,
403
581
  X402_ERROR_CODES,
404
582
  type X402ErrorCode,
405
583
 
@@ -412,6 +590,8 @@ import {
412
590
  type FacilitatorConfig,
413
591
  type FacilitatorResponse,
414
592
  type HTTPAdapter,
593
+ type MeterContext,
594
+ type MeterFunction,
415
595
  type MiddlewareResult,
416
596
  type PassResult,
417
597
  type PaymentConfirmation,
@@ -420,6 +600,8 @@ import {
420
600
  type PaymentMiddlewareConfig,
421
601
  type PaymentRequired,
422
602
  type PaymentRequirements,
603
+ type PriceContext,
604
+ type PriceFunction,
423
605
  type ResourceInfo,
424
606
  type RouteConfig,
425
607
  type RunHandlerResult,
@@ -443,6 +625,33 @@ import {
443
625
 
444
626
  Each subpath also exports its adapter class (`ExpressAdapter`, `NextAdapter`, `FastifyAdapter`, `HonoAdapter`) for use with [`createPaymentGate`](#advanced-createpaymentgate).
445
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
+
446
655
  ---
447
656
 
448
657
  ## Advanced: `createPaymentGate`
@@ -530,6 +739,17 @@ A single-call `client.pay(paymentHeader, requirements)` (`POST /x402/pay`) also
530
739
  |-------------|------|
531
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` |
532
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 |
533
753
 
534
754
  | `X402RuntimeError.code` | Meaning |
535
755
  |-------------------------|---------|