With the verify API, your own systems read and write the tags, products, and verifications of your team. A warehouse system writes batch numbers to tags as pallets ship, and a reporting tool pulls the week’s scans into a report. The API is server to server: a backend sends a request with an API key and gets a JSON:API document back. You create keys under API Keys on the dashboard.
Conventions
Every endpoint follows the same rules, so after your first request the rest look the same.
Base URL
https://verify.tagbase.io/apiAll paths in this reference are relative to that base. The path carries no
version. A custom domain changes
nothing here: the API answers on verify.tagbase.io for every team.
Content type
Send Content-Type: application/vnd.api+json on a request with a body.
Accept is optional; a response always comes back as
application/vnd.api+json. If you send Accept, use that media type or
application/json. Another value gets 406 Not Acceptable.
Content-Type: application/vnd.api+jsonA request body is one JSON:API document: a data member holding a resource
with a type, an id, and an attributes map. The
bulk update sends an array of those under
data.
{
"data": {
"type": "tags",
"id": "tag_TKWk9WD8BiXrurBwjeZ5NW",
"attributes": { "...": "..." }
}
}Identifiers
Every resource id is a URL-safe string with a type prefix: key_ for an API
key, tag_ for a tag, prd_ for a product, and vrf_ for a verification.
The prefix names the resource type; treat the rest as opaque. The ids match
the ones in the dashboard’s URLs, so an id you copy from the address bar works
in a request.
Behind the prefix sits a UUID in a compact encoding. The API accepts the raw
UUID in a path too, so GET /tags/d52352c7-502b-4c8a-83dc-61e742d9457f and
GET /tags/tag_TKWk9WD8BiXrurBwjeZ5NW read the same tag. Every response
carries the prefixed form. Store that one.
Errors
Errors come back as a JSON:API errors array. Each entry has an HTTP status
and a short title:
{
"errors": [
{ "status": "401", "title": "Unauthorized" }
]
}The HTTP response status matches the status field. The statuses you will
see:
| Status | Meaning |
|---|---|
200 |
The request succeeded. The body holds the resource, or the page you asked for. |
400 |
The body is malformed: no data.attributes object, or a bulk update with an empty array or more than 500 entries. |
401 |
Missing, invalid, or revoked API key. See Authentication. |
404 |
No such resource in your team. A resource of another team and a malformed id get the same answer. |
422 |
The request was well formed, but a value failed validation. Nothing changed. |
429 |
Too many requests from your address. See Rate limiting. |
A 422 names the value that failed. Each entry carries a detail with the
reason and a source.pointer, a
JSON Pointer into the document you
sent:
{
"errors": [
{
"status": "422",
"title": "Unprocessable Entity",
"detail": "can't be blank",
"source": { "pointer": "/data/attributes/data/batch" }
}
]
}The pointer follows the shape of your request. In a single update it starts
at /data/attributes, so a missing category field batch reads
/data/attributes/data/batch. In a bulk update it starts at the entry’s
position, counted from 0, so the same field on the second entry reads
/data/1/attributes/data/batch. A 404 in a bulk update points at the entry
too, /data/1, so you know which tag was unknown.
Idempotency
The API creates nothing. It reads with GET and writes with PATCH, and both
are safe to repeat. A PATCH replaces each attribute you send as a whole, so
the same body sent twice leaves the resource in the same state as the body
sent once. When a request times out or the connection drops, send it again.
The bulk update is all or nothing. When one entry fails, no tag changes, and the error points at that entry. Fix the entry and send the whole batch again.
When you did get a response, act on its status:
400,401,404: the same request fails the same way. Fix the request instead of repeating it.422: nothing changed. Fix the value the pointer names, then send again.429: wait, then send again.
Rate limiting
The API accepts about 600 requests a minute from one client address. Past
that it answers 429 Too Many Requests as plain text. The body is not a
JSON:API error document, and the response carries no rate-limit headers, so
there is nothing to inspect ahead of time: back off and retry after a pause. The limit is generous for
interactive traffic. A sync of a large catalogue should pace itself, and the
bulk update changes up to 500 tags in one
request, so it stays inside the budget.
Pagination
Every list endpoint takes page and page_size query parameters and reports
where you are in a meta object:
{
"data": ["..."],
"meta": { "total_count": 250, "page": 1, "page_size": 100 }
}page counts from 1 and defaults to 1. page_size defaults to 10, with a
maximum of 100; ask for more and you get 100 back. total_count counts the
rows that match your filters, so a request with page_size=1 is a cheap way
to count. Walk the pages until you have collected total_count records.
Each list has a fixed order:
| List | Order |
|---|---|
| Tags | Most recently changed first. A new tag counts as changed. |
| Products | By title. |
| Verifications | Newest first. |
Code samples
Each request in this reference comes as cURL, JavaScript, PHP, and Elixir.
The JavaScript samples use fetch. The PHP samples use a
Guzzle client, $client = new GuzzleHttp\Client().
The Elixir samples use Req. Every sample reads the
key from the TAGBASE_API_KEY environment variable.
Next: create a key and send it with every request. Authentication covers both.