# Authentication

API keys, bearer tokens, scope, and auth errors.


Every request is authenticated with an **API key** belonging to a team. The
key both identifies the team and scopes what it can see.

## Obtaining a key

Keys come with a team, and there are two kinds of team.

A sandbox team is free and self-service. [Sign up](/dashboard/sign-in) and
mint a key from the dashboard. Sandbox tags are virtual: the platform
provisions them instantly with everything a physical tag would carry, and the
browser emulates every scan, so nothing has to ship before you start. You can
build your entire integration against the sandbox, from the first API request
to a full verification flow.

A production team and its physical dynamic NFC tags come from TAGBASE.
[Contact TAGBASE](mailto:contact@tagbase.io) to get your root team and its
API keys. Your integration carries over unchanged, because sandbox and
production speak the same API.

From there you're self-service for tenants: use your root team's key to
[create a subteam](/docs/api/teams) for each customer or tenant, and the
response hands back that subteam's own key. So you get your root team and
key to begin with, and mint the rest yourself.

## Key format

A key is two parts, a public key id and a secret, joined by a colon:

```
key_VfUttEQbh1ceW7jtwV66rF:superstrongrandomsecret
```

You receive the full string **once**, when the key is minted (see
[Teams](/docs/api/teams)). The platform stores only a hash of the secret
and can never show it to you again. Treat the whole string as a credential: store
it somewhere secret, never commit it, never put it in a URL.

## Sending the key

Pass the full `key_id:secret` string as a bearer token:

<!-- tabs -->
```bash cURL
curl https://platform.tagbase.io/api/v1/tags \
  -X POST \
  -H "Authorization: Bearer key_VfUttEQbh1ceW7jtwV66rF:superstrongrandomsecret" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{ "data": [ { "type": "tags", "attributes": { "protocol": "ntag_424_dna", "url": "https://zanna.example/verify/lonafen/8a3f9c2b" } } ] }'
```
```js
await fetch("https://platform.tagbase.io/api/v1/tags", {
  method: "POST",
  headers: {
    "Authorization": "Bearer key_VfUttEQbh1ceW7jtwV66rF:superstrongrandomsecret",
    "Content-Type": "application/vnd.api+json",
  },
  body: JSON.stringify({
    data: [{ type: "tags", attributes: { protocol: "ntag_424_dna", url: "https://zanna.example/verify/lonafen/8a3f9c2b" } }],
  }),
});
```
```php
$client->post("https://platform.tagbase.io/api/v1/tags", [
    "headers" => [
        "Authorization" => "Bearer key_VfUttEQbh1ceW7jtwV66rF:superstrongrandomsecret",
        "Content-Type"  => "application/vnd.api+json",
    ],
    "json" => [
        "data" => [["type" => "tags", "attributes" => ["protocol" => "ntag_424_dna", "url" => "https://zanna.example/verify/lonafen/8a3f9c2b"]]],
    ],
]);
```
```elixir
Req.post!("https://platform.tagbase.io/api/v1/tags",
  headers: [
    {"authorization", "Bearer key_VfUttEQbh1ceW7jtwV66rF:superstrongrandomsecret"},
    {"content-type", "application/vnd.api+json"}
  ],
  json: %{data: [%{type: "tags", attributes: %{protocol: "ntag_424_dna", url: "https://zanna.example/verify/lonafen/8a3f9c2b"}}]}
)
```
<!-- /tabs -->

## Scope

A key sees **only its own team's resources**. This is the isolation boundary
for [subteams](/docs/overview/resource-model): a subteam's key can create
and verify tags under that subteam and nothing else. Presenting a key for a
tag owned by a different team returns `404 Not Found`: the platform doesn't
distinguish "not yours" from "doesn't exist".

## Auth errors

A missing, malformed, revoked, or unrecognized key returns `401`:

```json
{
  "errors": [
    { "status": "401", "title": "Unauthorized" }
  ]
}
```

This covers every failure mode: no `Authorization` header, a header that isn't
`Bearer <key_id>:<secret>`, a secret that doesn't match, or a key that has been revoked.

## Rotation

Each team can hold more than one active key, so you can rotate without
downtime: provision the replacement, move your traffic over, then retire the old
one.

> Self-service key management endpoints (create / list / revoke a key on an
> existing team) are not part of the public API yet. Today a key is minted
> together with its team. Until they ship, rotation on an existing team is
> handled by TAGBASE. Plan key storage so swapping the value is a config change
> on your side.

