Skip to main content
The sandbox is a read-only mirror of the production Partner API. Use it to integrate, run end-to-end tests, and demo the full purchase → inventory → sellback → shipping flow without consuming real inventory, settling real money, or triggering on-chain transactions.

Base URLs

EnvironmentBase URL
Productionhttps://api.phygitals.com
Sandboxhttps://api.phygitals.com/_
All endpoints are versioned under /api/v1/. The sandbox mirrors every production route under /_/api/v1/. For example:
ProductionSandbox
GET /api/v1/vm/availableGET /_/api/v1/vm/available
POST /api/v1/vm/buy/initPOST /_/api/v1/vm/buy/init
POST /api/v1/vm/buybackPOST /_/api/v1/vm/buyback

API keys

Two key tiers are issued by Phygitals:

Production keys

Identify your partner account and authorize live purchases, sellbacks, and shipments. Contact hello@phygitals.com to request one.

Sandbox keys

Prefixed with sandbox-. Authorize the sandbox environment at /_/api/v1/*. Do not identify a partner. Never produce real money movement, on-chain transactions, or shipments. Use them to integrate without consuming inventory.
A 401 { "error": "Invalid API key" } is returned for missing, empty, or unrecognized X-API-Key headers on any endpoint, in any environment.

How sandbox differs from production

Read this before integrating. Every behavior below is sandbox-only and must not be relied on in production code paths.

State is in-memory and ephemeral

Sandbox state (purchase sessions, simulated inventory, sellbacks, shipping orders) lives in the API server’s memory and is wiped on restart or deploy. Multi-instance deployments do not share state. Treat every sandbox session as fresh.

No real fulfillment

The sandbox never touches Alt, PSA, or Fanatics. There are no on-chain transactions, no payment processing, no carrier API calls, and no actual shipping label creation. Every fulfillment is simulated.

No row locking on purchases

The same underlying eBay listing can be returned to multiple users from concurrent buy/init calls. The sandbox does not reserve inventory. Don’t use sandbox to test “two users racing for the same item” scenarios. That’s a production-only behavior.

Synchronous fulfillment

POST /vm/buy/init returns the pulled items immediately. As a result:
  • POST /vm/buy/status never returns the { "status": "pending" } envelope in sandbox. It returns either the fulfilled { result: ... } payload or 400 { "error": "Transaction failed" }.
  • Production keeps the polling envelope as-is, so build your client to handle pending. Sandbox just won’t exercise that branch.

No idempotency

Calling POST /vm/buy/init twice with identical bodies produces two distinct session_ids and stacks the items in inventory. Don’t rely on idempotency keys in sandbox.

No rate limiting

The sandbox never returns 429. Rate limits exist in production but are not enforced here.

Frozen pricing on sellback

The amount returned from POST /vm/buyback equals the buyback_price assigned at buy/init time. Prices are frozen and there is no Alt oracle re-fetch. Production re-prices live at sellback time.

No vouchers

Voucher / promo logic on sellbacks is production-only.

Address validation is real; country allow-list is stubbed

  • destination is validated end-to-end through the Google Address Validation API. GOOGLE_MAPS_API_KEY must be configured server-side for /ship/quote to succeed. When the key is missing, or when Google returns a non-2xx, every quote 400s with { "error": "Invalid destination address", "details": "Validation failed: internal error" } — the validator fails closed rather than letting bad addresses through. Successful validation returns { "error": "Invalid destination address", "details": ..., "suggested": ... } for invalid input.
  • country is also checked separately for length (must be 2 chars). Any 2-character string is accepted, including "ZZ". Production validates against an actual carrier-supported allow-list.

Shipping orders never advance

A successful ship/request in sandbox creates an order with status: "queued" and stays queued. tracking_number, tracking_url, shipped_at, and delivered_at remain null for the lifetime of the sandbox process. Production cycles through queued → processing → label_created → shipped → delivered.

Sandbox is scoped by user_id only

Partner identity is not derived from the API key in sandbox. All sandbox state is isolated by the user_id you submit. In production, scoping is partner-aware.

Production-only features

Don’t integrate against these in sandbox. They’re stubbed or absent and behavior will diverge:
  • 429 rate-limit responses on /vm/buyback (no concurrency control in sandbox).
  • The { status: "pending" } polling envelope on /vm/buy/status.
  • The { order_id: null, status: "error", error_message } envelope on /ship/request.
  • Webhook callbacks for shipping status transitions or fulfillment events.
  • Voucher / promo logic on sellbacks.
  • Real Alt pricing-oracle FMV refresh on sellback.
  • Real country allow-list for shipping.
  • Real inventory reservation. The same listing can be pulled by multiple users.
  • Cross-process / cross-deploy state persistence.

Error reference

Every error string the sandbox can emit, exactly as returned. Match against these verbatim if you’re building error-handling logic.
Invalid API key
user_id is required
VM not found
Amount must be greater than 0
Amount must be less than or equal to N           (N = pack.max_per_mint)
Claw machine is out of stock
Error during transaction
Transaction failed
User not found
Card not found
item_id is required
Item not found
Item has expired
item_ids is required
Invalid destination address
Country not supported
quote_id is required
Quote expired
Item already shipped
Failed to fetch shipping rates
Failed to create shipping request
Order not found
An unexpected error occurred
Production may emit a superset of these strings. Treat any message not in this list as production-only.