# Error reference
URL: /docs/x1id/errors

> Every error a caller can receive from the X1ID SDK, the REST API and x402 pay-per-call, what it means, and what a UI should do about it.

# Error reference

Both the SDK (`@x1id/resolve`) and the REST API surface a small, fixed set of error conditions. Neither ever falls back to guessing a namespace or returning a plausible-but-wrong address — an unresolved or ambiguous query is always an error, never a best-effort answer.

<LlmActions />

<Callout type="warn">
  **`ambiguous` is a security property, not an edge case.** When a query matches both the `@handle` shape and a known domain shape at once (e.g. `@jack.x1`), X1ID refuses to resolve it rather than picking one. `@jack` and `jack.x1` can be owned by different people. A UI must surface this and ask the user to disambiguate — it must never retry with one namespace stripped, never "try handle first, then domain," and never resolve to whichever namespace happens to have a registered record.
</Callout>

## SDK errors (`ResolveError`)

`resolve()`, `reverse()` and `records()` throw a `ResolveError` with a `code` field from a fixed union — never a bare string — so a UI can branch on it directly instead of parsing a message.

| `code`                | Meaning                                                                                                                                                                                                 | What a UI should do                                                                                                                                                                                                                                         |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unrecognized`        | From `resolve()`: the input isn't a handle or a known domain shape at all — probably a raw address or unrelated text. From `reverse()`: the input isn't a base58 32-byte address.                       | Don't show a resolution error. For `resolve()`, this is the expected result of skipping `looksLikeName()` against arbitrary input (e.g. a pasted address) — treat the input as a literal address. For `reverse()`, it's a caller bug — only pass addresses. |
| `ambiguous`           | The input matches **both** the `@handle` shape and a domain shape at once (e.g. `@jack.x1`).                                                                                                            | Stop. Ask the user which namespace they mean (`@jack`, or `jack.x1`). Never auto-resolve either interpretation.                                                                                                                                             |
| `invalid-handle`      | Right shape (`@`-prefixed), but the content fails handle validation (bad characters, length).                                                                                                           | Show a normal "not a valid handle" validation message.                                                                                                                                                                                                      |
| `invalid-domain`      | Right shape (dot + known TLD), but the label fails domain validation.                                                                                                                                   | Show a normal "not a valid domain" validation message.                                                                                                                                                                                                      |
| `not-found`           | The name is well-formed and in the correct namespace, but nothing is registered under it. May carry `reason: 'nft-burned'` — the handle is tokenized and its NFT was burned, so no one holds authority. | Show "not registered" — distinct from an invalid-input error. A legitimate terminal state, not a retry case.                                                                                                                                                |
| `no-record-for-chain` | The name resolves, but there is no live address record for the specific chain requested (e.g. asking for `ETH` on a handle that has set no ETH record).                                                 | Show "no address on file for this chain" rather than any address. This code exists precisely so a caller never receives a wrong address for a chain the handle hasn't set.                                                                                  |
| `rpc-error`           | Transport failure — the RPC endpoint was unreachable, returned a non-2xx status, or returned a malformed/short account that couldn't be parsed.                                                         | Treat as transient. Safe to retry with backoff; do **not** treat it as "not found."                                                                                                                                                                         |

`ResolveError` also carries the original `input` string where available, plus an optional `reason` (`'nft-burned'` today) for a finer-grained explanation when the code alone isn't enough.

<Callout type="info">
  **What is *not* an error:** `reverse()` returns `null` — it does not throw — when an address has no primary, or when its primary pointer fails one of the three trust rules (stale after a release/re-register, or the address no longer holds the handle). "No primary" is a normal state, not a failure. See [reverse](/docs/x1id/sdk#reverse).
</Callout>

## REST API errors

The [REST API](/docs/x1id/api) returns the same underlying conditions as plain HTTP status codes with a JSON `{ "error": "...", "input": "..." }` body. It is a read-only convenience over the same on-chain data the SDK reads.

### `GET /resolve/:name`

| HTTP  | `error`                                       | Meaning                                                                                                                                                                                                                                                         | SDK equivalent                                                      |
| ----- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `409` | `ambiguous: both a handle and a domain shape` | Input matches both namespace shapes.                                                                                                                                                                                                                            | `ambiguous` — refuse to guess, never retryable by picking one side. |
| `400` | `invalid handle`                              | `@`-shaped (or bare-word) input fails handle validation. A bare word with no `@` and no TLD — `/resolve/nike` — is **treated as a handle** and resolves (`200`); it is not a `400`. (This differs from the SDK, where `resolve('nike')` throws `unrecognized`.) | `invalid-handle`                                                    |
| `400` | `invalid domain`                              | Domain-shaped input fails domain parsing.                                                                                                                                                                                                                       | `invalid-domain`                                                    |
| `404` | `handle not registered`                       | Well-formed `@handle`, nothing registered.                                                                                                                                                                                                                      | `not-found`                                                         |
| `404` | `domain not registered`                       | Well-formed domain, nothing at that account — including on a testnet-pointed instance, where X1NS (mainnet) domains never exist.                                                                                                                                | `not-found`                                                         |
| `502` | `malformed account`                           | The account was fetched but its data doesn't match the expected layout.                                                                                                                                                                                         | `rpc-error` — transient, safe to retry.                             |

### `GET /reverse/:address`

| HTTP  | `error`            | Meaning                                                           | SDK equivalent                                      |
| ----- | ------------------ | ----------------------------------------------------------------- | --------------------------------------------------- |
| `400` | `invalid address`  | `:address` isn't a valid base58 public key.                       | `reverse()` throwing `unrecognized` — a caller bug. |
| `404` | `no primary set`   | No primary for this address (or the pointer failed a trust rule). | `reverse()` returns `null`. Show the bare address.  |
| `502` | `malformed handle` | The handle account exists but couldn't be parsed.                 | Transient; safe to retry.                           |

### Any route

| HTTP  | `error`              | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ----- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `404` | `not found`          | Unknown route (no `input` field). A client/integration bug — check the path.                                                                                                                                                                                                                                                                                                                                                              |
| `405` | `method not allowed` | The HTTP method isn't supported on **that** route — the public `/resolve` and `/reverse` lookups are `GET`-only, so a `POST` to them is a `405`. It does **not** mean the whole API is read-only: the public surface also has write routes (`POST /gift/notify`, `POST /privy/wallet`), and the keyed [`/v1` platform](/docs/x1id/platform) adds sign-in, gating, and more. A `405` is a client bug — check the method against the route. |
| `502` | `rpc unreachable`    | `/version` couldn't reach the configured RPC. Transient; retry with backoff.                                                                                                                                                                                                                                                                                                                                                              |

## x402 payments

These come from the keyed [`/v1` platform](/docs/x1id/platform#pay-per-call-with-x402) when a request with no `X-API-Key` pays per call. A rejected payment is answered with `402` and a body of `{ x402Version, accepts, error, code, message }`: `error` and `code` both carry the code below, `message` explains it, and `accepts` is a **fresh challenge** you can pay straight away. The same object is base64-encoded in the `PAYMENT-REQUIRED` header. A `402` with no `error` field is a plain challenge, not a rejection.

| HTTP  | `code`                                           | Meaning                                                                                                                                                                                                                      | What to do                                                                                                                                                                                                                                                               |
| ----- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `402` | `PAYER_REQUIRED`                                 | The challenge was requested without `X-Payer` (or with a malformed one), so it carries no `extra.reference`.                                                                                                                 | Re-request with `X-Payer: <your wallet>` (or `?payer=`) and pay the reference that comes back.                                                                                                                                                                           |
| `402` | `PAYMENT_PAYLOAD_INVALID`                        | The `PAYMENT-SIGNATURE` / `X-PAYMENT` header is not base64 JSON of a payment payload with `payload.signature` or `payload.reference`.                                                                                        | A client bug. Fix the encoding; the payment, if any, is not consumed.                                                                                                                                                                                                    |
| `402` | `REDEEM_SIGNATURE_REQUIRED`                      | The payment header has no `payload.payerSig`. Since 2026-10-11 every redemption must be signed by the paying wallet.                                                                                                         | Sign `accepts[].extra.redeemMessage` with the wallet that paid and send the base58 signature as `payload.payerSig` ([how](/docs/x1id/platform#sign-the-redemption-with-the-paying-wallet)). The payment was not consumed: resend the same request with the same payment. |
| `402` | `REDEEM_SIGNATURE_INVALID`                       | `payload.payerSig` is not the paying wallet's signature over `extra.redeemMessage` (wrong wallet, wrong message, or not a base58 64-byte signature).                                                                         | Sign the `redeemMessage` for this payment's reference with the wallet bound by `X-Payer`. The payment was not consumed.                                                                                                                                                  |
| `402` | `PAYMENT_NETWORK_UNSUPPORTED`                    | The payload names a network no rail serves.                                                                                                                                                                                  | Pay one of the `accepts[]` entries and copy its `network`.                                                                                                                                                                                                               |
| `402` | `PAYMENT_NOT_FINALIZED`                          | The payment is not visible at `finalized` commitment yet. With `payload.reference`, no finalized transaction carrying the reference is a valid payment yet.                                                                  | Retryable. Wait a few seconds and send the identical request with the same payment header.                                                                                                                                                                               |
| `402` | `PAYMENT_ALREADY_USED`                           | This payment already bought a successful response. Replaying the same signature returns this.                                                                                                                                | Final for that payment. Pay again against the fresh challenge.                                                                                                                                                                                                           |
| `402` | `PAYMENT_FAILED`                                 | The payment transaction landed but failed on chain.                                                                                                                                                                          | Pay again; nothing was transferred.                                                                                                                                                                                                                                      |
| `402` | `PAYMENT_STALE`                                  | The payment is older than the redeem window.                                                                                                                                                                                 | Pay again against the fresh challenge.                                                                                                                                                                                                                                   |
| `402` | `PAYMENT_WRONG_ASSET`                            | The transfer moves a different mint than `accepts[].asset`.                                                                                                                                                                  | Pay the advertised asset.                                                                                                                                                                                                                                                |
| `402` | `PAYMENT_WRONG_DESTINATION`                      | The transfer is not to the associated token account of `accepts[].payTo`.                                                                                                                                                    | Pay the advertised `payTo`.                                                                                                                                                                                                                                              |
| `402` | `PAYMENT_AMOUNT_TOO_LOW`                         | The transfer amount, or the balance actually credited to `payTo`, is below `accepts[].amount`. A Token-2022 transfer fee that leaves `payTo` short, or a Token-2022 payment whose credited amount can't be read, lands here. | Pay at least the advertised amount, net of any transfer fee.                                                                                                                                                                                                             |
| `402` | `PAYER_MISMATCH`                                 | The transfer was not signed by the wallet the challenge was bound to with `X-Payer`.                                                                                                                                         | Pay from that wallet, or request a new challenge naming the wallet you pay from.                                                                                                                                                                                         |
| `402` | `PAYMENT_LAYOUT_INVALID`                         | The transaction's instructions don't match the x402 exact layout (extra instructions such as a Memo, a second transfer, compute-budget values out of bounds).                                                                | Rebuild the payment to [the documented layout](/docs/x1id/platform#the-payment-transaction).                                                                                                                                                                             |
| `402` | `REFERENCE_MISSING`                              | No issued, unexpired reference in the `TransferChecked`, or it is writable, a signer, repeated, or also in another instruction.                                                                                              | Append `accepts[].extra.reference` to the `TransferChecked` as one read-only, non-signer account.                                                                                                                                                                        |
| `402` | `REFERENCE_MISMATCH`                             | The reference was issued for a different request (method, path, query, body or endpoint), or the payment carries more than one reference.                                                                                    | Retry the exact request the challenge was issued for, byte for byte.                                                                                                                                                                                                     |
| `402` | `REFERENCE_REUSED`                               | A different payment carries a reference that already paid for a successful call.                                                                                                                                             | Request a new challenge; each reference redeems once.                                                                                                                                                                                                                    |
| `402` | `REFERENCE_EXPIRED`                              | The reference's 600-second redeem window has passed.                                                                                                                                                                         | Request a new challenge and pay its reference.                                                                                                                                                                                                                           |
| `402` | `MULTISIG_AUTHORITY_UNSUPPORTED`                 | The transfer authority is a token-program multisig account.                                                                                                                                                                  | Pay from a single-signer wallet.                                                                                                                                                                                                                                         |
| `402` | `PAYMENT_UNVERIFIABLE`                           | The payment could not be checked (for example a malformed transaction, or, on a deployment without a payment store, an RPC answer with no block time to bound the payment's age).                                            | Check the signature you sent; pay again if the transaction is not a valid payment.                                                                                                                                                                                       |
| `402` | `VERIFIER_UNAVAILABLE`                           | A verification dependency (RPC lookup, payment store) is temporarily down.                                                                                                                                                   | Retryable. Your payment was not consumed; retry the same request shortly.                                                                                                                                                                                                |
| `402` | `PAYMENT_INVALID`                                | The payment does not satisfy the requirements, for a reason not covered above. `message` names it.                                                                                                                           | Read `message`, fix the payment, and pay again.                                                                                                                                                                                                                          |
| `409` | `PAYMENT_ALREADY_USED` / `REFERENCE_REUSED`      | The payment was valid, but a concurrent request redeemed it first.                                                                                                                                                           | Final for that payment. Request a new challenge.                                                                                                                                                                                                                         |
| `503` | `METERING_UNAVAILABLE`                           | The payment store failed while recording your call, so the response is withheld.                                                                                                                                             | Retryable. Your payment was not consumed; retry the identical request.                                                                                                                                                                                                   |
| `400` | `PAYMENT_HEADER_CONFLICT`                        | Both `PAYMENT-SIGNATURE` and `X-PAYMENT` are present and differ.                                                                                                                                                             | Send one header (`PAYMENT-SIGNATURE` is the v2 name).                                                                                                                                                                                                                    |
| `429` | `RATE_LIMITED`                                   | Too many unauthenticated payment requests from your address.                                                                                                                                                                 | Back off, or use an `X-API-Key`.                                                                                                                                                                                                                                         |
| `502` | `X402_UNAVAILABLE`                               | Pay-per-call is not available on this deployment right now.                                                                                                                                                                  | Use an `X-API-Key`, or retry later.                                                                                                                                                                                                                                      |
| `402` | `SETTLEMENT_FAILED`                              | USDC-via-CDP rail only: the facilitator refused to settle a payment it had verified.                                                                                                                                         | Pay again against a fresh challenge.                                                                                                                                                                                                                                     |
| `502` | `X402_SETTLE_TIMEOUT` / `X402_FACILITATOR_ERROR` | USDC-via-CDP rail only: the facilitator timed out or errored while settling.                                                                                                                                                 | Retry shortly.                                                                                                                                                                                                                                                           |

The USDC-via-CDP codes only apply where that rail is on; `GET /v1/health` lists the live `rails` (api.x1id.io currently runs `x1-native` only).

A call whose handler fails (for example a `404` for an unregistered name) returns that error untouched and does not consume your payment, so you can retry with the same payment.

## On-chain program errors

The errors above come from the **read** surface (resolution). When you **build and send** a transaction — register, trade, make an offer, run an auction, manage a lease or recovery — the on-chain program can reject it with a custom error code. Wallets surface these as `custom program error: 0x…` (hex) or `custom(…)` (decimal). The common ones, with copy a UI can show:

| Code            | Name                                        | What it means                                                                                                                                                                                                                                                                                                               | What to tell the user                                                                                                                     |
| --------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `0x1781` (6017) | `HandleIsTokenized`                         | The name is a capability NFT. Offers, auctions and on-chain recovery (`place_offer`, `start_auction`, `place_bid`, `set_recovery`, `initiate_recovery`, `cancel_recovery`) are not available on a tokenized name.                                                                                                           | "This name trades only as its NFT — sell it with a fixed-price listing or by sending the NFT; offers, auctions and recovery don't apply." |
| `0x93` (147)    | pNFT frozen (listed)                        | The capability NFT is frozen because the name is **listed**. A plain send of a listed NFT fails with this.                                                                                                                                                                                                                  | "Cancel the listing before sending this name."                                                                                            |
| `0x0`           | account already in use                      | A second offer from the same wallet on the same name — your first offer's PDA still exists.                                                                                                                                                                                                                                 | "You already have an open offer on this name — cancel it first."                                                                          |
| `0x1798` (6040) | `PriceChanged`                              | The listing or offer amount changed between when you loaded it and when you signed.                                                                                                                                                                                                                                         | "The price changed — review the new amount and try again."                                                                                |
| `0x1792` (6034) | `BidTooLow`                                 | A bid below the reserve, or not at least 5% over the current high bid.                                                                                                                                                                                                                                                      | "Bid at least the reserve, or 5% above the current high bid."                                                                             |
| `0x1799` (6041) | `SellerCannotBid`                           | The seller tried to bid in their own auction.                                                                                                                                                                                                                                                                               | "You can't bid in your own auction."                                                                                                      |
| `0x178f` (6031) | `AuctionNotEnded`                           | Settling an auction that is still running.                                                                                                                                                                                                                                                                                  | "This auction hasn't ended yet."                                                                                                          |
| `0x1790` (6032) | `AuctionEnded`                              | Bidding on an auction that has already ended.                                                                                                                                                                                                                                                                               | "This auction has ended."                                                                                                                 |
| `0x1791` (6033) | `AuctionHasBids`                            | Cancelling an auction that already has bids, or moving the name out from under one: `transfer`, `release_handle`, `initiate_recovery` and `complete_recovery` all refuse while the name's auction carries a bid.                                                                                                            | "This name has a live auction with bids, so it can't be cancelled, moved or recovered until the auction settles."                         |
| `0x1793` (6035) | `BadDuration`                               | An auction duration outside the allowed range (600 s to 2,592,000 s / 30 days).                                                                                                                                                                                                                                             | "Pick a duration between 10 minutes and 30 days."                                                                                         |
| `0x17ab` (6059) | `VoucherNotExpired`                         | Refunding a gift before its expiry.                                                                                                                                                                                                                                                                                         | "This gift hasn't expired yet — a refund is only possible after it expires."                                                              |
| `0x17dd` (6109) | `VoucherClaimNotRecipient`                  | Claiming a recipient-bound gift through the name-only `claim_voucher` from a wallet other than the bound recipient. Relayers use `claim_voucher_tokenized`, which stays open to any signer.                                                                                                                                 | "Only the person this gift was sent to can claim it this way."                                                                            |
| `0x17df` (6111) | `AuctionNotStale`                           | `close_stale_auction` on an auction whose seller still owns the untokenized name.                                                                                                                                                                                                                                           | "This auction is still live; only the seller can cancel it."                                                                              |
| `0x17e0` (6112) | `VoucherExpiryTooFar`                       | Creating a gift whose expiry is more than 366 days away.                                                                                                                                                                                                                                                                    | "Pick a gift expiry within a year."                                                                                                       |
| `0x17e1` (6113) | `UnreceivableCreatorPayout`                 | Creating a TLD whose creator payout is a sysvar or an executable account, which can never receive XNT.                                                                                                                                                                                                                      | "Use an ordinary wallet address for the creator payout."                                                                                  |
| `0x17e2` (6114) | `NotTreasury`                               | `set_reserved_claim_approval` signed by a key other than the treasury multisig.                                                                                                                                                                                                                                             | "Only the treasury multisig can approve a reserved-name claim."                                                                           |
| `0x17e3` (6115) | `ReservedClaimNotApproved`                  | A reserved-name claim grant for a domain the treasury multisig has not approved for that name (or approved before the name's current reservation).                                                                                                                                                                          | "This name isn't approved for claiming by that domain."                                                                                   |
| `0x17e4` (6116) | `NamespacePricingMismatch`                  | `register_under_namespace_v2` got a pricing account that isn't the TLD's canonical `["ns_pricing", label]` PDA, isn't a valid price table, or is missing, including when a left-out optional placeholder shifts it into another slot. Also returned when the TLD is marked as priced but its price table account is absent. | "Something went wrong building this purchase. Refresh and try again."                                                                     |
| `0x17e5` (6117) | `NamespacePricingRequiresV2`                | The original `register_under_namespace` on a TLD whose creator has set its own prices. Those names can only be registered with `register_under_namespace_v2`.                                                                                                                                                               | "This TLD sets its own prices. Update the app, or buy it somewhere that supports creator pricing."                                        |
| `0x17e6` (6118) | `PriceExceedsMax`                           | `register_under_namespace_v2` where the current price is above the `max_price_lamports` the buyer approved, for example because the creator raised prices after the quote. Nothing is charged except the network fee.                                                                                                       | "The price went up since you got your quote. Review the new price and try again."                                                         |
| `0x17e7` (6119) | `NamespaceTierBelowFloor`                   | `set_namespace_pricing` with a price below the 1 XNT protocol floor (including `0`).                                                                                                                                                                                                                                        | "Each price must be at least 1 XNT."                                                                                                      |
| `0x17e8` (6120) | `NamespaceTierAboveMax`                     | `set_namespace_pricing` with a price above the 100,000,000 XNT protocol ceiling.                                                                                                                                                                                                                                            | "Each price must be at most 100,000,000 XNT."                                                                                             |
| `custom(6101)`  | lease first payment too small               | The first lease-to-own payment is below the one-year minimum (0.025 XNT at divisor 4).                                                                                                                                                                                                                                      | "The first lease payment must cover at least one year."                                                                                   |
| `0xbc4` (3012)  | delegate PDA missing                        | A record-delegate action when no delegate is set for the name (Anchor `AccountNotInitialized`).                                                                                                                                                                                                                             | "No record delegate is set for this name."                                                                                                |
| —               | `reclaim_expired_lease` on a permanent name | The name was registered permanently (or already paid off), so there's no lease to reclaim.                                                                                                                                                                                                                                  | "This name is permanent — there's nothing to reclaim."                                                                                    |

<Callout type="info">
  These are the on-chain program's own codes. The exact hex/decimal pairs are stable for a given program version; branch on them where you can, but always keep a generic "the transaction was rejected" fallback for a code you don't recognise. The anti-snipe extension is **`end = max(end, now + 300s)`** — a late bid pushes the end to 5 minutes from that bid, it does not simply add 5 minutes each time.
</Callout>

## General guidance

* **Never render a bare address.** Every successful result — from the SDK or the REST API — carries the namespace that produced it. Show it in any confirmation UI.
* **Treat `ambiguous` as blocking, not a fallback chain.** Don't retry the same input against "just the handle" or "just the domain" — that reintroduces exactly the wrong-recipient risk the error exists to prevent.
* **`not-found`, `no-record-for-chain` and "no primary" are not retryable.** They're correct, final answers about the current state of the registry.
* **Transport errors (`rpc-error`, HTTP `502`) are the only retryable class on the read surface.** Back off and retry; everything else is a stable answer. For x402, `PAYMENT_NOT_FINALIZED`, `VERIFIER_UNAVAILABLE` and `METERING_UNAVAILABLE` are the retryable codes; after `REDEEM_SIGNATURE_REQUIRED` or `REDEEM_SIGNATURE_INVALID`, resend the same payment with a valid `payerSig`.

## Next

<Cards>
  <Card href="/docs/x1id/sdk" icon="Code" title="SDK reference" description="Where these errors come from, and how resolve/reverse/records behave." />

  <Card href="/docs/x1id/examples" icon="BookOpen" title="Wallet integration" description="A recipient flow that branches on every code above." />

  <Card href="/docs/x1id/api" icon="Zap" title="Resolve API" description="The read-only REST endpoints these HTTP errors come from." />
</Cards>
