v1.0.0
OpenAPI 3.1.1

Phygitals Public API

The Phygitals Public API gives account holders programmatic access with user-scoped API keys: browse packs and marketplace filter facets, buy packs with crypto, and sell claw items back to the pool. All requests target https://api.phygitals.com.

Documented request and response shapes match what the API serves.

Authentication

API keys are user-scoped: create one in the Phygitals web app under Settings → API keys (enable API access first; a connected Solana wallet is required). Keys start with phy_ and are shown in full exactly once at creation. A lost key cannot be recovered, so revoke it and create a new one. Treat keys like passwords: keep them in an environment variable or secrets manager, never hardcoded or shipped client-side.

Authenticated endpoints accept the key in either header:

  • X-API-Key: phy_your_secret_key
  • Authorization: Bearer phy_your_secret_key

Keys carry scopes: vm.buy.crypto (pack purchase endpoints), marketplace.take-claw-bid (claw buyback endpoints) and vault.claim (physical claim endpoints); new keys receive all three by default. A request with a missing, invalid, or revoked key returns 401, as does one whose key lacks the endpoint's scope. Authenticated endpoints are rate-limited; exceeding a limit returns 429.

The catalog reads in this reference (the pack listing, the chase-card previews, and the marketplace filter facets) require no credential. The pack-buy settle and its status poll require a key holding the vm.buy.crypto scope; the buyback quote and the two claw-buyback endpoints require a key holding marketplace.take-claw-bid. On buyback init, pass owner_wallet when opening a sellback for a holder who will co-sign.

The six Vault endpoints require a key holding vault.claim. They claim a holder's vaulted cards for physical delivery and follow the order afterwards. Every call takes owner_wallet; claim-init also takes a wallet_proof from that holder. The Vault section below has the lifecycle, the proof format, and the statuses.

Errors

An error response carries a conventional HTTP status and a JSON envelope: code (a stable machine-readable string, e.g. INPUT_VALIDATION_FAILED), status, message, defined (whether the code is one the endpoint documents), and an optional data object with structured detail (for INPUT_VALIDATION_FAILED, the { formErrors, fieldErrors } from the failed request). For backward compatibility the body also includes an error field equal to message.

Each endpoint documents only the codes it can actually raise, grouped by HTTP status, rather than a catalog-wide list. A status that answers with one code names it in a code const and carries that code's meaning as the body description; a status that answers with several publishes a code enum and a per-code meaning table. Either way every code carries a copy-pasteable example showing the message that endpoint returns.

Server:https://api.phygitals.com/api

Production

Client Libraries

Claw machines

​

Public pack-catalog reads: the available-pack listing and chase-card previews for a pack (“what could I pull?”).

List available packs

​

Lists every purchasable pack in the storefront catalog: pricing, expected value (EV), rarity distribution, and reward metadata for each. Public catalog data: no credential required. Use platform to select a storefront (defaults to the first-party mainnet packs), includeRepacks=true to include partner repack listings, and excludeVariants=true to drop a base pack's sibling variant editions and list only base packs. Returns 200 with an empty array when no packs match the filters.

Query Parameters

  • Platform filter (e.g. "mainnet" for first-party Phygitals packs). Defaults to "mainnet".

  • Include partner repack listings. Accepts a real boolean from an RPC-style caller, or the literal string "true" from a query string; any other string is treated as false.

    • Include partner repack listings. Accepts a real boolean from an RPC-style caller, or the literal string "true" from a query string; any other string is treated as false.

  • Exclude child variant packs (rows with a non-null variant_of, a base pack's sibling spice-level editions), returning only base packs. Accepts a real boolean from an RPC-style caller, or the literal string "true" from a query string; any other value is treated as false. Omitted returns every pack, variants included.

    • Exclude child variant packs (rows with a non-null variant_of, a base pack's sibling spice-level editions), returning only base packs. Accepts a real boolean from an RPC-style caller, or the literal string "true" from a query string; any other value is treated as false. Omitted returns every pack, variants included.

  • Also return the packs that phygitals.com and the Phygitals app do not show. Accepts a real boolean from an RPC-style caller, or the literal string "true" from a query string; any other value is treated as false. Omitted returns only the packs phygitals.com shows.

    • Also return the packs that phygitals.com and the Phygitals app do not show. Accepts a real boolean from an RPC-style caller, or the literal string "true" from a query string; any other value is treated as false. Omitted returns only the packs phygitals.com shows.

Responses

  • application/json
  • application/json
  • application/json
Request Example for get/vm/available
curl 'https://api.phygitals.com/api/vm/available?platform=mainnet&includeRepacks=true&excludeVariants=true&return_non_1p=true'
[
  {
    "id": "AaB9kQ7pXt2vNf4dLmH8sYcR3wZ1eJ6uK5gP0oT2iVn",
    "slug": "topps-chrome-2024",
    "platform": "mainnet",
    "type": "CORE",
    "name": "Topps Chrome 2024",
    "enable": true,
    "description": "Chrome refractors, rookie autos, and numbered parallels.",
    "mint_price": "9.99",
    "max_per_mint": 10,
    "in_stock": true,
    "num_pulls_7d": 128,
    "chase": [],
    "rarity_distribution": [
      {
        "id": 0,
        "name": "Common",
        "color": "#9ca3af",
        "lower": 0,
        "upper": 10,
        "weight": 70
      },
      {
        "id": 1,
        "name": "Rare",
        "color": "#3b82f6",
        "lower": 10,
        "upper": 100,
        "weight": 25
      },
      {
        "id": 2,
        "name": "Chase",
        "color": "#f59e0b",
        "lower": 100,
        "upper": 1000,
        "weight": 5
      }
    ],
    "ev": 8.42,
    "ev_updated_at": "2026-01-02T03:04:05.678Z",
    "last_pull": null,
    "category": "cards",
    "categories": [
      "cards",
      "sports"
    ],
    "min_ev": 1.5,
    "max_ev": 250,
    "buyback_percent": 0.85,
    "repack": false,
    "claw_image_url": "https://cdn.phygitals.com/packs/topps-chrome-2024.png",
    "creator_profile": {
      "id": "did:privy:phygitals",
      "username": "phygitals",
      "profile_picture": null
    },
    "rewards_amounts": [],
    "sellback_rewards_amounts": [],
    "rewards_mint_addresses": [],
    "rewards_symbols": [],
    "rewards_decimals": [],
    "pulls_per_voucher": 0,
    "variant_of": null
  }
]

List a pack's chase cards

​

High-value chase cards for the pack identified by slug: the top cards by fair-market value in the pack's own rarity tier, so a buyer can preview what they could pull before purchasing. Public catalog data: no credential required. An unknown, mint-less, or tier-less slug returns 200 with an empty array rather than a 404, so an empty response means the pack has no chase cards to preview.

Path Parameters

  • The pack's URL slug: the identifier used across the pack catalog (e.g. topps-chrome-2024).

Responses

  • application/json
  • application/json
  • application/json
Request Example for get/vm/chase/{slug}
curl https://api.phygitals.com/api/vm/chase/topps-chrome-2024
[
  {
    "id": "8674311907",
    "name": "Juan Soto RC Auto /25",
    "image": "https://cdn.phygitals.com/cards/soto-rc-auto.png",
    "fmv": 250
  }
]

Pack buys

​

Pack (claw) buy flow: settle a purchase with a signed on-chain payment, then poll the session for fulfillment.

Settle a pack buy

​

Completes a pack purchase and returns the drawn items. The server verifies the payment, runs the provably-fair draw, delivers the cards on-chain, and answers with the pulled items, so a 200 here means the buy is settled, not merely accepted.

Scope required: vm.buy.crypto. Send a phy_ user API key as X-API-Key or Authorization: Bearer phy_…. Authenticated traffic is rate-limited (429 when exceeded).

Before you call

  1. An API key with the vm.buy.crypto scope.
  2. A linked external Solana wallet holding enough USDC or USDT.
  3. The pack's details from GET /api/vm/available: you need id (send it as claw_id), mint_price to compute the payment amount, and max_per_mint to cap amount. A pack whose in_stock is false is sold out.

Payment methods

The body is one of four shapes, picked by payment_method and, for a crypto buy, by chain. Fields belonging to another shape are ignored.

payment_method Pays with Also required
crypto on solana (the default) A transfer the buyer signed, in txs currency; legs when splitting across two stablecoins
crypto on an EVM chain An ERC-20 permit_data the buyer signed Nothing else
credits 100% Pack Credits idempotency_key; not available with an API key (403)
mixed Pack Credits plus an on-chain remainder idempotency_key, credit_portion_bps, txs, legs; the same

A voucher_id replaces the payment entirely: the on-chain payment is omitted and the buy is one pull.

Producing txs (Solana)

API-key callers build and sign the payment client-side (there is no phy_-callable builder that returns a ready transaction). Submit the signed bytes in txs as a base64 string or a byte array.

  1. Resolve the current signer pubkeys from POST /api/orpc/config/signers/pubkeys: you need vmBuyback (owner of the payment-receiver ATA), and solanaFeePayer if you want Phygitals to pay the network fee.
  2. Payment amount = amount × mint_price × 1_000_000 in the chosen stablecoin's base units (6 decimals; round to an integer, so 2 pulls at $25.00 is 50_000_000). Transfer that from the buyer ATA to the vmBuyback ATA for that mint.
  3. Compile a versioned (v0) transaction, then have the buyer wallet sign. Optional compute-budget and memo instructions may wrap the transfer; a mismatched amount, currency, receiver, fee payer, or stale blockhash fails verification.

Who pays the network fee

The fee payer is payerKey / account index 0, and you choose between three wallets:

Fee payer at index 0 Effect
solanaFeePayer Phygitals pays the fee and co-signs index 0. This is the default.
buyer_wallet The buyer pays their own fee. Sign index 0 with the buyer's key, or the transaction is rejected.
An allowed sponsor The sponsor pays the fee. Have the sponsor sign index 0 before you submit, or the transaction is rejected.

The allowed sponsors:

Sponsor Address
Jupiter's fee payer gaSFqrpdRkuezvsS4ppR7EPHKugdBopnJCg8hzMPYKP

Any other wallet at index 0 is rejected: Phygitals will not co-sign for a fee payer it does not hold, and the buyer's signature does not cover a third party's slot. Build the rest of the transaction identically in every case: verification derives what it expects from the fee payer you chose, so the shapes are equally valid but not interchangeable within one request.

When using legs, those legs must describe the same transfers as the signed transactions.

The server rebuilds the expected payment from buyer_wallet, claw_id, amount, currency, legs, and the voucher flag, and rejects a mismatch with 400 ("Transaction fingerprint mismatch"). Nothing is charged. Rebuild with a fresh blockhash and re-sign when that happens.

After a purchase

The response carries the settled result directly. If you need to re-read it, or a client timeout left you unsure, poll POST /api/vm/buy/status with the returned result.session_id, waiting briefly between attempts. Record a pull only when that poll returns { result }; see that endpoint's docs for the in-flight status values (there is no "pending" value).

Do NOT retry this endpoint to recover: a retry re-sends a transaction whose blockhash is stale and no longer matches the request, and on credits / mixed the idempotency_key is what makes a replay safe.

Errors

Status Example Cause
400 { "error": "Transaction fingerprint mismatch: …" } The signed transaction is not the payment the server expected
400 { "error": "Unsupported currency: …" } No currency was sent
400 { "error": "…" } Pack out of stock, voucher not claimable, insufficient credits, or another business-rule rejection
401 { "message": "Invalid or missing credentials" } Missing, invalid, revoked, or out-of-scope key
403 { "message": "Pack Credits purchases require a user session, not an API key" } credits or mixed attempted with an API key
422 { "message": "amount must be a positive integer" } The body failed validation; the message names the field
429 { "message": "Rate limit exceeded" } Rate limit exceeded for this key
502 { "message": "Pack credits purchase failed" } A server-side fault on a Pack Credits path

Body

required
application/json
    • Settle a pack buy with a signed Solana payment transaction.

      • How many pulls to settle. Must not exceed the pack's max_per_mint.

      • Pack identifier, as returned by GET /api/vm/available.

      • API-key callers only: settle on behalf of this wallet's owner rather than the key's owner. Must be the wallet that signed the payment transaction. Vouchers still follow the authenticated caller.

      • Payment token: usdc or usdt.

      • Caller-supplied metadata stored with the purchase session.

        Properties: 1
      • Split tender: one or two stablecoin legs that together fund the payment. Must describe the same transfers as the signed transactions in txs.

        Properties: 2
      • The buyer-signed payment transaction(s). Fee payer (account index 0) is either solanaFeePayer, which Phygitals co-signs, or the buyer's own wallet, which the buyer must sign. Absent for a voucher redemption, which pays nothing on chain.

      • A PURCHASE voucher's id or public code to spend on this pull. On Solana the payment transfer is replaced with a noop instruction.

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/vm/buy/crypto
curl https://api.phygitals.com/api/vm/buy/crypto \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "payment_method": "crypto",
  "claw_id": "BSG6DyEihFFtfvxtL9mKYsvTwiZXB1rq5gARMTJC2xAM",
  "amount": 1,
  "voucher_id": "",
  "extraData": {
    "additionalProperty": ""
  },
  "chain": "solana",
  "currency": "usdc",
  "txs": [
    ""
  ],
  "legs": [
    {
      "currency": "usdc",
      "units": 4500000
    }
  ],
  "buyer_wallet": "BSG6DyEihFFtfvxtL9mKYsvTwiZXB1rq5gARMTJC2xAM"
}'
{
  "result": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_id": "did:privy:abc123",
    "public_id": "_a1b2c3d4",
    "total": 25,
    "tx_hash": "5Kn8...bundle_signature",
    "nfts": [
      {
        "id": "9XnY6bTkPu3mRc8vJfLdQa2ZwEoN7sH1yG4iVxB5tCrM",
        "buyback_price": 21.25,
        "type": "ebay"
      }
    ]
  }
}

Poll pack-buy fulfillment

​

Polls the status of a purchase session created by POST /api/vm/buy/crypto. Authenticate with a phy_ user API key holding the vm.buy.crypto scope (X-API-Key or Authorization: Bearer phy_…).

Outcomes

Outcome Response Action
Fulfilled { "result": { session_id, user_id, buyer_wallet, public_id, nfts, tx_hash? } } Stop; record the pull once
Failed HTTP 400 "Transaction failed" Stop; do not record
Still in flight { "status": … } Keep polling, do not record yet

There is no wire value "pending". Treating anything other than a result payload as complete will double-count when fulfillment lands.

status values (in-flight arm)

Value Meaning Action
null / absent Session open; nothing settled yet Keep polling
processing Purchase still in progress Keep polling
debited Credits applied; delivery not yet confirmed Keep polling
review Parked for reconciliation (may have broadcast; not auto-refunded) Keep polling
success Terminal; delivered Normally returned as { "result": … } instead
failed Terminal; did not complete Normally returned as HTTP 400 instead

Any unrecognized status should be treated as still in flight.

result.buyer_wallet

On a fulfilled response, buyer_wallet is the wallet that paid and received the pull. When POST /api/vm/buy/crypto was called with a buyer_wallet override, this field echoes that override; otherwise it is the caller's linked wallet.

Polling

Retry with a short delay between attempts until result arrives or a 400 is returned. Safe completion rule: record only when result is present.

Body

required
application/json
  • Session ID from the buy/crypto response (result.session_id).

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/vm/buy/status
curl https://api.phygitals.com/api/vm/buy/status \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "session_id": "550e8400-e29b-41d4-a716-446655440000"
}'
{
  "result": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_id": "did:privy:abc123",
    "buyer_wallet": "7Z3TvJrbjTFn9Vd8SzHfPu9pQpEdxKzsEwUqcXNoWaSF",
    "public_id": "_a1b2c3d4",
    "tx_hash": "5Kn8...bundle_signature",
    "nfts": [
      {
        "id": "9XnY...mint_address",
        "type": "enft",
        "buyback_price": 21.25,
        "mint_address": "9XnY...mint_address",
        "content": {
          "metadata": {
            "name": "2024 Juan Soto RC Auto /25",
            "image": "https://cdn.phygitals.com/cards/example.png",
            "back_image": null,
            "attributes": [
              {
                "trait_type": "Category",
                "value": "Baseball"
              }
            ]
          },
          "links": {
            "image": "https://cdn.phygitals.com/cards/example.png"
          }
        }
      }
    ]
  }
}

Marketplace

​

Public catalog reads (the metadata filter facets that populate the trait pickers on the listing search), plus claw buyback: sell a pulled card back into the pool at its standing offer, in two steps (build the sellback transaction for optional owner_wallet or the caller's wallets, then settle).

Get marketplace filters

​

Returns the allowed metadata facet values for the Phygitals marketplace catalog: a map of trait name → allowed-value options, restricted to the public whitelist (Type, Rarity, Set, Grader, Grade, Set Release Date, Language). Use it to populate filter UI. Public catalog data: no credential required.

Responses

  • application/json
  • application/json
  • application/json
Request Example for get/marketplace/filters
curl https://api.phygitals.com/api/marketplace/filters
{
  "filters": {
    "metadata": {
      "Grader": [
        {
          "value": "PSA",
          "count": 1842
        },
        {
          "value": "BGS",
          "count": 311
        }
      ],
      "Grade": [
        {
          "value": "10",
          "count": 906
        },
        {
          "value": "9.5",
          "count": 274
        }
      ],
      "Language": [
        {
          "value": "English",
          "count": 2103
        },
        {
          "value": "Japanese",
          "count": 588
        }
      ]
    },
    "priceRanges": []
  }
}

Start a claw buyback (build the sellback transaction)

​

Prices a standing buyback offer on a claw-pulled card and returns the transaction(s) that swap it for USDC, plus the session id they are pinned to. Pass optional owner_wallet to open a sellback for that holder to sign; omit it to use the caller's own wallets. Exactly one offer must match, or the request is rejected. Optional client_id is stored on the session and echoed unchanged by take-claw-bid-finish so you can correlate the two steps. Nothing settles here: sign the transactions with the seller's wallet and post them to take-claw-bid-finish. API-key callers need the marketplace.take-claw-bid scope; exceeding the rate limit returns 429.

Who pays the network fee

fee_payer picks the wallet in the transaction's fee-payer slot (account index 0):

fee_payer Effect
platform (default) Phygitals pays the fee and the payout-ATA rent, and signs that slot during finish.
user The seller wallet pays both. Their signature covers the fee-payer slot as well as the transfer they authorize.
external The sponsor named in fee_payer_address pays both. Have the sponsor sign that slot before you post the transaction to finish.

fee_payer_address is required with external and rejected with any other mode. Only allowed sponsors are accepted; any other address is a 400. The allowed sponsors:

Sponsor fee_payer_address
Jupiter's fee payer gaSFqrpdRkuezvsS4ppR7EPHKugdBopnJCg8hzMPYKP

The choice is fixed when the session is opened, because the fee payer also funds the seller's USDC account when they do not have one yet, which makes it part of the transaction take-claw-bid-finish matches the session against. Ask for user or external at init or not at all; you cannot switch at finish, and a transaction built the other way is rejected there as a fingerprint mismatch.

Body·TakeClawBidInitInput

required
application/json
  • Mint address of the card being sold back. Must resolve to EXACTLY ONE standing buyback offer for owner_wallet (when set) or one of the caller's active wallets, or the request is rejected.

  • Optional opaque id of your choosing. Stored with the sellback session and returned unchanged on take-claw-bid-finish (including an idempotent replay), so you can correlate finish with the call that opened the session.

  • Who pays the network fee and any payout-ATA rent on the returned transactions. platform (the default) puts the Phygitals fee payer in that slot and signs it at finish; user puts the seller wallet there instead, so the seller's own signature covers the fee; external puts fee_payer_address there — a third-party sponsor from the allowed list, which must sign that slot before you post the transaction to finish.

  • The sponsoring wallet for fee_payer: "external"; required with it and rejected without it. Only allowed sponsors are accepted (currently Jupiter's fee payer, gaSFqrpdRkuezvsS4ppR7EPHKugdBopnJCg8hzMPYKP); any other address is a 400.

  • Holder wallet that owns the card and will co-sign the returned transactions. When set, the offer is priced for this wallet so an integrator key can open a sellback their end user then signs. Omit to use the caller's own wallets.

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/marketplace/transaction/take-claw-bid-init
curl https://api.phygitals.com/api/marketplace/transaction/take-claw-bid-init \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "mint_address": "8sN9dCLMzmvJvDCK1YbQGoDPbjLLxWLCVDDcU8Xt4ZAp"
}'
{
  "session_id": "019627f4-1f2c-7b3e-9a11-4c5d6e7f8091",
  "txV0s": [
    "gAEAAQ...base64-versioned-transaction"
  ]
}

Finish a claw buyback (settle and pay out)

​

Settles the sellback opened by take-claw-bid-init and pays the seller. Solana callers post the session id plus the signed transactions; EVM callers (chain: animechain | arbitrum) post the card and a signed ERC-721 permit instead. Post back exactly the transactions init returned, signed by the seller's wallet. Phygitals adds the payout authority's signature, and the fee payer's when the session was opened with the default fee_payer: "platform". A fee_payer: "user" or fee_payer: "external" session gets no platform signature in that slot at all, so the seller's own (or the sponsor's) must cover it, and an unsigned fee-payer slot is refused here rather than dropped on-chain. A 200 with tx_status: "success" includes tx_hash (the Solana payout signature(s), or the EVM settlement hash). When init was called with a client_id, that value is echoed on every finish response for the session — including an idempotent replay. Safe to retry: a replayed call answers 409 while the sellback is in flight and 200 with the recorded status (and tx_hash when the prior finish succeeded) once it is terminal. API-key callers need the marketplace.take-claw-bid scope; exceeding the rate limit returns 429.

Body·TakeClawBidFinishInput

required
application/json
  • Settlement chain. animechain / arbitrum select the EVM path (mint_address + nft_permit); anything else, including omitting it, selects the Solana path (session_id + txs).

  • EVM path: the card being sold back. Required there, ignored on Solana.

  • EVM path: the seller's signed ERC-721 permit authorizing the swap. Required there.

    Properties: 4
  • Solana path: the session id take-claw-bid-init returned. Required there, ignored on EVM.

  • Integrations should omit this field. Omitted or true accrues no rewards; do not rely on false.

  • Solana path: the init transactions, now co-signed by the seller's wallet. Must be the same transactions returned by init (non-empty, at most 4).

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/marketplace/transaction/take-claw-bid-finish
curl https://api.phygitals.com/api/marketplace/transaction/take-claw-bid-finish \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "session_id": "019627f4-1f2c-7b3e-9a11-4c5d6e7f8091",
  "txs": [
    [
      128,
      1,
      0,
      1
    ]
  ],
  "skip_rewards": true
}'
{
  "tx_status": "success",
  "tx_hash": "5Kn8TvJrbjTFn9Vd8SzHfPu9pQpEdxKzsEwUqcXNoWaSF",
  "referral_credit": {
    "referrer_username": "collector",
    "amount_usd": 0.42
  },
  "client_id": "order-42"
}

Vault

​

Claim a holder's vaulted cards for physical delivery, then follow the order to their door.

Lifecycle

1  POST /vault/claim/price-estimate ──► quote_id                (valid 15 min)

2  POST /vault/claim-init            ◄── claim proof             (memo tx, 5 min)
                                     ──► order_id

3  POST /vault/claim-request-txs
      │
      ├─ "transactions" ──► holder signs each ──► 4  POST /vault/claim-finish-tx ──┐
      │                                                                             │
      │◄─────────────────────────── while legs remain ◄─────────────────────────────┘
      │
      └─ "finalized"     ──► status: processing

5  POST /vault/order                 poll until delivered

Keep calling step 3 until it answers "finalized". Finalizing writes tracking and advances the order.

Claim proof

claim-init takes wallet_proof: a transaction the holder signed and submitted, whose fee payer is owner_wallet and which carries exactly one SPL Memo instruction reading:

phygitals:claim:<key id>:<quote_id>:<timestamp>
  • key id — the id of the API key you call with. Phygitals gives it to you with the key. Safe to publish.
  • timestamp — Unix seconds.

After confirmation, send { tx_signature, timestamp }. The memo must match exactly, and the transaction must be at most 5 minutes old. No other endpoint takes a proof: the order reads cover only orders booked through your own account.

Order status

Status Meaning
transaction_init Booked, nothing settled. Hidden from the list; visible by id.
partially_submitted Some legs settled, not all. Resume at step 3.
processing Every leg settled. With fulfillment.
transit With the carrier.
tracking_available Tracking number on the order. Shipped.
delivered Arrived.
failed Settlement failed. error_message says why.
cancelled Terminal.

partially_submitted never advances on its own. Each item also reports received, pending or failed, so you can see which cards are outstanding.

When a transaction fails

success: false from claim-finish-tx means the transaction confirmed with an error. Call claim-request-txs again for a fresh one. A leg rebuilds up to 3 times; after that, send retry: true. Settled legs are never rebuilt. Never resubmit the same bytes: the blockhash is stale.

Quote a claim

​

Prices the shipment of a set of vaulted cards to one address and returns the offered rates. Each rate's id is the quote_id every later step takes. Nothing is booked or charged.

The items and the address are bound to the quote. To change either, request a new quote. A quote is valid for 15 minutes; after that it is honored only if a re-price returns the same total.

API-key callers pass owner_wallet, the on-curve Solana wallet holding the cards. No proof is needed here; ownership is checked at claim-init. Requires the vault.claim scope; exceeding the rate limit returns 429.

Body

required
application/json
  • Properties: 3
  • Properties: 15
  • API-key callers only: quote, book and settle the claim for this wallet's owner rather than the key's owner. Must be the on-curve wallet that holds the cards and signs the settlement transactions. Rejected for signed-in web sessions.

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/vault/claim/price-estimate
curl https://api.phygitals.com/api/vault/claim/price-estimate \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "items": [
    {
      "slug": "",
      "type": "",
      "address": ""
    }
  ],
  "shippingDetails": {
    "name": "",
    "email": "",
    "line_1": "",
    "line_2": "",
    "city": "",
    "region": "",
    "postal_code": "",
    "country": "",
    "phoneNumber": "",
    "firstName": "",
    "lastName": "",
    "street": "",
    "aptUnit": "",
    "state": "",
    "zipCode": ""
  },
  "owner_wallet": ""
}'
{
  "session_id": "string",
  "rates": [
    {
      "id": "0194f0a5-3f60-7d4b-ae5f-8c1b7a4d9f23_rate_a1b2c3d4-e5f6-4789-abcd-ef1234567890",
      "carrier": "Phygitals",
      "service": "Graded Vault Fulfillment",
      "withdrawal_fees": 0,
      "shipping_cost": 20,
      "total_cost": 20,
      "true_cost": 20,
      "insured": true,
      "estimated_delivery": "3-5 business days",
      "notes": "Alt vault domestic shipping: $20 flat fee per order (1 card). All prices in USD.",
      "estimated_days_min": 3,
      "estimated_days_max": 5
    }
  ]
}

Book a claim

​

Consumes the quote_id and books the order: one settlement leg for the shipping fee plus one per card, all pending. The order id is the quote's session id. Re-submitting a quote whose order already booked returns the same order id. Nothing moves on-chain here.

The request is refused, and the quote stays reusable, if the wallet lacks USDC for the quoted total_cost, if a card has moved, been frozen or been escrowed since the quote, or if a claim is already in flight for one of the cards.

API-key callers

Send wallet_proof over the memo phygitals:claim:<key id>:<quote_id>:<timestamp>, valid for 5 minutes. See the Vault section for how to build it.

The fee settles in on-chain USDC only. Card and Pack Credits payment are not available on this path. Requires the vault.claim scope; exceeding the rate limit returns 429.

Body

required
application/json
  • API-key callers only: quote, book and settle the claim for this wallet's owner rather than the key's owner. Must be the on-curve wallet that holds the cards and signs the settlement transactions. Rejected for signed-in web sessions.

  • Required with owner_wallet. See the endpoint description for the memo to sign.

    Properties: 2

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/vault/claim-init
curl https://api.phygitals.com/api/vault/claim-init \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "quote_id": "",
  "owner_wallet": "",
  "wallet_proof": {
    "tx_signature": "",
    "timestamp": -9007199254740991
  }
}'
{
  "order_id": "string"
}

Get settlement transactions

​

Two modes, discriminated by mode.

transactions: legs remain. Returns the USDC fee transfer plus card transfers, two cards per transaction, at most 20 legs per call, each stamped with the fingerprint of the legs it settles. Sign each with the holder wallet and post it to claim-finish-tx. Phygitals pays the network fee.

finalized: every leg settled. Returns the order status, one entry per item, the bundle id and the fee paid. Call once more after the last claim-finish-tx to reach this state; it writes tracking and advances the order.

Set retry to true to rebuild legs that have failed three times.

API-key callers pass the same owner_wallet the order was booked for, from the key account that booked it. No proof is needed. Requires the vault.claim scope; exceeding the rate limit returns 429.

Body

required
application/json
  • API-key callers only: quote, book and settle the claim for this wallet's owner rather than the key's owner. Must be the on-curve wallet that holds the cards and signs the settlement transactions. Rejected for signed-in web sessions.

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/vault/claim-request-txs
curl https://api.phygitals.com/api/vault/claim-request-txs \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "order_id": "",
  "retry": true,
  "owner_wallet": ""
}'
{
  "mode": "transactions",
  "order_id": "string",
  "transactions": [
    {
      "type": "usdc",
      "tx": "string",
      "fingerprint": "string",
      "addresses": [
        "string"
      ]
    }
  ]
}

Submit a signed transaction

​

Takes one transaction from claim-request-txs, signed by the holder wallet, co-signs it as fee payer, broadcasts it, and records the outcome on the legs it settles.

success: true means confirmed. success: false means confirmed with an error; call claim-request-txs again for a fresh transaction. Post tx as the base64 wire transaction and echo type, fingerprint and addresses as received. Legs are selected by the fingerprint of the bytes you submit.

API-key callers pass the same owner_wallet the order was booked for, from the key account that booked it. No proof is needed. Requires the vault.claim scope; exceeding the rate limit returns 429.

Body

required
application/json
  • Properties: 4
  • API-key callers only: quote, book and settle the claim for this wallet's owner rather than the key's owner. Must be the on-curve wallet that holds the cards and signs the settlement transactions. Rejected for signed-in web sessions.

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/vault/claim-finish-tx
curl https://api.phygitals.com/api/vault/claim-finish-tx \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "order_id": "",
  "transaction": {
    "type": "usdc",
    "tx": "",
    "fingerprint": "",
    "addresses": [
      ""
    ]
  },
  "owner_wallet": ""
}'
{
  "success": true
}

Get an order

​

One order by the order_id that claim-init returned: status, items with per-card status and market value, tracking ids once shipped, and the destination. An id that does not exist, or is not yours, is 404.

API-key callers pass owner_wallet. No proof is needed: the read covers only orders booked through this key's account. The response carries the fields documented below; the internal ids and staff columns the Phygitals web app reads are not returned. Requires the vault.claim scope; exceeding the rate limit returns 429.

Body

required
application/json
  • API-key callers only: quote, book and settle the claim for this wallet's owner rather than the key's owner. Must be the on-curve wallet that holds the cards and signs the settlement transactions. Rejected for signed-in web sessions.

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/vault/order
curl https://api.phygitals.com/api/vault/order \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "order_id": null,
  "owner_wallet": ""
}'
{
  "order": {
    "order_id": "string",
    "created_at": "string",
    "status": "transaction_init",
    "order_type": "string",
    "address": "string",
    "email": "string",
    "phone": "string",
    "name": "string",
    "items": [
      {
        "address": "string",
        "name": "string",
        "image": "string",
        "back_image": "string",
        "fmv": 1,
        "certNumber": "string",
        "vault": "string",
        "slug": "string",
        "listing_id": "string",
        "title": "string",
        "grader": "psa",
        "pending_shipment": true
      }
    ],
    "tracking_id": "string",
    "tracking_ids": {
      "psa": "string",
      "cc": "string",
      "fanatics": "string",
      "alt": "string",
      "smc": "string",
      "ripfun": "string"
    },
    "vault_statuses": {
      "psa": "string",
      "fanatics": "string",
      "alt": "string",
      "smc": "string",
      "ripfun": "string"
    },
    "item_statuses": [
      {
        "type": "string",
        "status": "string",
        "address": "string"
      }
    ],
    "country": "string",
    "fees_paid": 1,
    "selected_pricing_estimate": {
      "id": "string",
      "withdrawal_fees": 1,
      "shipping_cost": 1,
      "total_cost": 1,
      "estimated_delivery": "string",
      "notes": "string",
      "name": "string",
      "carrier": "string",
      "service": "string",
      "estimated_days_min": 1,
      "estimated_days_max": 1,
      "rip_shipping_cost": 1,
      "rip_total_cost": 1
    }
  }
}

List orders

​

Orders in board order. Orders that were opened but never settled are excluded.

API-key callers get the orders booked for owner_wallet through this key's account. Orders placed on the Phygitals website, or through another integrator, are not included.

Pass owner_wallet. No proof is needed. Requires the vault.claim scope; exceeding the rate limit returns 429.

Body

application/json
  • API-key callers only: quote, book and settle the claim for this wallet's owner rather than the key's owner. Must be the on-curve wallet that holds the cards and signs the settlement transactions. Rejected for signed-in web sessions.

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for post/vault/orders
curl https://api.phygitals.com/api/vault/orders \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN' \
  --data '{
  "owner_wallet": ""
}'
{
  "orders": [
    {
      "order_id": "string",
      "created_at": "string",
      "status": "transaction_init",
      "order_type": "string",
      "address": "string",
      "email": "string",
      "phone": "string",
      "name": "string",
      "items": [
        {
          "address": "string",
          "name": "string",
          "image": "string",
          "back_image": "string",
          "fmv": 1,
          "certNumber": "string",
          "vault": "string",
          "slug": "string",
          "listing_id": "string",
          "title": "string",
          "grader": "psa",
          "pending_shipment": true
        }
      ],
      "tracking_id": "string",
      "tracking_ids": {
        "psa": "string",
        "cc": "string",
        "fanatics": "string",
        "alt": "string",
        "smc": "string",
        "ripfun": "string"
      },
      "vault_statuses": {
        "psa": "string",
        "fanatics": "string",
        "alt": "string",
        "smc": "string",
        "ripfun": "string"
      },
      "item_statuses": [
        {
          "type": "string",
          "status": "string",
          "address": "string"
        }
      ],
      "country": "string",
      "fees_paid": 1,
      "selected_pricing_estimate": {
        "id": "string",
        "withdrawal_fees": 1,
        "shipping_cost": 1,
        "total_cost": 1,
        "estimated_delivery": "string",
        "notes": "string",
        "name": "string",
        "carrier": "string",
        "service": "string",
        "estimated_days_min": 1,
        "estimated_days_max": 1,
        "rip_shipping_cost": 1,
        "rip_total_cost": 1
      }
    }
  ]
}

Vending machine

​

Buyback quote by card. GET /vm/offers/{mint_address_or_slug} returns the standing claw offer (same marketplace.take-claw-bid scope as the sellback).

Vending machine Operations

Get the buyback offer for one NFT

​

Returns the standing claw buyback bid for one card (by mint address or slug), or an empty offers array when none is eligible. API-key callers need the marketplace.take-claw-bid scope (same as the sellback); exceeding the rate limit returns 429.

Path Parameters

  • The NFT's on-chain address or its /card/<slug> segment.

Responses

  • application/json
  • application/json
  • application/json
  • application/json
  • application/json
Request Example for get/vm/offers/{mint_address_or_slug}
curl 'https://api.phygitals.com/api/vm/offers/{mint_address_or_slug}' \
  --header 'X-API-Key: YOUR_SECRET_TOKEN'
{
  "offers": [
    {
      "bid_state": "string",
      "owner": "string",
      "closer": "string",
      "seller": "string",
      "target_id": "string",
      "state": "string",
      "amount": 1,
      "expires_at": "string",
      "created_at": "2026-10-14T04:05:38.978Z",
      "updated_at": "2026-10-14T04:05:38.978Z"
    }
  ]
}