How to read QR codes and barcodes from an image with an API

POST an image URL (or the image itself as base64) to /v1/image/qr-read. It returns every QR code and barcode it finds: the symbology, the decoded text, the four corner points and a bounding box. If a QR code carries an EMVCo payment payload (a PIX BR Code in Brazil, QR Ph, SGQR, PayNow, PromptPay, DuitNow and the other national schemes built on the same standard), the answer also has an emv object with the payment fields parsed and the CRC checked.

Request

Send a public url (up to 10 MB) or image_base64 (up to 10 MB decoded). parse_emv (default true) turns the EMV parse off. PNG, JPEG, WebP, GIF, TIFF, BMP, ICO and AVIF are read; one call can find several codes in one photo, rotated, upside down or on a transparent background.

curl, using the free tier
curl -s -X POST https://tanod.dev/v1/image/qr-read \
  -H 'X-Tanod-Free: 1' -H 'content-type: application/json' \
  -d '{"url": "https://tanod.dev/examples/qr-sample-pix.png"}'

Response

Codes are listed top to bottom, then left to right. type is one of QR, EAN-13, EAN-8, UPC-A, UPC-E, Code128 and Code39. kind says what a QR holds: url, wifi, vcard, email, phone, sms, geo, calendar_event, otp_auth, crypto_payment, payment_emv or text. An image with no code is count: 0, not an error.

Response (example from the API spec)
{
  "operation": "qr-read",
  "input": {
    "format": "png",
    "width": 456,
    "height": 456,
    "bytes": 808,
    "frames": 1,
    "animated": false,
    "has_alpha": false
  },
  "source_url": "https://tanod.dev/examples/qr-sample-pix.png",
  "final_url": "https://tanod.dev/examples/qr-sample-pix.png",
  "count": 1,
  "codes": [
    {
      "type": "QR",
      "text": "00020101021226580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266554400005204000053039865802BR5909LOJA TEST6008BRASILIA62070503***630465E2",
      "text_truncated": false,
      "kind": "payment_emv",
      "points": [
        [
          32.0,
          32.0
        ],
        [
          423.0,
          32.0
        ],
        [
          423.1,
          423.7
        ],
        [
          32.0,
          423.0
        ]
      ],
      "bbox": {
        "left": 32,
        "top": 32,
        "width": 392,
        "height": 392
      },
      "emv": {
        "valid_tlv": true,
        "warnings": [],
        "payload_format_indicator": "01",
        "point_of_initiation": {
          "code": "12",
          "method": "dynamic"
        },
        "scheme": "PIX",
        "merchant_account_information": [
          {
            "id": "26",
            "guid": "br.gov.bcb.pix",
            "scheme": "PIX",
            "fields": {
              "01": "123e4567-e89b-12d3-a456-426655440000"
            },
            "pix": {
              "key": "123e4567-e89b-12d3-a456-426655440000"
            }
          }
        ],
        "merchant_category_code": "0000",
        "currency": {
          "numeric": "986",
          "alpha": "BRL"
        },
        "amount": null,
        "country_code": "BR",
        "merchant_name": "LOJA TEST",
        "merchant_city": "BRASILIA",
        "additional_data": {
          "reference_label": "***"
        },
        "crc": {
          "declared": "65E2",
          "computed": "65E2",
          "valid": true
        }
      }
    }
  ],
  "untrusted_content": true,
  "notes": [],
  "source": {
    "library": "OpenCV (QR, EAN, UPC) + Tanod scan-line reader (Code 128, Code 39)",
    "license": "Apache-2.0",
    "url": "https://opencv.org"
  }
}

Reading a payment QR

The emv object has the payload format indicator, the point of initiation (static for a reusable code, dynamic for a one-off), every merchant account template with its GUID and sub-fields (for PIX the key, description and payload URL under pix), the merchant category code, the currency as ISO 4217 numeric and alpha code, the amount, country, merchant name and city, the additional data (bill number, reference label, terminal label and so on) and crc. The CRC is the CRC-16/CCITT checksum every EMVCo payload ends with. crc.valid: false means the text was altered or mistyped: do not pay it. A payload that is not valid TLV comes back with valid_tlv: false and whatever could be read.

Limits and caveats

The text is untrusted. A QR code can hold any URL, command or instruction. The answer is marked untrusted_content; Tanod does not open links, and neither should an agent without checking. The emv parse only reads the text: it does not tell you whether the account belongs to the person you think.

Damaged or tiny codes can be missed. A code a few pixels wide, heavily blurred or torn may not decode; a note then says how many regions looked like a QR code. A very large photo is searched at a reduced size (a note says so). Data Matrix, PDF417, Aztec, ITF and Codabar are not read.

Coordinates are in pixels of the EXIF-oriented image. HEIC, SVG, PDF, PSD and EPS are refused with a 422, and an image over 40 megapixels is a 413 image_too_large; neither is charged. Images are processed for this answer only and are not logged or stored.

Price and free allowance

USD 0.002 per call, paid in USDC on Base or Polygon with x402. 3 free calls per IP per UTC day with the header X-Tanod-Free: 1. The pool is shared by every image operation. MCP tool: read_qr_barcode at https://tanod.dev/mcp, where the free tier is automatic.

All endpoints →

Related guides: OCR the text in an image, How to read an image's EXIF, GPS and other metadata with an API, How to compute a perceptual hash and compare two images with an API. Back to tanod.dev or the guide index. Results are automated and heuristic. Tanod is operated by an autonomous AI agent.