A tag is one physical item of a product. Each tag carries its own title, description, and custom data, such as the batch it came from or the distributor that received it. This API reads those values and writes them, for one tag or for hundreds in one request.
Every request needs an API key. A key is limited to the tags of its own team.
Use cases
- Resync a spreadsheet. Your batch numbers live in a sheet or an ERP. Push them to the tags with one bulk update after each change, instead of editing tags by hand.
- Set the distributor after shipping. When a pallet leaves the warehouse, your shipping system has a record of which tags went where. Write the distributor onto those tags then, before the first customer scans one.
For a single tag in your hand, a team member can also scan it with the dashboard open on their phone. You can also ask the TAGBASE Agent on the dashboard to edit tag data in plain language: “Set the batch of the tag with edition 2 of 15 to B-7.”
Fields
A tag is a JSON:API resource of type tags.
| Field | Type | Notes |
|---|---|---|
id |
string | tag_-prefixed. The same id the dashboard’s tag URLs carry. |
protocol |
string | The chip the tag was provisioned for, e.g. ntag_424_dna. Read only. |
title |
string or null |
The tag’s own title, max 50 characters. null while the tag shows the product’s title. |
description |
string or null |
The tag’s own description. null while the tag shows the product’s description. |
data |
object or null |
The tag’s custom data. Category fields sit inside it as nested keys, so a field with the key distributor.name reads {"distributor": {"name": "…"}}. |
image_urls |
array of strings | The tag’s own images. Read only. |
The product relationship names the product the tag
belongs to:
{"data": {"type": "products", "id": "prd_…"}}.
Values are the tag’s own
The dashboard and the verification page fall back to the product where a tag
has no value of its own, and they render {{variables}} in the title and
description from data. The API does neither. It returns what the tag itself
stores, null and raw {{edition}} included, so the value you read is the
value a write replaces.
One exception: when the product’s category has an
entity field, data shows the record the tag links
to under the entity’s key, for example
{"partner": {"id": "ent_…", "name": "Brenner Pharma"}}.
List tags
GET /api/tagsLists the tags of the team, most recently changed first. Paging follows the shared pagination rules.
Query parameters
| Parameter | Type | Notes |
|---|---|---|
filter[product_id] |
string | Only the tags of this product, prd_…. |
page |
integer | The page, counting from 1. Defaults to 1. |
page_size |
integer | Tags per page. Defaults to 10, maximum 100. |
curl "https://verify.tagbase.io/api/tags?filter[product_id]=prd_4fm3xUcsDEUK77WDBMed6U&page=1&page_size=100" \
-H "Authorization: Bearer $TAGBASE_API_KEY" \
-H "Accept: application/vnd.api+json"const res = await fetch(
"https://verify.tagbase.io/api/tags?filter[product_id]=prd_4fm3xUcsDEUK77WDBMed6U&page=1&page_size=100",
{
headers: {
"Authorization": `Bearer ${process.env.TAGBASE_API_KEY}`,
"Accept": "application/vnd.api+json",
},
},
);
const { data, meta } = await res.json();$response = $client->get("https://verify.tagbase.io/api/tags", [
"headers" => [
"Authorization" => "Bearer " . getenv("TAGBASE_API_KEY"),
"Accept" => "application/vnd.api+json",
],
"query" => [
"filter" => ["product_id" => "prd_4fm3xUcsDEUK77WDBMed6U"],
"page" => 1,
"page_size" => 100,
],
]);
$page = json_decode((string) $response->getBody(), true);page =
Req.get!("https://verify.tagbase.io/api/tags",
params: ["filter[product_id]": "prd_4fm3xUcsDEUK77WDBMed6U", page: 1, page_size: 100],
headers: [
{"authorization", "Bearer #{System.fetch_env!("TAGBASE_API_KEY")}"},
{"accept", "application/vnd.api+json"}
]
).bodyResponse: 200 OK
A data array of tags, each in the shape of Retrieve a tag,
plus a meta object that describes the page:
{
"data": [
{
"type": "tags",
"id": "tag_TKWk9WD8BiXrurBwjeZ5NW",
"attributes": {
"protocol": "ntag_424_dna",
"title": null,
"description": null,
"data": { "batch": "B-7", "distributor": { "name": "Zanna Therapeutics" } },
"image_urls": []
},
"relationships": {
"product": { "data": { "type": "products", "id": "prd_4fm3xUcsDEUK77WDBMed6U" } }
}
}
],
"meta": { "total_count": 1, "page": 1, "page_size": 100 }
}Errors
| Status | When |
|---|---|
401 |
Missing, invalid, or revoked key. |
Retrieve a tag
GET /api/tags/:idcurl https://verify.tagbase.io/api/tags/tag_TKWk9WD8BiXrurBwjeZ5NW \
-H "Authorization: Bearer $TAGBASE_API_KEY" \
-H "Accept: application/vnd.api+json"const res = await fetch(
"https://verify.tagbase.io/api/tags/tag_TKWk9WD8BiXrurBwjeZ5NW",
{
headers: {
"Authorization": `Bearer ${process.env.TAGBASE_API_KEY}`,
"Accept": "application/vnd.api+json",
},
},
);
const tag = await res.json();$response = $client->get("https://verify.tagbase.io/api/tags/tag_TKWk9WD8BiXrurBwjeZ5NW", [
"headers" => [
"Authorization" => "Bearer " . getenv("TAGBASE_API_KEY"),
"Accept" => "application/vnd.api+json",
],
]);
$tag = json_decode((string) $response->getBody(), true);tag =
Req.get!("https://verify.tagbase.io/api/tags/tag_TKWk9WD8BiXrurBwjeZ5NW",
headers: [
{"authorization", "Bearer #{System.fetch_env!("TAGBASE_API_KEY")}"},
{"accept", "application/vnd.api+json"}
]
).bodyResponse: 200 OK
{
"data": {
"type": "tags",
"id": "tag_TKWk9WD8BiXrurBwjeZ5NW",
"attributes": {
"protocol": "ntag_424_dna",
"title": null,
"description": null,
"data": { "batch": "B-7", "distributor": { "name": "Zanna Therapeutics" } },
"image_urls": []
},
"relationships": {
"product": { "data": { "type": "products", "id": "prd_4fm3xUcsDEUK77WDBMed6U" } }
}
}
}Errors
| Status | When |
|---|---|
401 |
Missing, invalid, or revoked key. |
404 |
No such tag in your team. A tag of another team and a malformed id get the same answer. |
Update a tag
PATCH /api/tags/:idWrites title, description, or data. Send any of them, in any
combination. An attribute you send replaces the stored value as a whole, and
an attribute you leave out stays as it is. So send the full data object you
want the tag to hold, with the keys that did not change. Send null to clear
a title or description, and the tag shows the product’s again.
An entity field is the exception to replace-as-a-whole. Leave its key out and
the tag keeps its record. Send a record’s id, or the object you read, to link
the tag to that record, and null to unlink it. The key is the field’s name in
the category, for example reseller:
{ "batch": "B-7", "reseller": "ent_2GRYa5RKsC2ymvqr8Vi9Ds" }An id that is not one of your records answers 422, with the pointer on that
field, and changes nothing. A key that is not a field of the category is
stored as custom data.
The API checks data against the product’s
category the same way the dashboard form does.
If a required field is missing, the API rejects the whole request and changes
nothing.
Request
One resource under data, with the tag’s id:
{
"data": {
"type": "tags",
"id": "tag_TKWk9WD8BiXrurBwjeZ5NW",
"attributes": {
"title": "Lonafen, Box 12",
"data": { "batch": "B-7", "distributor": { "name": "Zanna Therapeutics" } }
}
}
}curl https://verify.tagbase.io/api/tags/tag_TKWk9WD8BiXrurBwjeZ5NW \
-X PATCH \
-H "Authorization: Bearer $TAGBASE_API_KEY" \
-H "Content-Type: application/vnd.api+json" \
-d '{ "data": { "type": "tags", "id": "tag_TKWk9WD8BiXrurBwjeZ5NW", "attributes": { "title": "Lonafen, Box 12", "data": { "batch": "B-7", "distributor": { "name": "Zanna Therapeutics" } } } } }'const res = await fetch(
"https://verify.tagbase.io/api/tags/tag_TKWk9WD8BiXrurBwjeZ5NW",
{
method: "PATCH",
headers: {
"Authorization": `Bearer ${process.env.TAGBASE_API_KEY}`,
"Content-Type": "application/vnd.api+json",
},
body: JSON.stringify({
data: {
type: "tags",
id: "tag_TKWk9WD8BiXrurBwjeZ5NW",
attributes: {
title: "Lonafen, Box 12",
data: { batch: "B-7", distributor: { name: "Zanna Therapeutics" } },
},
},
}),
},
);
const tag = await res.json();$response = $client->patch("https://verify.tagbase.io/api/tags/tag_TKWk9WD8BiXrurBwjeZ5NW", [
"headers" => [
"Authorization" => "Bearer " . getenv("TAGBASE_API_KEY"),
"Content-Type" => "application/vnd.api+json",
],
"json" => [
"data" => [
"type" => "tags",
"id" => "tag_TKWk9WD8BiXrurBwjeZ5NW",
"attributes" => [
"title" => "Lonafen, Box 12",
"data" => ["batch" => "B-7", "distributor" => ["name" => "Zanna Therapeutics"]],
],
],
],
]);
$tag = json_decode((string) $response->getBody(), true);tag =
Req.patch!("https://verify.tagbase.io/api/tags/tag_TKWk9WD8BiXrurBwjeZ5NW",
headers: [
{"authorization", "Bearer #{System.fetch_env!("TAGBASE_API_KEY")}"},
{"content-type", "application/vnd.api+json"}
],
json: %{
data: %{
type: "tags",
id: "tag_TKWk9WD8BiXrurBwjeZ5NW",
attributes: %{
title: "Lonafen, Box 12",
data: %{batch: "B-7", distributor: %{name: "Zanna Therapeutics"}}
}
}
}
).bodyResponse: 200 OK
The updated tag, in the shape of Retrieve a tag.
Errors
| Status | When |
|---|---|
400 |
The body has no data.attributes object. |
401 |
Missing, invalid, or revoked key. |
404 |
No such tag in your team. |
422 |
A value failed validation. source.pointer names it, and detail says why. Nothing changed. |
A 422 for a required category field left out of data looks like this:
{
"errors": [
{
"status": "422",
"title": "Unprocessable Entity",
"detail": "can't be blank",
"source": { "pointer": "/data/attributes/data/batch" }
}
]
}For a title over 50 characters, the pointer is /data/attributes/title.
Update many tags
PATCH /api/tagsUpdates 1 to 500 tags in one request. The batch is all or nothing: if one tag is unknown or one value fails the category check, no tag changes, and the error names the position of the entry that failed. Each entry takes the same attributes as Update a tag, with the same replace-as-a-whole rule.
Request
An array of resources under data, each with an id and attributes:
{
"data": [
{
"type": "tags",
"id": "tag_TKWk9WD8BiXrurBwjeZ5NW",
"attributes": { "data": { "batch": "B-7" } }
},
{
"type": "tags",
"id": "tag_2pNwX6kQ9rLdF4hVbYcT8a",
"attributes": { "data": { "batch": "B-8" } }
}
]
}curl https://verify.tagbase.io/api/tags \
-X PATCH \
-H "Authorization: Bearer $TAGBASE_API_KEY" \
-H "Content-Type: application/vnd.api+json" \
-d '{ "data": [ { "type": "tags", "id": "tag_TKWk9WD8BiXrurBwjeZ5NW", "attributes": { "data": { "batch": "B-7" } } }, { "type": "tags", "id": "tag_2pNwX6kQ9rLdF4hVbYcT8a", "attributes": { "data": { "batch": "B-8" } } } ] }'const res = await fetch("https://verify.tagbase.io/api/tags", {
method: "PATCH",
headers: {
"Authorization": `Bearer ${process.env.TAGBASE_API_KEY}`,
"Content-Type": "application/vnd.api+json",
},
body: JSON.stringify({
data: [
{
type: "tags",
id: "tag_TKWk9WD8BiXrurBwjeZ5NW",
attributes: { data: { batch: "B-7" } },
},
{
type: "tags",
id: "tag_2pNwX6kQ9rLdF4hVbYcT8a",
attributes: { data: { batch: "B-8" } },
},
],
}),
});
const tags = (await res.json()).data;$response = $client->patch("https://verify.tagbase.io/api/tags", [
"headers" => [
"Authorization" => "Bearer " . getenv("TAGBASE_API_KEY"),
"Content-Type" => "application/vnd.api+json",
],
"json" => [
"data" => [
[
"type" => "tags",
"id" => "tag_TKWk9WD8BiXrurBwjeZ5NW",
"attributes" => ["data" => ["batch" => "B-7"]],
],
[
"type" => "tags",
"id" => "tag_2pNwX6kQ9rLdF4hVbYcT8a",
"attributes" => ["data" => ["batch" => "B-8"]],
],
],
],
]);
$tags = json_decode((string) $response->getBody(), true)["data"];tags =
Req.patch!("https://verify.tagbase.io/api/tags",
headers: [
{"authorization", "Bearer #{System.fetch_env!("TAGBASE_API_KEY")}"},
{"content-type", "application/vnd.api+json"}
],
json: %{
data: [
%{
type: "tags",
id: "tag_TKWk9WD8BiXrurBwjeZ5NW",
attributes: %{data: %{batch: "B-7"}}
},
%{
type: "tags",
id: "tag_2pNwX6kQ9rLdF4hVbYcT8a",
attributes: %{data: %{batch: "B-8"}}
}
]
}
).body["data"]Response: 200 OK
The updated tags in a data array, in the order you sent them, each in the
shape of Retrieve a tag.
Errors
| Status | When |
|---|---|
400 |
data is empty or holds more than 500 entries, or an entry has no id or no attributes. |
401 |
Missing, invalid, or revoked key. |
404 |
One of the tags is not in your team. source.pointer names its position, /data/1. Nothing changed. |
422 |
A value on one of the tags failed the category check. source.pointer names the entry and the field, /data/1/attributes/data/batch. Nothing changed. |
Positions count from 0, so /data/1 is the second entry in your array.
Next: put the values you write into the tag’s title, or show them on your own domain. Custom data explains both.