Skip to content

Tags API

List and manage tags, tag and untag creators, campaigns, accounts and videos one at a time or in bulk, and filter lists by tag.

Updated Oct 13, 2026 · 8 min read

Tags are free-form labels on creators, campaigns, accounts and videos. The organization keeps one list of tags (the vocabulary) for each entity type, so a creator tag can only go on creators. These endpoints are the same ones the MCP tag tools use (list_tags, add_tags, apply_tags and the rest). See the Tags guide for how people use them in the app.

Tags are not an account's tracking_hashtags (hashtag tracking mode), the hashtags in a video's caption, or the tags on Knowledge entries.

Entity types: creator, campaign, account, video. Colors: gray, blue, green, orange, purple, pink, red, yellow. A color of null shows as gray.

Permissions#

ActionNeeds
Read tags, and filter lists by tagThe read scope
Create tags, and tag or untag anythingThe write scope
Rename, recolor, merge and delete tagsThe write scope, and the key must act as an org admin (403 insufficient_role otherwise)

Every write accepts an Idempotency-Key header and is recorded in the API audit log.

Names#

A tag name is trimmed, and every run of spaces becomes one space. It must be 1 to 40 characters, with no commas, and it can't look like an id (a UUID). Names match case-insensitively: VIP, vip and Vip are one tag, and the tag keeps the casing it was created with.

Wherever an endpoint takes tags, each entry can be a tag's name or its id. An entry that is a UUID is treated as an id; anything else is a name. Names that have no tag yet are created on the fly when you add or set tags.

Limits#

LimitValue
Tags on one creator, campaign, account or video20
Tags per entity type in one organization500
Tags named in one request20
Items in one POST /api/v2/tags/apply500
Tags in one list filter20
Source tags in one merge20

Tag fields#

FieldTypeDescription
iduuidTag ID
entity_typecreator | campaign | account | videoWhat the tag is for
namestringThe name, in the casing it was created with
colorstring | nullOne of the colors above; null is gray
usage_countnumberHow many items carry the tag. Removed creators and accounts and excluded videos keep their tags but aren't counted
created_byuuid | nullThe user who created it
created_viaui | api | mcpWhere it was created: the app, the REST API or MCP
created_at, updated_atISO 8601

Creators, campaigns, accounts and videos carry a shorter form, { id, name, color }, in their tags array.


GET/api/v2/tags#

List the organization's tags with their usage counts. Needs the read scope.

bash
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/tags?entity_type=creator"
ParameterTypeDescription
entity_typecreator | campaign | account | videoOnly tags for this entity type. Leave it out for all four (grouped by type, each sorted by name).
qstringCase-insensitive part of the name (up to 100 characters).
JSON
{
  "data": [
    {
      "id": "uuid",
      "entity_type": "creator",
      "name": "Paid",
      "color": "green",
      "usage_count": 8,
      "created_by": "uuid",
      "created_via": "ui",
      "created_at": "2026-10-14T15:02:11Z",
      "updated_at": "2026-10-14T15:02:11Z"
    },
    {
      "id": "uuid",
      "entity_type": "creator",
      "name": "Q4 launch",
      "color": null,
      "usage_count": 12,
      "created_by": "uuid",
      "created_via": "api",
      "created_at": "2026-10-14T15:04:40Z",
      "updated_at": "2026-10-14T15:04:40Z"
    }
  ],
  "error": null,
  "meta": { "total": 2 }
}

The list isn't paginated.


POST/api/v2/tags#

Add a tag to the vocabulary without putting it on anything. Needs the write scope. You rarely need this: adding tags by name creates them.

bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"entity_type": "video", "name": "Sponsored", "color": "orange"}' \
  https://www.trackagoat.com/api/v2/tags
FieldTypeDescription
entity_typestringRequired.
namestringRequired. See Names.
colorstring | nullOptional. Defaults to gray.

Returns the tag. A new tag is 201 with meta.created: true. If a tag of that type already has the name (in any casing), it is returned with 200 and meta.created: false, and its color is left unchanged (recoloring needs an org admin, with PATCH).

HTTPCause
400invalid_body: a bad name (empty, over 40 characters, a comma, or an id) or an unknown entity_type or color.
403Key lacks the write scope.
422tag_vocabulary_full: the organization already has 500 tags of this type.

PATCH/api/v2/tags/{id}#

Rename a tag or change its color. The new name shows everywhere the tag is used. Needs the write scope and an org admin.

bash
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Paid creators", "color": "blue"}' \
  https://www.trackagoat.com/api/v2/tags/<uuid>
FieldTypeDescription
namestringNew name. See Names.
colorstring | nullNew color; null resets it to gray.

Send at least one. Returns the updated tag.

HTTPCause
400invalid_body.
403insufficient_role: the key acts as a member who isn't an org admin.
404not_found: no tag with that id in your organization.
409tag_name_taken: another tag of the same type already has that name. meta.existing_tag_id is its id. Merge the two instead.

DELETE/api/v2/tags/{id}#

Delete a tag and remove it from every item that carries it. This can't be undone. Needs the write scope and an org admin.

bash
curl -X DELETE \
  -H "Authorization: Bearer tga_<key>" \
  https://www.trackagoat.com/api/v2/tags/<uuid>
JSON
{ "data": { "id": "uuid", "deleted": true, "removed_from": 14 }, "error": null, "meta": null }

removed_from is how many items had the tag. 403 insufficient_role for a key that doesn't act as an org admin, 404 not_found when the tag isn't in your organization.


POST/api/v2/tags/merge#

Fold duplicate tags into one. Everything that carries a source tag gets the target tag (an item that already has the target keeps one), then the source tags are deleted. Needs the write scope and an org admin.

bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"target_tag_id": "<uuid of VIP>", "source_tag_ids": ["<uuid of vips>", "<uuid of VIP creators>"]}' \
  https://www.trackagoat.com/api/v2/tags/merge
FieldTypeDescription
target_tag_iduuidThe tag to keep.
source_tag_idsuuid[]1 to 20 tags to fold into the target and delete.
JSON
{
  "data": {
    "tag": { "id": "uuid", "entity_type": "creator", "name": "VIP", "color": "purple", "usage_count": 23, "created_by": "uuid", "created_via": "ui", "created_at": "2026-10-14T15:02:11Z", "updated_at": "2026-10-14T16:30:00Z" },
    "moved": 9,
    "deleted_tags": 2
  },
  "error": null,
  "meta": null
}

tag is the target as listed, moved is how many items gained the target tag, and deleted_tags is how many source tags were deleted.

HTTPCause
400invalid_body (including a merge whose only source is the target). tag_type_mismatch: the tags are for different entity types.
403insufficient_role.
404not_found: the target or a source tag isn't in your organization.

POST/api/v2/tags/apply#

Add tags to, or remove them from, up to 500 items of one entity type. Needs the write scope.

bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"entity_type": "creator", "entity_ids": ["<uuid1>", "<uuid2>", "<uuid3>"], "tags": ["Q4 launch", "Paid"]}' \
  https://www.trackagoat.com/api/v2/tags/apply
FieldTypeDescription
entity_typestringRequired.
entity_idsuuid[]1 to 500 items of that type.
tagsstring[]1 to 20 tag names or ids.
modeadd | removeadd (default) keeps each item's other tags and creates names that have no tag yet. remove takes these tags off.
JSON
{
  "data": {
    "entity_type": "creator",
    "entity_ids": ["uuid", "uuid"],
    "not_found_ids": ["uuid"],
    "tags": [
      { "id": "uuid", "name": "Q4 launch", "color": null },
      { "id": "uuid", "name": "Paid", "color": "green" }
    ],
    "added": 3,
    "removed": 0,
    "created_tags": ["Q4 launch"],
    "not_found_tags": []
  },
  "error": null,
  "meta": null
}
FieldDescription
entity_idsThe items found in your organization. Removed creators and accounts aren't.
not_found_idsRequested ids that aren't items of this type in your organization. They are skipped.
tagsThe tags the request named.
added, removedHow many item and tag pairs were added or removed. An item that already had a tag isn't counted again.
created_tagsNames this call created.
not_found_tagsIn remove mode, names or ids that matched no tag.
HTTPCause
400invalid_body. invalid_tag_name: a name breaks the rules (meta.invalid lists each input and its problem). unknown_tag_ids (add mode): an id isn't a tag of this type in your organization (meta.unknown_tag_ids).
404not_found: none of the entity_ids are in your organization (meta.not_found_ids).
422too_many_tags: an item would carry more than 20 tags. Nothing is written. tag_vocabulary_full: creating the new names would take the organization over 500 tags of this type.

One item's tags#

Each creator, campaign, account and video has a tags endpoint:

  • /api/v2/creators/{id}/tags
  • /api/v2/campaigns/{id}/tags
  • /api/v2/accounts/{id}/tags
  • /api/v2/videos/{id}/tags

All four return 404 not_found when the item isn't in your organization. Removed creators and accounts are 404, as they are for GET /api/v2/creators/{id} and GET /api/v2/accounts/{id}.

GET#

The item's tags, sorted by name. Needs the read scope.

bash
curl -H "Authorization: Bearer tga_<key>" \
  https://www.trackagoat.com/api/v2/creators/<uuid>/tags
JSON
{
  "data": [
    { "id": "uuid", "name": "Paid", "color": "green" },
    { "id": "uuid", "name": "Q4 launch", "color": null }
  ],
  "error": null,
  "meta": null
}

POST: add tags#

Adds tags and keeps the ones the item already has. Names with no tag yet are created. Needs the write scope.

bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"tags": ["Sponsored"]}' \
  https://www.trackagoat.com/api/v2/videos/<uuid>/tags

Body: tags, 1 to 20 names or ids.

PUT: set tags#

Replaces the item's tags with exactly this list. Tags not in the list are taken off, and an empty list clears them. Names with no tag yet are created. Needs the write scope.

bash
curl -X PUT \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"tags": ["Q4 launch", "Paid"]}' \
  https://www.trackagoat.com/api/v2/creators/<uuid>/tags

Body: tags, 0 to 20 names or ids.

DELETE: remove tags#

Takes tags off the item. The tags stay in the vocabulary. The tags go in a JSON body. Needs the write scope.

bash
curl -X DELETE \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"tags": ["Paid"]}' \
  https://www.trackagoat.com/api/v2/creators/<uuid>/tags

Body: tags, 1 to 20 names or ids.

Response#

POST, PUT and DELETE return the item's tags after the write, sorted by name, with counts in meta:

JSON
{
  "data": [
    { "id": "uuid", "name": "Q4 launch", "color": null }
  ],
  "error": null,
  "meta": { "added": 0, "removed": 1, "created_tags": [] }
}
meta fieldDescription
added, removedHow many tags were put on or taken off.
created_tagsNames this call created.
not_foundDELETE only, when present: names or ids that matched no tag.
HTTPCause
400invalid_body. invalid_tag_name (meta.invalid). unknown_tag_ids (POST and PUT): an id isn't a tag of this type in your organization (meta.unknown_tag_ids).
404not_found.
422too_many_tags: the item would carry more than 20 tags. tag_vocabulary_full.

Filtering lists by tag#

These list endpoints take a tag filter and return tags ([{ id, name, color }]) on every item:

GET /api/v2/creators/{id}, GET /api/v2/accounts/{id} and GET /api/v2/videos/{id} also return tags.

ParameterDescription
tagsComma-separated tag names (any casing) or ids, up to 20. Tag names can't contain commas, so a comma always separates two tags.
tags_matchany (default): items with at least one of the tags. all: items with every one of them.
bash
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/creators?tags=q4%20launch,paid&tags_match=all"

A name that matches no tag is ignored with any; if none of them match, the list is empty. With all, any unknown name makes the list empty. The filter runs in the database, so limit, cursor and hasMore work as they do without it. More than 20 tags is 400 invalid_query.