Docs API reference
API reference
Three routes, all JSON. Identify a photo, resolve a catalog object, or read live quotes.
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\"}"const res = await fetch("/api/identify", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ image: dataUrl }),
});
const { basket, error } = await res.json();
if (!res.ok) throw new Error(error);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'const res = await fetch("/api/identify?slug=iphone-17-pro");
const { basket } = await res.json();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'const { quotes, ethUsd, updatedAt } = await fetch("/api/assets").then((r) => r.json());
const aapl = quotes["AAPL"];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.
| Route | Status | When |
|---|---|---|
| POST /api/identify | 400 | Body not JSON, or image is not a data URL |
| POST /api/identify | 413 | Image over 8 MB |
| POST /api/identify | 422 | Not an object, or no maker trades on Robinhood Chain |
| POST /api/identify | 502 | All models failed |
| GET /api/identify | 400 / 404 | Missing slug / unknown slug |
| GET /api/identify | 502 | Registry unavailable |
| GET /api/assets | 502 | Registry or ETH price unavailable |
Stale beats broken
