X1ID is live on X1 mainnet
X1ID / Error reference

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.

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.

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.

codeMeaningWhat a UI should do
unrecognizedFrom 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.
ambiguousThe 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-handleRight shape (@-prefixed), but the content fails handle validation (bad characters, length).Show a normal "not a valid handle" validation message.
invalid-domainRight shape (dot + known TLD), but the label fails domain validation.Show a normal "not a valid domain" validation message.
not-foundThe 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-chainThe 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-errorTransport 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.

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.

REST API errors

The REST 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

HTTPerrorMeaningSDK equivalent
409ambiguous: both a handle and a domain shapeInput matches both namespace shapes.ambiguous — refuse to guess, never retryable by picking one side.
400invalid 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
400invalid domainDomain-shaped input fails domain parsing.invalid-domain
404handle not registeredWell-formed @handle, nothing registered.not-found
404domain not registeredWell-formed domain, nothing at that account — including on a testnet-pointed instance, where X1NS (mainnet) domains never exist.not-found
502malformed accountThe account was fetched but its data doesn't match the expected layout.rpc-error — transient, safe to retry.

GET /reverse/:address

HTTPerrorMeaningSDK equivalent
400invalid address:address isn't a valid base58 public key.reverse() throwing unrecognized — a caller bug.
404no primary setNo primary for this address (or the pointer failed a trust rule).reverse() returns null. Show the bare address.
502malformed handleThe handle account exists but couldn't be parsed.Transient; safe to retry.

Any route

HTTPerrorMeaning
404not foundUnknown route (no input field). A client/integration bug — check the path.
405method not allowedThe 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 adds sign-in, gating, and more. A 405 is a client bug — check the method against the route.
502rpc unreachable/version couldn't reach the configured RPC. Transient; retry with backoff.

x402 payments

These come from the keyed /v1 platform 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.

HTTPcodeMeaningWhat to do
402PAYER_REQUIREDThe 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.
402PAYMENT_PAYLOAD_INVALIDThe 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.
402REDEEM_SIGNATURE_REQUIREDThe 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). The payment was not consumed: resend the same request with the same payment.
402REDEEM_SIGNATURE_INVALIDpayload.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.
402PAYMENT_NETWORK_UNSUPPORTEDThe payload names a network no rail serves.Pay one of the accepts[] entries and copy its network.
402PAYMENT_NOT_FINALIZEDThe 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.
402PAYMENT_ALREADY_USEDThis payment already bought a successful response. Replaying the same signature returns this.Final for that payment. Pay again against the fresh challenge.
402PAYMENT_FAILEDThe payment transaction landed but failed on chain.Pay again; nothing was transferred.
402PAYMENT_STALEThe payment is older than the redeem window.Pay again against the fresh challenge.
402PAYMENT_WRONG_ASSETThe transfer moves a different mint than accepts[].asset.Pay the advertised asset.
402PAYMENT_WRONG_DESTINATIONThe transfer is not to the associated token account of accepts[].payTo.Pay the advertised payTo.
402PAYMENT_AMOUNT_TOO_LOWThe 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.
402PAYER_MISMATCHThe 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.
402PAYMENT_LAYOUT_INVALIDThe 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.
402REFERENCE_MISSINGNo 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.
402REFERENCE_MISMATCHThe 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.
402REFERENCE_REUSEDA different payment carries a reference that already paid for a successful call.Request a new challenge; each reference redeems once.
402REFERENCE_EXPIREDThe reference's 600-second redeem window has passed.Request a new challenge and pay its reference.
402MULTISIG_AUTHORITY_UNSUPPORTEDThe transfer authority is a token-program multisig account.Pay from a single-signer wallet.
402PAYMENT_UNVERIFIABLEThe 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.
402VERIFIER_UNAVAILABLEA verification dependency (RPC lookup, payment store) is temporarily down.Retryable. Your payment was not consumed; retry the same request shortly.
402PAYMENT_INVALIDThe payment does not satisfy the requirements, for a reason not covered above. message names it.Read message, fix the payment, and pay again.
409PAYMENT_ALREADY_USED / REFERENCE_REUSEDThe payment was valid, but a concurrent request redeemed it first.Final for that payment. Request a new challenge.
503METERING_UNAVAILABLEThe payment store failed while recording your call, so the response is withheld.Retryable. Your payment was not consumed; retry the identical request.
400PAYMENT_HEADER_CONFLICTBoth PAYMENT-SIGNATURE and X-PAYMENT are present and differ.Send one header (PAYMENT-SIGNATURE is the v2 name).
429RATE_LIMITEDToo many unauthenticated payment requests from your address.Back off, or use an X-API-Key.
502X402_UNAVAILABLEPay-per-call is not available on this deployment right now.Use an X-API-Key, or retry later.
402SETTLEMENT_FAILEDUSDC-via-CDP rail only: the facilitator refused to settle a payment it had verified.Pay again against a fresh challenge.
502X402_SETTLE_TIMEOUT / X402_FACILITATOR_ERRORUSDC-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:

CodeNameWhat it meansWhat to tell the user
0x1781 (6017)HandleIsTokenizedThe 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."
0x0account already in useA 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)PriceChangedThe 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)BidTooLowA 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)SellerCannotBidThe seller tried to bid in their own auction."You can't bid in your own auction."
0x178f (6031)AuctionNotEndedSettling an auction that is still running."This auction hasn't ended yet."
0x1790 (6032)AuctionEndedBidding on an auction that has already ended."This auction has ended."
0x1791 (6033)AuctionHasBidsCancelling 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)BadDurationAn 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)VoucherNotExpiredRefunding a gift before its expiry."This gift hasn't expired yet — a refund is only possible after it expires."
0x17dd (6109)VoucherClaimNotRecipientClaiming 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)AuctionNotStaleclose_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)VoucherExpiryTooFarCreating a gift whose expiry is more than 366 days away."Pick a gift expiry within a year."
0x17e1 (6113)UnreceivableCreatorPayoutCreating 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)NotTreasuryset_reserved_claim_approval signed by a key other than the treasury multisig."Only the treasury multisig can approve a reserved-name claim."
0x17e3 (6115)ReservedClaimNotApprovedA 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)NamespacePricingMismatchregister_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)NamespacePricingRequiresV2The 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)PriceExceedsMaxregister_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)NamespaceTierBelowFloorset_namespace_pricing with a price below the 1 XNT protocol floor (including 0)."Each price must be at least 1 XNT."
0x17e8 (6120)NamespaceTierAboveMaxset_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 smallThe 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 missingA 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 nameThe name was registered permanently (or already paid off), so there's no lease to reclaim."This name is permanent — there's nothing to reclaim."

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.

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

Browse the docs

Error reference