/api/v1/vision/identify read:catalog 5
creditsThe 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).
POST /api/v1/vision/identify write These inputs are shared across all docs pages, so an id entered here carries over.
{
"image_url": "https://api.rip.fun/storage/v1/object/public/tcg/cards/sm10/217vh.large.webp",
"limit": 10,
"include": "prices"
}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 -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"}' | Field | Type | Required | Description |
|---|---|---|---|
image | file | yes | The image part: one JPEG, PNG or WebP, ≤ 10 MB, under the field name `image` (any other field name is a 400 `invalid_upload`). |
game | string | — | Restrict matching to one game: `pokemon` or `onepiece`. Omit to auto-detect (the usual choice — the pipeline reads the game off the card). |
expansion_ids | string | — | 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. |
limit | number | — | How many candidates to return, 1–10 (default 5). |
include | string | — | Set to `prices` to embed our pricing on every candidate’s `card`, exactly as `include=prices` does on the catalog. No surcharge. |
session_id | string | — | 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. |
data)| Field | Description |
|---|---|
match | The 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_kind | What the photo showed: `raw_card`, `graded_slab`, `card_back` or `unknown`. |
grading | For 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_id | Handle for the confirm call. Send it back with the card the user kept. |
processing_ms | Server-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). |
| Status | Code | When |
|---|---|---|
| 400 | image_required | no `image` part, `image_url` or `image_base64` was sent |
| 400 | invalid_image | the bytes could not be decoded as JPEG, PNG or WebP (HEIC/HEIF is not supported), or `image_base64` is not base64 |
| 400 | invalid_upload | the multipart file was not under the field name `image`, or more than one file was sent |
| 400 | image_url_not_allowed | `image_url` is not a public `https://` URL — private, loopback and link-local hosts are refused, at every redirect |
| 400 | image_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 |
| 413 | image_too_large | the image is over 10 MB, whichever way it was sent |
| 400 | invalid_game | `game` is not `pokemon` or `onepiece` |
| 400 | invalid_limit | `limit` carries no integer at all — an out-of-range integer is clamped to 1–10, not rejected |
| 400 | invalid_include | `include` is something other than `prices` |
| 400 | invalid_expansion_ids | not a comma-separated string or array of strings, an entry over 64 characters, or more than 50 ids |
| 429 | rate_limited | over 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 |
| 503 | timeout | the 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.