# codereader > HTTP API that reads QR codes and barcodes from images and videos. Upload a file (or pass its URL), get back every code with its format, text, raw bytes and position. Videos are scanned frame by frame and de-duplicated, so each appearance of a code is reported once. Base URL: https://decode.ivjn.us - [OpenAPI 3.1 spec](https://decode.ivjn.us/openapi.json): machine-readable description of every endpoint - [Interactive docs](https://decode.ivjn.us/docs) ## Limits - Max upload size: 95 MB - Images: at most 16384px per side (larger images get HTTP 422) - Synchronous video decoding stops after 90s and returns what it found so far (`state` = `stopped`); use `mode=async` for longer clips - 60 decode requests per minute per client (HTTP 429 with Retry-After when exceeded) - 2 uploads are decoded at a time; others wait up to 20s, then get HTTP 503 (retry later) - Async tasks stop after 30 minutes; at most 4 run at once (HTTP 429 otherwise); finished tasks are kept for 1 hour - `url=` inputs: same size cap, 30s to download, 4 downloads at a time; only public http(s) hosts on ports 80/443, at most 5 redirects - Public instance: uploads and `url=` inputs only; stream/camera tasks and webhooks are disabled ## Decode an image `POST /v1/decode/image` with the image as the raw body (or multipart field `file`). PNG, JPEG, BMP, GIF, WebP, TIFF. ```bash curl --data-binary @photo.jpg "https://decode.ivjn.us/v1/decode/image" curl -F file=@photo.jpg "https://decode.ivjn.us/v1/decode/image?formats=qr_code,ean_13" ``` Image already online? Pass its URL instead and the server downloads it. A plain GET works too, handy when you can only fetch URLs (URL-encode the image URL): ```bash curl "https://decode.ivjn.us/v1/decode/image?url=https%3A%2F%2Fexample.com%2Fticket.png" curl -X POST "https://decode.ivjn.us/v1/decode/image?url=https%3A%2F%2Fexample.com%2Fticket.png&engine=zxing-cpp" curl -H "Content-Type: application/json" -d '{"url":"https://example.com/ticket.png","formats":"qr_code"}' "https://decode.ivjn.us/v1/decode/image" ``` Download problems are reported as 400 (`url` not allowed, e.g. a private address), 413 (too large) or 422 (the URL answered non-2xx, failed or timed out; `error` says which). Response: ```json {"width":1280,"height":720,"elapsed_ms":41.2, "detections":[{"format":"qr_code","text":"https://example.com","raw_b64":"aHR0cHM6Ly9leGFtcGxlLmNvbQ==", "position":[{"x":62.5,"y":102.5},{"x":178.5,"y":102.5},{"x":178.5,"y":218.5},{"x":62.5,"y":218.5}], "engine":"rxing"}]} ``` An empty `detections` array means no code was found. Use `raw_b64` when the payload is binary. If the first pass finds nothing, the server automatically retries with the other engine and (for images under 1600px) a 2x upscaled copy; `strategy` tells which pass succeeded (`direct`, `alt-engine`, `upscale-2x`, `upscale-2x-alt-engine`, or `null`). Codes need roughly 1.2+ pixels per bar module (an EAN-13 at least ~115px wide) to be readable. ## Decode a video `POST /v1/decode/video` with the video as the raw body (or multipart field `file`). MP4/MOV, MKV/WebM, AVI, FLV, MPEG-TS, Ogg, MJPEG, GIF. ```bash curl --data-binary @clip.mp4 "https://decode.ivjn.us/v1/decode/video?fps=5&cooldown=3" curl -X POST "https://decode.ivjn.us/v1/decode/video?url=https%3A%2F%2Fexample.com%2Fclip.mp4" # or JSON body {"url": ...} ``` Response: `state` (`finished`, or `stopped` if the time limit cut it short; `error` explains), `stats`, `occurrences` (one entry per appearance: `format`, `text`, `first_seen`, `last_seen` in seconds, `frames`) and `events` (`detected` / `lost` in time order). For long videos use `mode=async`; the response (HTTP 202) is a task with `id`, `events_url` and `ws_url`: ```bash curl --data-binary @long.mp4 "https://decode.ivjn.us/v1/decode/video?mode=async" curl -N "https://decode.ivjn.us/v1/streams//events" # Server-Sent Events; ends after the final status event curl "https://decode.ivjn.us/v1/streams/" # or poll: state, active codes, occurrences, stats curl -X DELETE "https://decode.ivjn.us/v1/streams/" # stop early ``` Event objects: `{"event":"detected","t":1.0,"frame":25,"format":...,"text":...,"raw_b64":...,"position":[...],"engine":...}`, `{"event":"lost","t":7.0,"format":...,"text":...,"first_seen":1.0,"last_seen":3.8,"frames":15}`, `{"event":"status","t":...,"state":"running|finished|stopped|failed","message":...}`. Task events also carry `task_id` and `seq`. ## Parameters (query string) - `formats`: comma separated (`qr_code`, `data_matrix`, `aztec`, `pdf417`, `ean_13`, `ean_8`, `upc_a`, `upc_e`, `code_128`, `code_39`, `code_93`, `codabar`, `itf`, `databar`, …), or `all` (default), `1d`, `2d`. Full list: `GET /v1/formats` - `engine`: `rxing` (default) or `zxing-cpp` (often better on blurry, tilted or small codes) - `try_harder`: `true` (default) tries rotations and inverted codes; `false` is faster - `url`: http(s) URL to download instead of uploading (query string with an empty body, or JSON body `{"url": ...}`, whose other fields override the query string) - Video only: `fps` (default 5), `cooldown` seconds (default 3), `emit_lost` (default true), `mode` (`sync` / `async`) ## Errors JSON `{"error": "..."}` with 400 (bad parameter or `url` not allowed), 404 (unknown task), 413 (too large), 422 (not a readable image/video, or the `url` download failed or timed out), 429 (rate limited, see Retry-After), 503 (busy, retry shortly). ## Tips - Upload the original image, not a thumbnail: below ~1.2 px per module the bars blur together and no decoder can read them. - Restrict `formats` when you know what you are looking for: it is faster and avoids false positives.