A team owns tags and API keys. The team that owns your key is your root team; teams you create through the API are subteams of it — the unit of multi-tenancy. Creating a subteam also mints its first API key.
Migrating from the accounts API? Teams were previously called accounts. The old
/api/v1/accountsroutes (andacc_-prefixed ids) keep working until 2026-10-14 and answer withDeprecation/Sunsetheaders plus ameta.deprecationnotice. Please update to/api/v1/teams— resource typeteams, id prefixtea_— as soon as possible.
When to use subteams
Subteams are optional, and for most integrations the answer is no — you can do everything with your root team’s key. Reach for one only when you have a concrete separation requirement.
What a subteam isolates is tags and the keys that read them: a key sees only
its own team’s tags, and a tag lookup or verification across teams returns
404. It’s also the billing and quota boundary — a subteam per customer gives
each tenant its own bill.
Common reasons to create one:
- Per-customer isolation (multi-tenant / reseller). You serve multiple independent customers whose tags must stay walled off from each other — one subteam per customer, each with its own key.
- Business-unit or regional separation. One organization that needs hard separation between legal entities or regions.
- Environment separation. A dedicated subteam for test/staging tags so they never mix with production — useful even for a single-customer integration.
Skip them if you can’t point to two parties — or environments — that must not see each other’s tags. A single application with one set of tags should just use the root team directly; a subteam would only add key management for no benefit.
Decide before you provision at scale. A tag is owned permanently by the team that created it, and there’s no endpoint to move it to another team later. So if a tenant or environment will ever need its own isolated tags, create its subteam first and provision under that subteam’s key — you can’t retrofit the split onto tags that already exist.
Fields
| Field | Type | Notes |
|---|---|---|
id |
string | tea_-prefixed, assigned by the platform. |
name |
string | Required. 1–100 characters. |
A subteam is always created beneath the team whose key you present; you don’t pass a parent — it’s inferred from the key.
Create a subteam
POST /api/v1/teams
Request
{
"data": {
"type": "teams",
"attributes": {
"name": "Metropolitan Museum — Night Watch"
}
}
}
name is the only required attribute. The request also accepts optional profile
attributes if you want to store them on the team: company, vat,
country, address, city, state, zip_code.
curl https://platform.tagbase.io/api/v1/teams \
-X POST \
-H "Authorization: Bearer $TAGBASE_API_KEY" \
-H "Content-Type: application/vnd.api+json" \
-d '{ "data": { "type": "teams", "attributes": { "name": "Metropolitan Museum — Night Watch" } } }'
const res = await fetch("https://platform.tagbase.io/api/v1/teams", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.TAGBASE_API_KEY}`,
"Content-Type": "application/vnd.api+json",
},
body: JSON.stringify({
data: { type: "teams", attributes: { name: "Metropolitan Museum — Night Watch" } },
}),
});
const team = await res.json();
$response = $client->post("https://platform.tagbase.io/api/v1/teams", [
"headers" => [
"Authorization" => "Bearer " . getenv("TAGBASE_API_KEY"),
"Content-Type" => "application/vnd.api+json",
],
"json" => [
"data" => ["type" => "teams", "attributes" => ["name" => "Metropolitan Museum — Night Watch"]],
],
]);
$team = json_decode((string) $response->getBody(), true);
team =
Req.post!("https://platform.tagbase.io/api/v1/teams",
headers: [
{"authorization", "Bearer #{System.fetch_env!("TAGBASE_API_KEY")}"},
{"content-type", "application/vnd.api+json"}
],
json: %{data: %{type: "teams", attributes: %{name: "Metropolitan Museum — Night Watch"}}}
).body
Response — 201 Created
The new subteam, plus its freshly minted API key as an included
api_keys resource. The key’s secret is shown here and
nowhere else — capture it now.
{
"data": {
"type": "teams",
"id": "tea_abcdef0123456789",
"attributes": { "name": "Metropolitan Museum — Night Watch" },
"relationships": {
"api_keys": {
"data": [ { "type": "api_keys", "id": "key_abcdef0123456789" } ]
}
}
},
"included": [
{
"type": "api_keys",
"id": "key_abcdef0123456789",
"attributes": { "secret": "key_abcdef0123456789:superstrongrandomsecret" }
}
]
}
Store included[].attributes.secret immediately — it’s the credential you’ll use
for every request made on behalf of this subteam.
Errors
| Status | When |
|---|---|
400 |
The body has no data.attributes object. |
401 |
Missing, invalid, or revoked key. |
422 |
Validation failed — e.g. name is missing or out of length. |
Retrieve a team
GET /api/v1/teams/:id
Fetch your own team, or a subteam you created. The key you present must own
the team — be its root team, or be the team itself — otherwise the
platform responds 404.
curl https://platform.tagbase.io/api/v1/teams/tea_abcdef0123456789 \
-H "Authorization: Bearer $TAGBASE_API_KEY" \
-H "Accept: application/vnd.api+json"
const res = await fetch(
"https://platform.tagbase.io/api/v1/teams/tea_abcdef0123456789",
{
headers: {
"Authorization": `Bearer ${process.env.TAGBASE_API_KEY}`,
"Accept": "application/vnd.api+json",
},
},
);
const team = await res.json();
$response = $client->get("https://platform.tagbase.io/api/v1/teams/tea_abcdef0123456789", [
"headers" => [
"Authorization" => "Bearer " . getenv("TAGBASE_API_KEY"),
"Accept" => "application/vnd.api+json",
],
]);
$team = json_decode((string) $response->getBody(), true);
team =
Req.get!("https://platform.tagbase.io/api/v1/teams/tea_abcdef0123456789",
headers: [
{"authorization", "Bearer #{System.fetch_env!("TAGBASE_API_KEY")}"},
{"accept", "application/vnd.api+json"}
]
).body
Response — 200 OK
The team, with its active api_keys as relationship
references. Key secrets are never returned here — a secret is shown only once,
when the key is minted. Read a key’s metadata with
GET /api/v1/api_keys/:id.
{
"data": {
"type": "teams",
"id": "tea_abcdef0123456789",
"attributes": { "name": "Metropolitan Museum — Night Watch" },
"relationships": {
"api_keys": {
"data": [ { "type": "api_keys", "id": "key_abcdef0123456789" } ]
}
}
}
}
Errors
| Status | When |
|---|---|
401 |
Missing, invalid, or revoked key. |
404 |
No such team, or it isn’t owned by the team you present. |
Notes
- A created team can be fetched by id (above); there is no endpoint to list
teams or to update one. Record the returned
idand key when you create it. - Subteams nest one level beneath the presenting team; deeper hierarchies aren’t exposed through the API.