Read EAN, UPC, Code 128 and other 1D barcodes from any image.
Barcode Reader decodes 1D barcodes from an uploaded image or an image URL, including EAN-13, EAN-8, UPC-A, UPC-E, Code 128, Code 39, Code 93, ITF, Codabar and GS1 DataBar. It returns the decoded value and the symbology. Paid plans add the GTIN, the barcode's orientation and corners, and every barcode found in the image.
Try it — live request, no key required
No key required to try it. Get a key to use it in your app.
{
"status": "ok",
"error": null,
"data": {
"text": "1234567890128",
"format": "EAN-13",
"isRetail": true,
"gtin": "1234567890128",
"orientation": 0,
"position": {
"topLeft": {
"x": 49,
"y": 6
},
"topRight": {
"x": 588,
"y": 6
},
"bottomRight": {
"x": 588,
"y": 418
},
"bottomLeft": {
"x": 49,
"y": 418
}
},
"barcodeCount": 1,
"barcodes": [
{
"text": "1234567890128",
"format": "EAN-13",
"gtin": "1234567890128",
"orientation": 0,
"position": {
"topLeft": {
"x": 49,
"y": 6
},
"topRight": {
"x": 588,
"y": 6
},
"bottomRight": {
"x": 588,
"y": 418
},
"bottomLeft": {
"x": 49,
"y": 418
}
}
}
]
}
}
About the Barcode Reader API
Images are normalised and decoded with zxing-cpp, which tries rotated and inverted versions of the image, so barcodes printed sideways or light-on-dark are still found. Retail codes are reported in their native form, with the 13-digit GTIN alongside.
What people use it for
- Product Lookup
- Read the EAN or UPC from a customer's photo of a product and look the item up in your catalogue by GTIN.
- Inventory and Warehousing
- Decode Code 128 and ITF labels from photos taken by staff without dedicated scanners.
- Returns and Receipts
- Pull order or parcel numbers out of photographed labels and receipts to match them to records.
- Document Processing
- Extract barcodes from scanned forms and shipping documents as part of an intake pipeline.
Ways to call it
One endpoint, many ways in — REST with JSON, XML, YAML and CSV, plus GraphQL and an MCP interface for AI agents.
- JSON
- Default REST response
- XML
- Markup format
- YAML
- Human-readable
- CSV
- Tabular export
- GraphQL
- Query language
- MCP
- For AI agents
How many barcode scans does the Starter plan cover?
What does the Free plan return?
Which barcode formats are supported?
How large can the image be?
How are UPC-A and UPC-E reported?
Request parameters — POST /v1/barcodereader
Sent as JSON in the request body. Premium parameters are accepted on every plan but only take effect on plans that include them. Anything not listed here is dropped rather than passed through.
Optional
| Parameter | Type | Example | Description |
|---|---|---|---|
imageOptional | string | An image file containing a barcode (JPG, PNG, GIF, WebP or BMP), or a public image URL url |
curl -X POST "https://api.apiverve.com/v1/barcodereader" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'Authentication
Send your key in the x-api-key header. That is the only auth step — no token exchange, and no per-endpoint scope to configure.
| Header | When | Value |
|---|---|---|
x-api-keyRequired | Every request | Your API key. Header names are case-insensitive, so X-API-Key is the same header. |
AuthorizationAlternate | Instead of the above | Bearer <key> — for clients that only expose bearer auth. Resolves to the same account and the same billing. |
content-typeRequired | POST body | application/json |
A 401 means the key is missing, invalid or expired. A 403 means the key is valid but not permitted here — blocked by a key restriction or an IP allow-list. Running out of credits is a 429.
Response — POST /v1/barcodereader
Every APIVerve endpoint returns the same three top-level keys, so one response handler covers your whole integration: status, error and data. Only data changes shape.
{
"status": "ok",
"error": null,
"data": {
"text": "1234567890128",
"format": "EAN-13",
"isRetail": true,
"gtin": "1234567890128",
"orientation": 0,
"position": {
"topLeft": {
"x": 49,
"y": 6
},
"topRight": {
"x": 588,
"y": 6
},
"bottomRight": {
"x": 588,
"y": 418
},
"bottomLeft": {
"x": 49,
"y": 418
}
},
"barcodeCount": 1,
"barcodes": [
{
"text": "1234567890128",
"format": "EAN-13",
"gtin": "1234567890128",
"orientation": 0,
"position": {
"topLeft": {
"x": 49,
"y": 6
},
"topRight": {
"x": 588,
"y": 6
},
"bottomRight": {
"x": 588,
"y": 418
},
"bottomLeft": {
"x": 49,
"y": 418
}
}
}
]
}
}
Response fields
Paths are relative to data. Premium fields are absent rather than zeroed on plans that do not include them, so check for presence instead of comparing to 0.
| Field | Type | Example | Description |
|---|---|---|---|
text | string | "1234567890128" | The decoded barcode value |
format | string | "EAN-13" | The symbology, such as EAN-13, UPC-A, Code 128 or ITF |
isRetail | boolean | true | True for retail product codes (EAN-13, EAN-8, UPC-A, UPC-E) |
gtinPremium | string | "1234567890128" | The 13-digit GTIN for retail codes, null for other formats |
orientationPremium | number | 0 | Rotation of the barcode in degrees |
positionPremium | object | {...} | Corner coordinates of the barcode in the decoded image |
topLeftPremium | object | {...} | Top left corner with x and y |
xPremium | number | 49 | |
yPremium | number | 6 | |
topRightPremium | object | {...} | Top right corner with x and y |
xPremium | number | 588 | |
yPremium | number | 6 | |
bottomRightPremium | object | {...} | Bottom right corner with x and y |
xPremium | number | 588 | |
yPremium | number | 418 | |
bottomLeftPremium | object | {...} | Bottom left corner with x and y |
xPremium | number | 49 | |
yPremium | number | 418 | |
barcodeCountPremium | number | 1 | Number of barcodes found in the image (up to 10) |
barcodesPremium | array[1] | Every barcode found, with text, format, gtin, orientation and position | |
textPremium | string | "1234567890128" | Decoded value |
formatPremium | string | "EAN-13" | Symbology |
gtinPremium | string | "1234567890128" | 13-digit GTIN for retail codes, null otherwise |
orientationPremium | number | 0 | Rotation in degrees |
positionPremium | object | {...} | Corner coordinates |
topLeftPremium | object | {...} | |
xPremium | number | 49 | |
yPremium | number | 6 | |
topRightPremium | object | {...} | |
xPremium | number | 588 | |
yPremium | number | 6 | |
bottomRightPremium | object | {...} | |
xPremium | number | 588 | |
yPremium | number | 418 | |
bottomLeftPremium | object | {...} | |
xPremium | number | 49 | |
yPremium | number | 418 |
Response headers
Every response carries your balance, so your own code always knows where it stands without polling anything. All four are exposed to browsers through CORS.
| Header | What it carries |
|---|---|
x-api-remaining-credits | Credits left in the current cycle |
x-api-credits-used | Credits spent in the current cycle |
x-api-max-credits | The allowance for the cycle |
x-api-version | Version of the endpoint that answered |
Errors
Read the HTTP status first, then error for the specific reason. The body names the parameter that has to change.
| Status | Meaning | What to do |
|---|---|---|
400 | Input was rejected | Read error; it names the parameter that has to change. |
401 | Key missing or invalid | Check the header name and the key value. |
403 | Key valid, but not permitted here | A key restriction or an IP allow-list — never a bad key. |
429 | Rate limited, or out of credits | Read error to tell them apart, then back off or top up. |
What a call costs — 10 credits per call
Credits are shared across every APIVerve API — one balance, one key, one bill. Failed requests never count against it, so a rejected input costs nothing.
| Plan | Per month | Credits | Barcode Reader calls | Per 1,000 calls |
|---|---|---|---|---|
| Free | Free | 200 | 20 | Free |
| Starter | $29.99 | 200,000 | 20,000 | $1.50 |
| Pro | $99.99 | 1,000,000 | 100,000 | $1.00 |
| Mega | $299.99 | 4,000,000 | 400,000 | $0.75 |
Calls are what the credits buy at this API's rate — spend them here, on any of the other APIs, or across both. Nothing is reserved per endpoint.
Call it from cURL — POST /v1/barcodereader
No SDK to install — this is a plain HTTPS call to /v1/barcodereader with your key in the x-api-key header. Swap in a real key and it runs as-is.
curl "https://api.apiverve.com/v1/barcodereader" \
-H "x-api-key: YOUR_API_KEY"Resources
Call it from Node.js — POST /v1/barcodereader
No SDK to install — this is a plain HTTPS call to /v1/barcodereader with your key in the x-api-key header. Swap in a real key and it runs as-is.
const res = await fetch("https://api.apiverve.com/v1/barcodereader", {
headers: { "x-api-key": "YOUR_API_KEY" },
});
const { data } = await res.json();
console.log(data);Resources
Call it from Python — POST /v1/barcodereader
No SDK to install — this is a plain HTTPS call to /v1/barcodereader with your key in the x-api-key header. Swap in a real key and it runs as-is.
import requests
res = requests.get(
"https://api.apiverve.com/v1/barcodereader",
headers={"x-api-key": "YOUR_API_KEY"},
)
print(res.json()["data"])Resources
Call it from C# / .NET — POST /v1/barcodereader
No SDK to install — this is a plain HTTPS call to /v1/barcodereader with your key in the x-api-key header. Swap in a real key and it runs as-is.
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY");
var json = await client.GetStringAsync("https://api.apiverve.com/v1/barcodereader");
Console.WriteLine(json);Resources
Call it from Go — POST /v1/barcodereader
No SDK to install — this is a plain HTTPS call to /v1/barcodereader with your key in the x-api-key header. Swap in a real key and it runs as-is.
req, _ := http.NewRequest("GET", "https://api.apiverve.com/v1/barcodereader", nil)
req.Header.Set("x-api-key", "YOUR_API_KEY")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))Resources
Call it from PHP — POST /v1/barcodereader
No SDK to install — this is a plain HTTPS call to /v1/barcodereader with your key in the x-api-key header. Swap in a real key and it runs as-is.
$ch = curl_init("https://api.apiverve.com/v1/barcodereader");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: YOUR_API_KEY"]);
$response = curl_exec($ch);
echo $response;Resources
Call it from Ruby — POST /v1/barcodereader
No SDK to install — this is a plain HTTPS call to /v1/barcodereader with your key in the x-api-key header. Swap in a real key and it runs as-is.
require "net/http"
uri = URI("https://api.apiverve.com/v1/barcodereader")
req = Net::HTTP::Get.new(uri)
req["x-api-key"] = "YOUR_API_KEY"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.bodyResources
Zero-code embed — one snippet, no backend
Drop an interactive Barcode Reader form onto any page. The widget calls the API for you, so no key ever appears in your markup and there is nothing to deploy.
Changelog — Barcode Reader API
Every change to this endpoint, newest first. Breaking changes ship as a new version and the previous one keeps serving — response fields are added, never removed or retyped in place, so an integration written against v1 keeps working.
Entries are picked up from dated catalog snapshots, so the first one appears after the next snapshot that moves something. A period where nothing changed produces no entry — silence is a valid changelog for the Barcode Reader API.
Images
- Barcode Reader
- Face Detector
- HEIC Converter
- Image Converter
- NSFW Image Detector
- QR Code Reader
- SVG Converter
- WebP Converter