Skip to main content

Tags and tag data

Tag data works like a spreadsheet. Fields are the columns. Each tag holds values for some or all of them. See Tag Data for how it behaves in the hub.

MethodPathDoes
GET/tagsList tags with their current data and zone
GET/tags/{epc}/dataOne tag's current values
PUT/tags/{epc}/dataSet or clear values on one tag
GET/tags/{epc}/historyEvery value a tag's fields have held
GET/fieldsList fields
POST/fieldsAdd a field
PATCH/fields/{key}Rename a field, or make it the name field
DELETE/fields/{key}Delete a field and its values
POST/tags/importImport a CSV
GET/tags/exportExport every tag as CSV
POST/tags/bulk-deleteForget tags entirely

List tags​

GET /tags

Returns up to 500 tags, most recently seen first.

Query
qOnly tags whose EPC or any field value contains this text (case-insensitive).
hasData=falseOnly tags that have no data yet. Useful to find tags that need labelling.
[
{
"id": "0b7f0b8e-2c55-4a8b-9b1e-6d0f7e1c2a90",
"epc": "E2801160600002084F1A3C21",
"label": "BRK-2210",
"targetType": null,
"attributes": { "sku": "BRK-2210", "qty": "12", "description": "Brake caliper, front left" },
"zoneName": "Receiving",
"firstSeenAt": "2026-10-09T08:12:44.102Z",
"lastSeenAt": "2026-10-10T14:03:22.418Z"
}
]
Field
labelThe value of the name field, or null.
attributesCurrent values. In this list, every value is a string, so a number arrives as "12". Use GET /tags/{epc}/data for typed values.
zoneNameThe zone the tag was last seen in, or null if it has never been placed.
targetType"asset" if the tag is linked to an asset, otherwise null.

For the complete set of tags rather than the 500 most recent, use export.

Read one tag's data​

GET /tags/{epc}/data
{
"epc": "E2801160600002084F1A3C21",
"tagId": "0b7f0b8e-2c55-4a8b-9b1e-6d0f7e1c2a90",
"attributes": { "sku": "BRK-2210", "qty": 12, "description": "Brake caliper, front left", "hazardous": false }
}

Values here keep their stored type: numbers are JSON numbers and true/false values are booleans. An EPC Titan has never heard of returns 200 with empty attributes and no tagId. It isn't an error.

Set values on one tag​

PUT /tags/{epc}/data

The body is an object of field key to value, with every value sent as a string:

curl -X PUT "$API/tags/E2801160600002084F1A3C21/data" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"qty": "11", "bin": "C-04-2", "notes": ""}'
  • Only the fields you send change. Fields you leave out keep their values.
  • An empty string clears a field on this tag. The old value goes to history.
  • Types are worked out for you: "11" is stored as the number 11, "true"/"yes"/"false"/"no" as booleans, and anything with a leading zero, like "00789", stays text.
  • New fields are created automatically: send a key that doesn't exist and it becomes a column.
  • Unknown EPCs are created, so you can load data before a tag has ever been read.

Returns the tag's full set of current values, in the same shape as the GET.

To update thousands of tags, use CSV import instead of one request per tag.

Tag data history​

GET /tags/{epc}/history

Every value every field has held on this tag, newest first. A value that's still current has no validTo.

[
{ "key": "qty", "value": 11, "validFrom": "2026-10-10T14:10:00.000Z" },
{ "key": "qty", "value": 12, "validFrom": "2026-10-09T08:15:31.000Z", "validTo": "2026-10-10T14:10:00.000Z" }
]

List fields​

GET /fields
[
{
"id": "a6e1f3c4-5b0d-4f7e-9c2a-1d8b7e6f5a40",
"key": "sku",
"label": "SKU",
"dataType": "text",
"options": null,
"displayOrder": 0,
"showInTable": true,
"isLabel": true
}
]

dataType is Titan's guess from the first value it saw: text, number, boolean or url. It's informational only. You can still store any value in any field.

Add a field​

POST /fields
{ "label": "Unit price" }

Creates the field with key unit_price and returns it with 201. You rarely need this, because writing a value to a new key creates the field anyway.

Rename a field, or make it the name field​

PATCH /fields/{key}
{ "label": "Part number" }

Renaming changes the key too (sku becomes part_number), and existing values move with it. Returns the updated field. Renaming to a name that's already in use returns 409.

{ "isLabel": true }

Makes this field the name field: its value labels the tag throughout the hub, and appears as label in tag lists and live events. Only one field can be the name field. Returns the full field list.

Delete a field​

DELETE /fields/{key}

Removes the field and its values from every tag. Returns 204.

Import a CSV​

POST /tags/import
Content-Type: multipart/form-data

Send the file in a form field named file. Add ?dryRun=true to check the file without changing anything.

curl -X POST "$API/tags/import?dryRun=true" \
-H "Authorization: Bearer $KEY" \

The file needs a header row containing epc, and every other column becomes a field. Blank cells leave a field unchanged. They don't clear it. EPCs must be 8 to 64 hex characters, and each may appear once. The limits are 100,000 rows and 64 MB.

{
"total": 1250,
"imported": 1180,
"updated": 68,
"fields": ["bin", "description", "sku"],
"errors": [
{ "line": 17, "epc": "E28011606000", "reason": "duplicate of line 4" },
{ "line": 902, "reason": "empty epc" }
],
"dryRun": true
}

imported counts tags new to Titan, and updated counts ones that already existed. Rows listed in errors are skipped, and the rest are applied. A file that can't be read at all (no epc column, too many rows) returns 400.

Export all tag data as CSV​

GET /tags/export

Returns text/csv with one row per tag (every tag, not just the latest 500) and one column per field. The first column is epc. The format is the same one the importer accepts.

curl -o tags.csv "$API/tags/export" -H "Authorization: Bearer $KEY"

Delete tags​

POST /tags/bulk-delete
{ "epcs": ["E2801160600002084F1A3C21", "E2801160600002084F1A3C22"] }

Forgets these tags completely: their data, history, read history, location and any asset link. A deleted tag that's read again comes back as a new tag with no data. You can send up to 10,000 EPCs per request. This can't be undone.

{ "deleted": 2 }