Veribl logo

REST API

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 again
  • X-RateLimit-Limit — 120
  • X-RateLimit-Remaining — 0
  • X-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 100
  • cursor — pass the nextCursor value 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"
  }
}
StatusCodeMeaning
400invalid_body, invalid_field, invalid_parameter, invalid_cursorThe request is wrong. Read the message.
401unauthorizedMissing, invalid or revoked key
401key_expiredThe key was rotated and its 7 days are over. Use the new key
403insufficient_scopeThe key has no access, or only read access, to this area
413file_too_largeThe file is over the size limit
415unsupported_fileOnly PDFs and images can be uploaded
403plan_requiredYour plan does not include the API (Starter, key created on or after 16 October 2026)
403plan_limitYour plan's product limit is reached
404not_foundNo such product
409conflictFor example, the GTIN is already used or locked
429rate_limitedToo many requests

Products

List products

GET /products

Newest change first.

Query parameterDescription
statusdraft, live or archived
gtinOnly the product with this GTIN
updated_sinceISO 8601 date or Unix seconds. Products changed at or after it.
limit, cursorPaging
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 fieldDescription
nameRequired. Up to 200 characters.
brandBrand name. Defaults to your company name.
modelModel number
gtinA valid GTIN (8, 12, 13 or 14 digits with a correct check digit)
categoryDefaults to General
descriptionUp to 20,000 characters
imageUrlMust start with https://
warrantyYearsA number from 0 to 50. Defaults to 2.
statusdraft (default), live or archived
materialsList of up to 100 items (see below)
certificationsList 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 fieldDescription
noteOptional. 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 pathWhat it does
GET /products/{id}/articlesList the product's articles (without content)
POST /products/{id}/articlesCreate 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 fieldDescription
titleRequired when creating.
contentThe article body as HTML (<h2>, <h3>, <p>, <ol>, <ul>, <table>…). Scripts are removed.
descriptionOne-line summary shown under the title.
statusdraft (the default), visible or hidden. Only visible articles show to customers.
featuredtrue shows it first.
orderPosition on the page (PATCH only).
categoryIdA 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 pathWhat it does
GET /products/{id}/article-categoriesThe product page's article groups
POST /products/{id}/article-categories{ "name", "icon"? }: add one (an existing name is reused)
GET /languagesYour default language and the others you publish in
GET /products/{id}/articles/{articleId}/translationsThe 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 fieldDescription
fields{ "<code>": value } for checklist fields, or "product.<field>" for product passport fields
recyclingA list of disposal and recycling instructions
expectedLifetimeYearsExpected 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 fieldDescription
nameRequired.
typespare_part (the default), accessory or consumable
partNumberA part number you already have is reused instead of duplicated
toolsRequired, userReplaceable, availabilityYearsRepair details; spare parts show on the passport
description, price, buyUrl, imageUrlOptional

Registrations

GET /registrations

Every registered product unit, with its owner. Newest first.

Query parameterDescription
product_idOnly registrations for this product
sinceISO 8601 date or Unix seconds. Registered at or after it.
limit, cursorPaging
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 marketingConsent is true.

Contacts

GET /contacts

One row per owner, newest first (by first registration).

Query parameterDescription
consenttrue to return only people who agreed to marketing email
sinceISO 8601 date or Unix seconds. Filters on the person's first registration.
limit, cursorPaging
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 fieldDescription
stateOfHealthPercentage from 0 to 100
cyclesCharge cycles, a whole number of 0 or more
statusoriginal, repurposed, re-used, remanufactured or waste
manufacturedOnDate 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.

EndpointWhat it returns
GET /api/dpp/{gtin}The passport as JSON-LD (schema.org and GS1). Optional: version, serial, lot, lang.
GET /api/dpp/{gtin}/verifyA signature check: whether every published version is unchanged since it was signed. Optional: serial.
GET /api/dpp/keysVeribl'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.

More ways to get help

  • Support

    Can't find the answer? Send us your question and the product or page it's about.

    Contact support
  • Specialists

    Get hands-on help with setup, migrating manuals, or designing your product pages.

    Book a call
  • Enterprise

    Dedicated onboarding, custom integrations, and regulatory guidance for large teams.

    Contact enterprise