TAGBASE

API

What the verify API does, and the conventions every endpoint shares.

View as Markdown

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/api

All 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+json

A 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.