Docs API reference

API reference

Three routes, all JSON. Identify a photo, resolve a catalog object, or read live quotes.

Three routes. All answer JSON, all set the usual error shape on failure.

There is no auth and no rate limit of Photoswap's own. The identify route is bounded by the AI Gateway and the registry's burst limit; the assets route is cached at the edge for twenty seconds. Base URL is the site itself.

POST /api/identify

Identify the object in a photo and return its resolved basket. Body is JSON with one field, image, a base64 data URL of an image (data:image/jpeg;base64,…). Keep it under 8 MB; the app sends 1280px JPEGs. Runs on Node with a 60 second budget.

IMG=$(base64 -i photo.jpg | tr -d '\n')
curl -s https://photoswap.family/api/identify \
  -H 'content-type: application/json' \
  -d "{\"image\":\"data:image/jpeg;base64,$IMG\"}"

Response

{
  "basket": {
    "id": "3f9c2a1d7e",
    "object": {
      "name": "iPhone 17 Pro",
      "category": "Smartphone",
      "brand": "Apple",
      "confidence": 0.96,
      "description": "An Apple-designed A19 Pro fabbed by TSMC, Korean memory, a Qualcomm modem and Broadcom radios."
    },
    "companies": [
      { "ticker": "AAPL", "name": "Apple",     "role": "Design",   "weight": 0.46, "address": "0x…", "logoUrl": "https://…" },
      { "ticker": "TSM",  "name": "TSMC",      "role": "Silicon",  "weight": 0.18, "address": "0x…", "logoUrl": "https://…" },
      { "ticker": "QCOM", "name": "Qualcomm",  "role": "Modem",    "weight": 0.10, "address": "0x…", "logoUrl": "https://…" },
      { "ticker": "SKHY", "name": "SK Hynix",  "role": "Memory",   "weight": 0.10, "address": "0x…", "logoUrl": "https://…" },
      { "ticker": "AVGO", "name": "Broadcom",  "role": "Wireless", "weight": 0.08, "address": "0x…", "logoUrl": "https://…" },
      { "ticker": "GLW",  "name": "Corning",   "role": "Glass",    "weight": 0.08, "address": "0x…", "logoUrl": "https://…" }
    ],
    "unlisted": [
      { "name": "Foxconn", "role": "Assembly", "reason": "Listed only in Taiwan" },
      { "name": "Sony",    "role": "Camera sensors", "reason": "Not tokenized on Robinhood Chain" }
    ],
    "source": "ai",
    "createdAt": "2026-09-04T14:02:11.318Z"
  }
}

GET /api/identify?slug=

Return a catalog object's basket, resolved against the live registry. Same response shape as the POST, with source: "catalog" and id equal to the slug. Use it to preload the scanner or to build on the fourteen curated baskets without running a model.

curl -s 'https://photoswap.family/api/identify?slug=iphone-17-pro'

GET /api/assets

Every live tokenized stock on chain 4663 keyed by ticker, plus the ETH price in USD. ethUsd comes from a 1 ETH → USDG quote on Uniswap v4, then v3, then CoinGecko, whichever answers first, cached 60 seconds. The response carries Cache-Control: s-maxage=20, stale-while-revalidate=60.

curl -s https://photoswap.family/api/assets | jq '.quotes.AAPL, .ethUsd'

Response

{
  "quotes": {
    "AAPL": {
      "ticker": "AAPL",
      "name": "Apple",
      "address": "0x…",
      "decimals": 18,
      "logoUrl": "https://…",
      "bid": 231.12,
      "ask": 231.30,
      "mid": 231.21,
      "halted": false,
      "multiplier": "1000566080061092436"
    },
    "NVDA": { "…": "…" }
  },
  "ethUsd": 4210.55,
  "updatedAt": "2026-09-04T14:02:11.318Z"
}

Shapes

These are the types in lib/types.ts. Field names in responses match them exactly.

interface Basket {
  id: string;                          // catalog slug, or a 10-char hash for AI results
  object: { name; category; brand?; confidence; description };
  companies: Company[];                // listed, weights sum to 1, sorted desc
  unlisted: { name; role; reason }[];  // shown greyed out
  source: "catalog" | "ai";
  createdAt: string;                   // ISO
}
interface Company { ticker; name; role; weight; address?: `0x${string}`; logoUrl? }

interface TokenQuote {
  ticker; name; address; decimals; logoUrl?;
  bid; ask; mid; halted: boolean;
  multiplier: string;                  // ERC-8056 ui multiplier, 18-dec fixed. 1e18 == 1
}

Errors

Every failure is { "error": string } with a 4xx or 5xx status. The message is meant to be shown to a person as is.

RouteStatusWhen
POST /api/identify400Body not JSON, or image is not a data URL
POST /api/identify413Image over 8 MB
POST /api/identify422Not an object, or no maker trades on Robinhood Chain
POST /api/identify502All models failed
GET /api/identify400 / 404Missing slug / unknown slug
GET /api/identify502Registry unavailable
GET /api/assets502Registry or ETH price unavailable

Stale beats broken

Registry reads keep the last good value and serve it if a refresh fails. A 502 from these routes means there has never been a good value since the server started.