The Veribl REST API lets you read and manage your products, passports, registrations and contacts from your own systems.
Base URL: https://app.veribl.com/api/v1
The REST API is available on the Growth and Enterprise plans. API keys created before 16 October 2026 keep working on any plan. On Starter, a newer key gets a 403 with the code plan_required. The battery passport data endpoint (/dpp/units/{serial}) works on every plan.
All requests and responses use JSON. All dates are ISO 8601 (for example 2026-10-16T09:30:00.000Z).
Authenticate
Create an API key in Settings, then API keys (see API Keys). Send it as a bearer token:
curl https://app.veribl.com/api/v1/products \
-H "Authorization: Bearer vdpp_your_key_here"Each key has an access level per area: products, passports (including battery data), registrations and contacts. Read allows GET endpoints; Read and write also allows the endpoints that change data. A missing, wrong or revoked key returns 401. A request outside the key's access returns 403.
Each key can only see data from its own company.
Rate limits
Each key can make 120 requests per minute. When you go over, the API returns 429 with these headers:
Retry-After— seconds to wait before trying againX-RateLimit-Limit—120X-RateLimit-Remaining—0X-RateLimit-Reset— Unix time when the limit resets
Wait for Retry-After seconds, then retry.
Paging
List endpoints return up to 50 items by default.
limit— items per page, from 1 to 100cursor— pass thenextCursorvalue from the previous response
{
"data": [ ... ],
"nextCursor": "MTc2MDYwNjQwMHxwcm9kLTEyMw"
}When nextCursor is null, you have reached the last page. Treat cursors as opaque text. An invalid cursor returns 400 with the code invalid_cursor.
Errors
Errors use the HTTP status and this body:
{
"error": {
"code": "invalid_field",
"message": "status must be draft, live or archived"
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_body, invalid_field, invalid_parameter, invalid_cursor | The request is wrong. Read the message. |
| 401 | unauthorized | Missing, invalid or revoked key |
| 401 | key_expired | The key was rotated and its 7 days are over. Use the new key |
| 403 | insufficient_scope | The key has no access, or only read access, to this area |
| 413 | file_too_large | The file is over the size limit |
| 415 | unsupported_file | Only PDFs and images can be uploaded |
| 403 | plan_required | Your plan does not include the API (Starter, key created on or after 16 October 2026) |
| 403 | plan_limit | Your plan's product limit is reached |
| 404 | not_found | No such product |
| 409 | conflict | For example, the GTIN is already used or locked |
| 429 | rate_limited | Too many requests |
Products
List products
GET /products
Newest change first.
| Query parameter | Description |
|---|---|
status | draft, live or archived |
gtin | Only the product with this GTIN |
updated_since | ISO 8601 date or Unix seconds. Products changed at or after it. |
limit, cursor | Paging |
curl "https://app.veribl.com/api/v1/products?status=live&limit=2" \
-H "Authorization: Bearer vdpp_your_key_here"{
"data": [
{
"id": "3f1c2a9e-5b7d-4c1e-9a2b-8d6e4f0a1b23",
"publicId": "p-9a1b2c3d",
"name": "Aero 2 Helmet",
"brand": "Halden",
"model": "AERO-2",
"gtin": "04012345678901",
"category": "Helmets",
"description": "Lightweight road helmet.",
"imageUrl": "https://example.com/aero2.jpg",
"status": "live",
"warrantyYears": 2,
"familyId": null,
"urls": {
"productPage": "https://go.veribl.com/halden/p-9a1b2c3d",
"passport": "https://go.veribl.com/01/04012345678901",
"passportData": "https://go.veribl.com/api/dpp/04012345678901"
},
"passport": { "published": true, "latestVersion": 3, "publishedAt": "2026-10-10T08:00:00.000Z" },
"createdAt": "2026-09-01T12:00:00.000Z",
"updatedAt": "2026-10-10T08:00:00.000Z"
}
],
"nextCursor": "MTc2MDA4NjQwMHwzZjFjMmE5ZQ"
}Get a product
GET /products/{id}
Returns the same fields as the list, plus materials and certifications.
curl https://app.veribl.com/api/v1/products/3f1c2a9e-5b7d-4c1e-9a2b-8d6e4f0a1b23 \
-H "Authorization: Bearer vdpp_your_key_here"{
"data": {
"id": "3f1c2a9e-5b7d-4c1e-9a2b-8d6e4f0a1b23",
"name": "Aero 2 Helmet",
"status": "live",
"materials": [
{ "name": "Polycarbonate", "percentage": 60, "recycledPercentage": 20, "recyclability": "Recyclable", "originCountry": "DE" }
],
"certifications": [
{ "name": "CE", "number": "CE-1234", "issuer": "TUV", "issuedOn": "2025-01-15", "expiresOn": "2028-01-15", "status": "valid" }
]
}
}(Other product fields are shown in the list example above.)
Create a product
POST /products — needs a read-and-write key.
| Body field | Description |
|---|---|
name | Required. Up to 200 characters. |
brand | Brand name. Defaults to your company name. |
model | Model number |
gtin | A valid GTIN (8, 12, 13 or 14 digits with a correct check digit) |
category | Defaults to General |
description | Up to 20,000 characters |
imageUrl | Must start with https:// |
warrantyYears | A number from 0 to 50. Defaults to 2. |
status | draft (default), live or archived |
materials | List of up to 100 items (see below) |
certifications | List of up to 100 items (see below) |
Each material has name (required), percentage (0 to 100, required), recycledPercentage (0 to 100), recyclability and originCountry (2-letter country code).
Each certification has name (required), number, issuer, issuedOn and expiresOn (dates as YYYY-MM-DD).
curl -X POST https://app.veribl.com/api/v1/products \
-H "Authorization: Bearer vdpp_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Aero 2 Helmet",
"model": "AERO-2",
"gtin": "04012345678901",
"warrantyYears": 2,
"materials": [{ "name": "Polycarbonate", "percentage": 60 }]
}'Returns 201 with the new product in data. If your plan's product limit is reached, you get 403 with the code plan_limit. A GTIN already used by another active product returns 409.
Update a product
PATCH /products/{id} — needs a read-and-write key.
Send only the fields you want to change. The fields are the same as for create, and name is only needed if you change it. Set a field to null to clear it, where the field allows it.
curl -X PATCH https://app.veribl.com/api/v1/products/3f1c2a9e-5b7d-4c1e-9a2b-8d6e4f0a1b23 \
-H "Authorization: Bearer vdpp_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "status": "live", "warrantyYears": 3 }'Returns the updated product in data.
Two rules to know:
- Materials and certifications replace the whole list. If you send
materials, the old materials are removed and only the ones you sent remain. Leave the field out to keep the existing list. - The GTIN is locked after you publish the passport. It is the passport's public address. Changing it returns
409.
Archive a product
DELETE /products/{id} — needs a read-and-write key.
Products are never deleted through the API. This sets the status to archived and returns the product.
Passports
Get a product's passport
GET /products/{id}/passport
Returns the latest published version with all its fields, the version history, and the state of the signatures.
curl https://app.veribl.com/api/v1/products/3f1c2a9e-5b7d-4c1e-9a2b-8d6e4f0a1b23/passport \
-H "Authorization: Bearer vdpp_your_key_here"{
"data": {
"productId": "3f1c2a9e-5b7d-4c1e-9a2b-8d6e4f0a1b23",
"published": true,
"publicUrl": "https://go.veribl.com/01/04012345678901",
"verifyUrl": "https://go.veribl.com/api/dpp/04012345678901/verify",
"signatures": "intact",
"latest": { "version": 3, "publishedAt": "2026-10-10T08:00:00.000Z", "data": { } },
"versions": [
{ "version": 3, "publishedAt": "2026-10-10T08:00:00.000Z", "note": "Added recycled content", "signed": true, "entryHash": "9f2c..." },
{ "version": 2, "publishedAt": "2026-09-20T08:00:00.000Z", "note": null, "signed": true, "entryHash": "41ab..." }
]
}
}signatures is one of none, not_signed, partly_signed, intact or broken. latest is null if nothing is published yet.
Publish a passport
POST /products/{id}/passport — needs a read-and-write key.
Publishes the product's current data as the next passport version.
| Body field | Description |
|---|---|
note | Optional. A short note for the version history (up to 300 characters). |
curl -X POST https://app.veribl.com/api/v1/products/3f1c2a9e-5b7d-4c1e-9a2b-8d6e4f0a1b23/passport \
-H "Authorization: Bearer vdpp_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "note": "Added recycled content" }'{ "data": { "version": 4 } }If the product can't be published yet, you get 400 with the code cannot_publish and a message that says why.
Files and manuals
Upload a file
POST /files (Products: read and write). Stores a PDF or an image. Send one of:
- a multipart form with the file in a field called
file - JSON
{ "url": "https://…" }: a public https address Veribl downloads - JSON
{ "fileName": "manual.pdf", "contentBase64": "…" }
PDFs up to 25 MB, images (PNG, JPEG, WebP, GIF, SVG, AVIF) up to 10 MB.
curl -X POST https://app.veribl.com/api/v1/files \
-H "Authorization: Bearer vdpp_your_key_here" \
-F "[email protected]"{ "data": { "id": "a81c…", "fileName": "aerona-manual.pdf", "mimeType": "application/pdf", "size": 2481930, "url": "https://…" } }Use the url as a product's imageUrl, or attach a PDF as a manual.
Attach a manual
POST /products/{id}/manuals with { "fileId": "a81c…" } (Products: read and write). The manual shows on the product page and the AI assistant answers from it. GET /products/{id}/manuals lists them; DELETE /products/{id}/manuals/{fileId} detaches one.
Read a manual's text
GET /products/{id}/manuals/{fileId} (Products: read) returns the manual's text, for writing the product's articles from it. Long manuals come in parts of 60,000 characters: pass ?offset= with nextOffset until it's null.
Articles
Help articles on a product's page. Needs a key with access to Articles.
| Method and path | What it does |
|---|---|
GET /products/{id}/articles | List the product's articles (without content) |
POST /products/{id}/articles | Create an article |
GET /products/{id}/articles/{articleId} | One article with its content |
PATCH /products/{id}/articles/{articleId} | Change it; only the fields you send change |
DELETE /products/{id}/articles/{articleId} | Delete it |
| Body field | Description |
|---|---|
title | Required when creating. |
content | The article body as HTML (<h2>, <h3>, <p>, <ol>, <ul>, <table>…). Scripts are removed. |
description | One-line summary shown under the title. |
status | draft (the default), visible or hidden. Only visible articles show to customers. |
featured | true shows it first. |
order | Position on the page (PATCH only). |
categoryId | A category from /products/{id}/article-categories, or null. |
source | { "fileId", "fileName", "pages" }: the manual it was written from, shown to reviewers. |
curl -X POST https://app.veribl.com/api/v1/products/3f1c…/articles \
-H "Authorization: Bearer vdpp_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "title": "Replacing the filter", "content": "<ol><li>Turn the purifier off.</li></ol>" }'Categories, translations and languages
| Method and path | What it does |
|---|---|
GET /products/{id}/article-categories | The product page's article groups |
POST /products/{id}/article-categories | { "name", "icon"? }: add one (an existing name is reused) |
GET /languages | Your default language and the others you publish in |
GET /products/{id}/articles/{articleId}/translations | The article's other languages |
PUT /products/{id}/articles/{articleId}/translations/{locale} | { "title", "content", "description"? }: save one language |
DELETE /products/{id}/articles/{articleId}/translations/{locale} | Remove one language |
Passport data
GET /products/{id}/passport-checklist (Passports: read) lists what each passport rule needs, what's done, and for each item how to fill it: howToFill.via and howToFill.key. When howToFill is null, a person fills it in Veribl (confirmations, substance declarations, the economic operator).
PATCH /products/{id}/passport-data (Passports: read and write) fills checklist items:
| Body field | Description |
|---|---|
fields | { "<code>": value } for checklist fields, or "product.<field>" for product passport fields |
recycling | A list of disposal and recycling instructions |
expectedLifetimeYears | Expected lifetime in years |
battery | { "<key>": value } for battery passport fields |
It returns what was saved and how ready the passport is. Publish a new version with POST /products/{id}/passport.
Spare parts and accessories
GET /products/{id}/parts lists the product's spare parts, accessories and consumables. POST /products/{id}/parts adds one (Products: read and write):
| Body field | Description |
|---|---|
name | Required. |
type | spare_part (the default), accessory or consumable |
partNumber | A part number you already have is reused instead of duplicated |
toolsRequired, userReplaceable, availabilityYears | Repair details; spare parts show on the passport |
description, price, buyUrl, imageUrl | Optional |
Registrations
GET /registrations
Every registered product unit, with its owner. Newest first.
| Query parameter | Description |
|---|---|
product_id | Only registrations for this product |
since | ISO 8601 date or Unix seconds. Registered at or after it. |
limit, cursor | Paging |
curl "https://app.veribl.com/api/v1/registrations?since=2026-10-01" \
-H "Authorization: Bearer vdpp_your_key_here"{
"data": [
{
"id": "reg-7d2e",
"registeredAt": "2026-10-14T16:20:00.000Z",
"product": { "id": "3f1c2a9e-5b7d-4c1e-9a2b-8d6e4f0a1b23", "name": "Aero 2 Helmet", "gtin": "04012345678901" },
"serial": "HLD-000123",
"purchaseDate": "2026-10-02",
"purchasedFrom": "Bike Shop Riga",
"warrantyExpiresAt": "2028-10-14T00:00:00.000Z",
"location": { "city": "Riga", "country": "LV" },
"owner": {
"contactId": "eu-55a1",
"email": "[email protected]",
"firstName": "Anna",
"lastName": "Berzina",
"marketingConsent": true,
"marketingConsentAt": "2026-10-14T16:20:00.000Z"
}
}
],
"nextCursor": null
}Note: Only use an owner's email for marketing if
marketingConsentistrue.
Contacts
GET /contacts
One row per owner, newest first (by first registration).
| Query parameter | Description |
|---|---|
consent | true to return only people who agreed to marketing email |
since | ISO 8601 date or Unix seconds. Filters on the person's first registration. |
limit, cursor | Paging |
curl "https://app.veribl.com/api/v1/contacts?consent=true" \
-H "Authorization: Bearer vdpp_your_key_here"{
"data": [
{
"id": "eu-55a1",
"email": "[email protected]",
"firstName": "Anna",
"lastName": "Berzina",
"country": "LV",
"registrations": 2,
"firstRegisteredAt": "2026-09-03T10:00:00.000Z",
"lastRegisteredAt": "2026-10-14T16:20:00.000Z",
"marketingConsent": true,
"marketingConsentAt": "2026-09-03T10:00:00.000Z",
"tags": ["vip", "newsletter"]
}
],
"nextCursor": null
}Battery units
These endpoints are for battery passports, for example a battery management system that reports state of health. The {serial} is the unit's serial number.
Get a unit
GET /dpp/units/{serial}
curl https://app.veribl.com/api/v1/dpp/units/BAT-0001 \
-H "Authorization: Bearer vdpp_your_key_here"{
"data": {
"serial": "BAT-0001",
"model": "PowerCell 48V",
"status": "original",
"manufacturedOn": "2026-03-01",
"stateOfHealth": 97,
"cycles": 120,
"updatedAt": 1760600000
}
}updatedAt is Unix seconds. An unknown serial returns 404.
Update a unit
POST /dpp/units/{serial} — needs a read-and-write key.
Send at least one field.
| Body field | Description |
|---|---|
stateOfHealth | Percentage from 0 to 100 |
cycles | Charge cycles, a whole number of 0 or more |
status | original, repurposed, re-used, remanufactured or waste |
manufacturedOn | Date text, for example 2026-03-01 |
event | { "kind": "...", "data": { } } — adds a custom entry to the unit's history |
curl -X POST https://app.veribl.com/api/v1/dpp/units/BAT-0001 \
-H "Authorization: Bearer vdpp_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "stateOfHealth": 95, "cycles": 180 }'{ "success": true }Errors on this endpoint return { "error": "message" } as plain text, not the { error: { code, message } } shape used elsewhere.
Public passport endpoints
These endpoints need no API key. They are on the passport host, https://go.veribl.com, and only work for passports that are public.
| Endpoint | What it returns |
|---|---|
GET /api/dpp/{gtin} | The passport as JSON-LD (schema.org and GS1). Optional: version, serial, lot, lang. |
GET /api/dpp/{gtin}/verify | A signature check: whether every published version is unchanged since it was signed. Optional: serial. |
GET /api/dpp/keys | Veribl's public signing keys, as a JWK set, for checking signatures yourself |
curl https://go.veribl.com/api/dpp/04012345678901/verify{
"gtin": "04012345678901",
"verdict": "intact",
"summary": "Every published version is unchanged since it was signed, and none is missing.",
"latest": { "version": 3, "publishedAt": "2026-10-10T08:00:00.000Z" },
"checkedAt": "2026-10-16T09:30:00.000Z",
"keys": "https://go.veribl.com/api/dpp/keys"
}The verdict is intact, partly_signed, broken or not_signed. An invalid GTIN returns 400. A passport that doesn't exist or isn't public returns 404.
Get events pushed to you
Polling is not the only option. To be told when something happens, such as a new registration, set up webhooks. Veribl sends a signed request to your URL for each event.