A tag is the dynamic NFC identity bound to a physical item. You provision tags under a team; later, each tag is written onto a physical chip and, once in the field, scanned to produce verifications.
Fields
| Field | Type | Notes |
|---|---|---|
id |
string | tag_-prefixed, assigned by the platform and returned at creation. |
protocol |
string | Required at creation. The tag protocol to provision (see below). |
url |
string | Required at creation. The address written to the chip and opened when the tag is scanned. You choose it. |
comment |
string | Optional, max 50 characters. A human-readable label — makes it easy to see what any tag is at a glance, e.g. a product name. Writer apps can display it to identify which physical item a tag belongs to. |
session_duration |
integer | Optional, defaults to 600. How long a session on this tag stays open between its first and second scan, in seconds (1–3600). |
status |
string | Lifecycle state (see below). Returned when you retrieve a tag. |
configured_at |
string | ISO 8601 timestamp when the tag was written to a chip, or null. Returned when you retrieve a tag. |
A tag also carries a lifecycle status that advances as the tag is manufactured:
| Status | Meaning |
|---|---|
created |
Provisioned in the platform; not yet written to a chip. |
configured |
Written to a chip and ready to be scanned in the field. |
A tag can only be verified once it reaches configured — before that it has no
chip behind it, so a scan against it returns 404 (see
Verifications).
The protocol attribute
protocol selects which tag protocol the chip uses. The currently supported
values are:
| Value | Chip |
|---|---|
ntag_424_dna |
NTAG 424 DNA |
ntag_223_dna |
NTAG 223 DNA |
Use the identifier (left column) as the protocol value. Any other value is
rejected with 422.
The url attribute
url is the address written onto the chip and opened when the tag is scanned —
your verification landing page. You choose it freely — it can live on
your own custom domain, in whatever shape you like. The platform writes exactly
what you provide and enforces no format.
The platform assigns the id, so the URL can’t contain it. Keep your own
mapping from each url you send to the id returned for it.
Create tags
POST /api/v1/tags
Provision a batch of tags under the team whose key you present. Send a
protocol and url for each; the platform assigns an id and returns it. Tags
are stored in created status, and each one fires a
tag.created webhook.
Request
A JSON:API array under data, 1–500 resources per request. Each entry:
| Member | Type | Required | Notes |
|---|---|---|---|
attributes.protocol |
string | yes | The tag protocol identifier. |
attributes.url |
string | yes | The address to write to the chip. You choose it. |
attributes.comment |
string | no | A human-readable label, max 50 characters. |
attributes.session_duration |
integer | no | Session window in seconds, 1–3600. Defaults to 600 (10 minutes). |
{
"data": [
{
"type": "tags",
"attributes": {
"protocol": "ntag_424_dna",
"url": "https://zannatherapeutics.com/verify/lonafen/8a3f9c2b"
}
}
]
}
curl https://platform.tagbase.io/api/v1/tags \
-X POST \
-H "Authorization: Bearer $TAGBASE_API_KEY" \
-H "Content-Type: application/vnd.api+json" \
-d '{ "data": [ { "type": "tags", "attributes": { "protocol": "ntag_424_dna", "url": "https://zannatherapeutics.com/verify/lonafen/8a3f9c2b" } } ] }'
const res = await fetch("https://platform.tagbase.io/api/v1/tags", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.TAGBASE_API_KEY}`,
"Content-Type": "application/vnd.api+json",
},
body: JSON.stringify({
data: [
{
type: "tags",
attributes: {
protocol: "ntag_424_dna",
url: "https://zannatherapeutics.com/verify/lonafen/8a3f9c2b",
},
},
],
}),
});
const tags = (await res.json()).data;
$response = $client->post("https://platform.tagbase.io/api/v1/tags", [
"headers" => [
"Authorization" => "Bearer " . getenv("TAGBASE_API_KEY"),
"Content-Type" => "application/vnd.api+json",
],
"json" => [
"data" => [[
"type" => "tags",
"attributes" => [
"protocol" => "ntag_424_dna",
"url" => "https://zannatherapeutics.com/verify/lonafen/8a3f9c2b",
],
]],
],
]);
$tags = json_decode((string) $response->getBody(), true)["data"];
tags =
Req.post!("https://platform.tagbase.io/api/v1/tags",
headers: [
{"authorization", "Bearer #{System.fetch_env!("TAGBASE_API_KEY")}"},
{"content-type", "application/vnd.api+json"}
],
json: %{
data: [
%{
type: "tags",
attributes: %{
protocol: "ntag_424_dna",
url: "https://zannatherapeutics.com/verify/lonafen/8a3f9c2b"
}
}
]
}
).body["data"]
Response — 201 Created
A JSON:API array of the created tags. Each entry pairs the platform-assigned
id with the url it was created from, so you can map them back to your records.
{
"data": [
{
"type": "tags",
"id": "tag_abcdef0123456789",
"attributes": {
"url": "https://zannatherapeutics.com/verify/lonafen/8a3f9c2b",
"comment": null,
"session_duration": 600
}
}
]
}
Tags are returned in created status. Writing them onto physical chips happens
separately and asynchronously; your integration holds the ids in the meantime and
learns when each tag advances through webhooks —
tag.configured when it’s written and ready to scan, tag.configuration_failed
if a write fails.
Errors
| Status | When |
|---|---|
400 |
data is not an array of 1–500 entries, or any entry is missing protocol or url. |
401 |
Missing, invalid, or revoked key. |
422 |
Validation failed — e.g. a duplicate url or an unrecognized protocol. |
Retrieve a tag
GET /api/v1/tags/:id
Fetch a tag you provisioned, including its current lifecycle status. The key you
present must own the tag, or the platform responds 404.
curl https://platform.tagbase.io/api/v1/tags/tag_abcdef0123456789 \
-H "Authorization: Bearer $TAGBASE_API_KEY" \
-H "Accept: application/vnd.api+json"
const res = await fetch(
"https://platform.tagbase.io/api/v1/tags/tag_abcdef0123456789",
{
headers: {
"Authorization": `Bearer ${process.env.TAGBASE_API_KEY}`,
"Accept": "application/vnd.api+json",
},
},
);
const tag = await res.json();
$response = $client->get("https://platform.tagbase.io/api/v1/tags/tag_abcdef0123456789", [
"headers" => [
"Authorization" => "Bearer " . getenv("TAGBASE_API_KEY"),
"Accept" => "application/vnd.api+json",
],
]);
$tag = json_decode((string) $response->getBody(), true);
tag =
Req.get!("https://platform.tagbase.io/api/v1/tags/tag_abcdef0123456789",
headers: [
{"authorization", "Bearer #{System.fetch_env!("TAGBASE_API_KEY")}"},
{"accept", "application/vnd.api+json"}
]
).body
Response — 200 OK
{
"data": {
"type": "tags",
"id": "tag_abcdef0123456789",
"attributes": {
"url": "https://zannatherapeutics.com/verify/lonafen/8a3f9c2b",
"comment": "Lonafen 50mg",
"session_duration": 600,
"status": "configured",
"configured_at": "2026-06-08T12:34:56.123456Z"
}
}
}
Polling this endpoint is a fallback for tracking a tag’s lifecycle; webhooks are the push alternative and fire as the tag advances.
Errors
| Status | When |
|---|---|
401 |
Missing, invalid, or revoked key. |
404 |
No such tag under your team. |
Update a tag
PATCH /api/v1/tags/:id
Update a tag’s mutable attributes. The comment and session_duration are
always editable — they live only in the platform, not on the chip. The url can
only change while the tag is still created; once configured it is physically
on the chip and locked, and a url change responds 422.
Request
A single JSON:API resource under data:
{
"data": {
"type": "tags",
"id": "tag_abcdef0123456789",
"attributes": { "comment": "Lonafen 50mg" }
}
}
curl https://platform.tagbase.io/api/v1/tags/tag_abcdef0123456789 \
-X PATCH \
-H "Authorization: Bearer $TAGBASE_API_KEY" \
-H "Content-Type: application/vnd.api+json" \
-d '{ "data": { "type": "tags", "id": "tag_abcdef0123456789", "attributes": { "comment": "Lonafen 50mg" } } }'
const res = await fetch(
"https://platform.tagbase.io/api/v1/tags/tag_abcdef0123456789",
{
method: "PATCH",
headers: {
"Authorization": `Bearer ${process.env.TAGBASE_API_KEY}`,
"Content-Type": "application/vnd.api+json",
},
body: JSON.stringify({
data: {
type: "tags",
id: "tag_abcdef0123456789",
attributes: { comment: "Lonafen 50mg" },
},
}),
},
);
const tag = await res.json();
$response = $client->patch("https://platform.tagbase.io/api/v1/tags/tag_abcdef0123456789", [
"headers" => [
"Authorization" => "Bearer " . getenv("TAGBASE_API_KEY"),
"Content-Type" => "application/vnd.api+json",
],
"json" => [
"data" => [
"type" => "tags",
"id" => "tag_abcdef0123456789",
"attributes" => ["comment" => "Lonafen 50mg"],
],
],
]);
$tag = json_decode((string) $response->getBody(), true);
tag =
Req.patch!("https://platform.tagbase.io/api/v1/tags/tag_abcdef0123456789",
headers: [
{"authorization", "Bearer #{System.fetch_env!("TAGBASE_API_KEY")}"},
{"content-type", "application/vnd.api+json"}
],
json: %{
data: %{
type: "tags",
id: "tag_abcdef0123456789",
attributes: %{comment: "Lonafen 50mg"}
}
}
).body
Response — 200 OK
The updated tag, in the same shape as Retrieve a tag.
Errors
| Status | When |
|---|---|
400 |
The body is not a single JSON:API resource with attributes. |
401 |
Missing, invalid, or revoked key. |
404 |
No such tag under your team. |
422 |
Validation failed — e.g. the url of a configured tag, a duplicate url, a comment over 50 characters, or a session_duration outside 1–3600. |
Notes
- A tag can be fetched by id (above); there is no endpoint to list tags. Persist the returned ids when you create the batch.
- Tags belong to the team whose key created them. To keep tenants isolated, create each tenant’s tags with that tenant’s subteam key.