# Tags

Read and write the values of every tag, one at a time or in bulk.


A **tag** is one physical item of a product. Each tag carries its own title,
description, and [custom data](/docs/dashboard/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](/docs/api/authentication). 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](#update-many-tags) 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](/docs/dashboard/custom-data#editing-a-tag-by-scanning-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](/docs/api/products) 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](/docs/dashboard/entities) 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/tags
```

Lists the tags of the team, most recently changed first. Paging follows
the shared [pagination rules](/docs/api/introduction#pagination).

### 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`. |

<!-- tabs -->
```bash cURL
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"
```
```js
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();
```
```php
$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);
```
```elixir
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"}
    ]
  ).body
```
<!-- /tabs -->

### Response: `200 OK`

A `data` array of tags, each in the shape of [Retrieve a tag](#retrieve-a-tag),
plus a `meta` object that describes the page:

```json
{
  "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/:id
```

<!-- tabs -->
```bash cURL
curl https://verify.tagbase.io/api/tags/tag_TKWk9WD8BiXrurBwjeZ5NW \
  -H "Authorization: Bearer $TAGBASE_API_KEY" \
  -H "Accept: application/vnd.api+json"
```
```js
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();
```
```php
$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);
```
```elixir
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"}
    ]
  ).body
```
<!-- /tabs -->

### Response: `200 OK`

```json
{
  "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/:id
```

Writes `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`:

```json
{ "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](/docs/dashboard/categories) 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`:

```json
{
  "data": {
    "type": "tags",
    "id": "tag_TKWk9WD8BiXrurBwjeZ5NW",
    "attributes": {
      "title": "Lonafen, Box 12",
      "data": { "batch": "B-7", "distributor": { "name": "Zanna Therapeutics" } }
    }
  }
}
```

<!-- tabs -->
```bash cURL
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" } } } } }'
```
```js
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();
```
```php
$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);
```
```elixir
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"}}
        }
      }
    }
  ).body
```
<!-- /tabs -->

### Response: `200 OK`

The updated tag, in the shape of [Retrieve a tag](#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:

```json
{
  "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/tags
```

Updates 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](#update-a-tag), with the same replace-as-a-whole
rule.

### Request

An array of resources under `data`, each with an `id` and `attributes`:

```json
{
  "data": [
    {
      "type": "tags",
      "id": "tag_TKWk9WD8BiXrurBwjeZ5NW",
      "attributes": { "data": { "batch": "B-7" } }
    },
    {
      "type": "tags",
      "id": "tag_2pNwX6kQ9rLdF4hVbYcT8a",
      "attributes": { "data": { "batch": "B-8" } }
    }
  ]
}
```

<!-- tabs -->
```bash cURL
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" } } } ] }'
```
```js
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;
```
```php
$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"];
```
```elixir
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"]
```
<!-- /tabs -->

### Response: `200 OK`

The updated tags in a `data` array, in the order you sent them, each in the
shape of [Retrieve a tag](#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](/docs/dashboard/custom-data#using-the-values) explains
both.

