Scan as a job
Start a scan, get a job id in about a second, and read the result whenever you like — from any machine with your key.
Start it, then check on it
# start: answers 202 with a job id
curl -X POST https://spoken.fyi/api/v1/scans \
-H "Authorization: Bearer $SPOKEN_KEY" \
-H "Content-Type: application/json" \
-d '{ "photos": ["https://your-cdn.example/bike.jpg"], "external_ref": "listing-9921" }'
# check: free, as often as every 2 seconds
curl https://spoken.fyi/api/v1/scans/$JOB_ID \
-H "Authorization: Bearer $SPOKEN_KEY"
# → { "status": "running", "progress": 40 }
# → { "status": "done", "progress": 100, "result": { "parts": [ … ], "build_id": "…" }, "credits_charged": 100 }The scan keeps running after you disconnect. Checking a job is free. A job that fails charges nothing, and external_ref — your own id for the photo — comes back on the result.
From a parts list
When you already know what is in the setup — a quote, an invoice, a spreadsheet — read it into rows with POST /api/v1/lists/parse (free: pasted text, or a CSV, XLSX, PDF or TXT file as base64) and send the rows as parts with the photos. The list is then the truth: every row becomes a part of the build, a row the scan also finds in the photo takes its place there, a row it does not see stays on the build without a pin, and a part the scan finds that is not on the list is dropped. Each part answers provided and placed, and the result carries list with how many were placed. Up to 60 rows; billed like any scan.
curl -X POST https://spoken.fyi/api/v1/lists/parse \
-H "Authorization: Bearer $SPOKEN_KEY" -H "Content-Type: application/json" \
-d '{ "text": "Cannondale SuperSix EVO LAB71 frameset 56cm\nZipp 353 NSW wheelset\nSRAM Red eTap AXS rear derailleur" }'
# → { "parts": [ { "category": "frame", "brand": "Cannondale", "model": "SuperSix EVO LAB71", "spec": "56cm" }, … ], "rows": 3 }
curl -X POST https://spoken.fyi/api/v1/scans \
-H "Authorization: Bearer $SPOKEN_KEY" -H "Content-Type: application/json" \
-d '{ "photos": ["https://your-cdn.example/bike.jpg"], "parts": [ { "brand": "Cannondale", "model": "SuperSix EVO LAB71 frameset" }, … ] }'Keep nothing on our side
Add "save": false and the parts come back under the job id with nothing saved to spoken.fyi: no build page, no posters, and build_id is null. The record lives only in your system. Leave it out and the job saves a build exactly like /scan does.
Retries are safe
Send an Idempotency-Key header when you start a job. If the request is retried with the same key, you get the original job back instead of paying for a second scan.