Identify from a file upload

POST /api/v1/vision/identify read:catalog 5 credits

The same endpoint and the same matcher as the URL variant, but you post the bytes as `multipart/form-data`: the photo in a part named `image`, the options as ordinary form fields. Use this from a scanner or a point-of-sale where the photo never needs to leave your system until you send it to us.

This page’s runner sends the JSON `image_url` form of the same request (the docs proxy is JSON-only); the response is identical. The curl form for a file is `curl -X POST https://api.getcardos.com/api/v1/vision/identify -H 'X-API-Key: rip_…' -F image=@card.jpg -F limit=3`.

Send one photo of one card. Fill the frame with the card, keep the collector number legible (it is what settles reprints and parallels), and avoid strong glare. Anything from about 600 px on the long edge works; the server downscales to ~1600 px, so a larger upload only costs transfer time. JPEG, PNG or WebP, up to 10 MB.

Costs 5 credits per identification, refunded on any error — including a 503 timeout. A no-match is a normal 200 with `match: null`, and is billed: the work was done. 60 identifications/minute per key, plus a small per-server concurrency gate that sheds as 429 with `Retry-After: 5`. Typical latency is 1–4 s for a raw card and longer for a graded slab, whose label is read and whose certificate is looked up.

Close the loop: when your user keeps a candidate — or corrects to another — call the confirm endpoint with `feedback_id`. Confirmations train the matcher on your photos, which is how accuracy improves for your capture conditions, and they are free.

Graded slabs: photograph the whole slab, label included. `input_kind` becomes `graded_slab` and `grading` carries the company, grade and certificate number read from the label. If you already have the certificate number, the Grading certificate lookup is exact and cheaper (1 credit).

Try it POST /api/v1/vision/identify write

These inputs are shared across all docs pages, so an id entered here carries over.

request body
object · 3 keys
{
  "image_url": "https://api.rip.fun/storage/v1/object/public/tcg/cards/sm10/217vh.large.webp",
  "limit": 10,
  "include": "prices"
}
response

Not run yet. Press Run to make a live call against https://service.rip.fun (through this demo's server-side proxy; the API key never reaches the browser).

curl (tracks the inputs above)
curl -X POST 'https://service.rip.fun/api/v1/vision/identify' \
  -H 'X-API-Key: rip_…' \
  -H 'Content-Type: application/json' \
  -d '{"image_url":"https://api.rip.fun/storage/v1/object/public/tcg/cards/sm10/217vh.large.webp","limit":10,"include":"prices"}'

Request fields

FieldTypeRequiredDescription
imagefileyesThe image part: one JPEG, PNG or WebP, ≤ 10 MB, under the field name `image` (any other field name is a 400 `invalid_upload`).
gamestring—Restrict matching to one game: `pokemon` or `onepiece`. Omit to auto-detect (the usual choice — the pipeline reads the game off the card).
expansion_idsstring—Comma-separated expansion ids (`sv3pt5,sv4`) to restrict matching to — for example the sets a shop actually stocks. Wins over `game` when both are sent. Up to 50.
limitnumber—How many candidates to return, 1–10 (default 5).
includestring—Set to `prices` to embed our pricing on every candidate’s `card`, exactly as `include=prices` does on the catalog. No surcharge.
session_idstring—Your own id for a scanning session (up to 128 characters), grouping identifications that belong together — a pack opening, a collection import. Groups the training signal; never affects the result.

Response fields (data)

FieldDescription
matchThe identification, or `null` when nothing in the catalog matched confidently. `{ card, game, confidence }` — `card` is the exact Card object `GET /api/v1/{game}/cards/{id}` returns (with `pricing` when you asked for `include=prices`), `game` names that mount (`pokemon`, `onepiece`), `confidence` is 0–1.
candidates[]Ranked candidates, best first, up to `limit`. When `match` is present it is the first entry. Same shape as `match`.
ambiguous`true` when the pipeline knows it is unsure — a printing tie it could not resolve, a collector number that disagreed with the art, a close call. Show `candidates` and let the user pick instead of asserting `match`.
ambiguity_reasons[]Why, e.g. `printing_tie_unresolved`, `ocr_number_mismatch`, `low_confidence_tight_margin`. Empty when not ambiguous.
input_kindWhat the photo showed: `raw_card`, `graded_slab`, `card_back` or `unknown`.
gradingFor a slab: `{ company, grade, grade_numeric, cert_number, verification_status }`, read off the label — `verification_status` is `verified` when the grader’s record confirmed it, else `needs_review`, `not_found`, `error` or `not_attempted`. `null` for a raw card.
variants[]The match’s printing family, `{ card_id, name }` per sibling finish (Normal, Reverse Holo, Master Ball …), only when it has more than one member. Offer it as a picker; a chosen sibling is confirmed like any other card.
feedback_idHandle for the confirm call. Send it back with the card the user kept.
processing_msServer-side time spent identifying, in milliseconds.
warnings[]Fail-open notes from the pipeline, e.g. `slab_ocr_timeout` (the slab label read timed out; the card was matched on its art alone).

Errors

StatusCodeWhen
400image_requiredno `image` part, `image_url` or `image_base64` was sent
400invalid_imagethe bytes could not be decoded as JPEG, PNG or WebP (HEIC/HEIF is not supported), or `image_base64` is not base64
400invalid_uploadthe multipart file was not under the field name `image`, or more than one file was sent
400image_url_not_allowed`image_url` is not a public `https://` URL — private, loopback and link-local hosts are refused, at every redirect
400image_fetch_failed`image_url` answered with a non-2xx status or an empty body, redirected more than 3 times, or took longer than 10 seconds
413image_too_largethe image is over 10 MB, whichever way it was sent
400invalid_game`game` is not `pokemon` or `onepiece`
400invalid_limit`limit` carries no integer at all — an out-of-range integer is clamped to 1–10, not rejected
400invalid_include`include` is something other than `prices`
400invalid_expansion_idsnot a comma-separated string or array of strings, an entry over 64 characters, or more than 50 ids
429rate_limitedover 60 identifications/minute for the key, or the server’s identification gate is full (8 in flight per instance plus a short queue). Wait for `Retry-After` — 5 seconds for the latter
503timeoutthe identification did not finish inside the 55-second budget. Carries `retryable: true`; retry after `Retry-After` (2 seconds). Not billed

See Errors for the response envelope and the full code list.