# Creators

> List, add, update, and remove creators. Each creator owns one or more platform accounts.

Since Trackagoat 2.0, a **creator** is a person or brand that owns one or more platform **accounts** (`tiktok` or `instagram`; the enum also accepts `youtube`, which is reserved and not supported). A creator has no handle of its own. It embeds an `accounts[]` array and exposes a derived top-level `handle` (the primary account's handle: TikTok first, else the earliest-created active account). Handle and tracking configuration live on the account. See [Accounts](https://www.trackagoat.com/docs/api-reference/accounts.md).

## GET /api/v2/creators

List creators being tracked in your organization, with their accounts embedded.

### Request

```bash
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/creators?project_id=<uuid>"
```

### Query parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `project_id` | uuid | None | Filter to a specific project |
| `platform` | `tiktok` \| `instagram` \| `youtube` | None | Only creators with an account on this platform |
| `search` | string | None | Partial match on creator display name **or** any account handle |
| `limit` | number | 50 | Items per page (max 100) |
| `cursor` | string | None | Pagination cursor |
| `include_pin` | boolean | `false` | Include entrance PINs in `share_link`. Requires the `admin` scope; `403` otherwise |

### Response fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | uuid | Creator ID |
| `project_id` | uuid | Project this creator belongs to |
| `org_id` | uuid | Owning organization ID |
| `display_name` | string \| null | Creator display name |
| `handle` | string \| null | Primary account's handle (derived, TikTok first, else earliest) |
| `avatar_url` | string \| null | Profile image URL |
| `readme` | string \| null | Markdown notes (rendered in the Notes tab) |
| `metadata` | object | Agent-writable structured data (`{}` by default) |
| `is_active` | boolean | Whether the creator is actively tracked |
| `follower_count` | number \| null | Followers summed across the creator's accounts |
| `following_count` | number \| null | Following count summed across accounts |
| `total_likes_count` | number \| null | Cumulative likes summed across accounts |
| `video_count` | number \| null | Posts on the platforms: the profile post count each platform reports, summed across accounts. Not the number of tracked posts |
| `tracked_post_count` | number | Posts trackagoat counts in metrics: accepted, tracking on, on an active account. See [How metrics are calculated](https://www.trackagoat.com/docs/guides/metrics.md) |
| `pending_review_count` | number | Posts awaiting review in the inbox; not counted until accepted |
| `accounts` | array | The creator's platform accounts. See [Accounts](https://www.trackagoat.com/docs/api-reference/accounts.md#account-fields) |
| `share_link` | object \| null | The creator's PIN-gated public page, or `null` if they don't have one. See [Share Links](https://www.trackagoat.com/docs/api-reference/share-links.md) |
| `created_at` | ISO 8601 | When creator was added |
| `updated_at` | ISO 8601 | Last update |

Creator-level counts are the **sum across the creator's accounts**. Add an Instagram account and the creator's follower total grows to include it.

### Share links

`share_link` carries the creator's shareable page. The URL is always `{site}/share/creator/{creator_id}`, so you can construct it from any creator id without a lookup.

The entrance `pin` is **omitted** unless the request passes `include_pin=true` **and** the key holds the `admin` scope; anything else returns `403 insufficient_scope`. See [Share Links](https://www.trackagoat.com/docs/api-reference/share-links.md) for the full rules and the management endpoints.

---

## POST /api/v2/creators

Create a creator with **one or more accounts** (at least one is required). Requires the `write` scope. Dispatches a profile + discovery scrape automatically: the scraper fans out to every account.

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "project_id": "<uuid>",
    "display_name": "Charli",
    "accounts": [
      {"platform": "tiktok", "handle": "charlidamelio"},
      {"platform": "instagram", "handle": "charlidamelio", "tracking_mode": "selective"}
    ]
  }' \
  "https://www.trackagoat.com/api/v2/creators"
```

### Body

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `project_id` | uuid | required | Project to create the creator in. |
| `display_name` | string | first account's handle | Creator display name (max 100 chars). |
| `metadata` | object | `{}` | Arbitrary JSON for your own identifiers, so an external system can map its records back to this creator. Also settable later via `PATCH`, which replaces the whole object rather than merging. |
| `accounts` | array | required | 1 to 20 account objects (see below). |

Each object in `accounts[]`:

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `platform` | `tiktok` \| `instagram` \| `youtube` | required | Which platform this account is on. |
| `handle` | string | required | Platform `@username`, or a profile URL on `platform` (max 200 chars). Normalized: leading `@` stripped, lowercased. See [accepted profile links](https://www.trackagoat.com/docs/guides/accounts.md#accepted-profile-links). |
| `tracking_mode` | `auto` \| `selective` \| `hashtag` | `auto` | `auto`: track all videos. `selective`: new videos land in the inbox for review. `hashtag`: auto-track videos whose caption contains a tracked hashtag. |
| `tracking_hashtags` | `string[]` | `[]` | Required (non-empty) when `tracking_mode` is `hashtag`. Normalized server-side and by a database trigger: NFC + lowercase, leading `#` stripped, and characters that can't appear in a hashtag removed (`#Black-Friday` becomes `blackfriday`). Whitespace and commas separate tags. Max 50 tags, 100 chars each. |
| `hashtag_inbox_non_matching` | `boolean` | `true` | Hashtag mode: inbox non-matching videos (`true`) or exclude them (`false`). |
| `tracking_start_date` | YYYY-MM-DD | today (org timezone) | First day of the tracked window. Defaults to today in the org's analytics timezone. |
| `tracking_end_date` | YYYY-MM-DD | None | Optional last day, inclusive (must be ≥ start date; equal to the start date tracks a single day). |

### Responses

| HTTP | Cause |
|------|-------|
| 201 | Creator created; body is the full creator with embedded `accounts[]`. |
| 400 | `invalid_account` (bad handle or URL; see `meta.reason`), `duplicate_account` (the same handle twice in `accounts`). Short links (`vm.tiktok.com`, `vt.tiktok.com`, `tiktok.com/t/`) are followed, at most 10 per request (`reason: too_many_short_links`). |
| 402 | `max_accounts_per_org` limit reached (accounts, not creators, are counted). |
| 403 | Key lacks the `write` scope. |
| 404 | Project not found (or not in your org). |
| 409 | One of the handles is already tracked in this project. |

The creator and all of its accounts are created together: on a 402 or 409 nothing is created, so you can fix the request and retry without leaving an empty creator behind.

---

## POST /api/v2/projects/{projectId}/creators

Add one creator or a batch (up to 50) to a specific project. Requires the `write` scope. Each creator carries an `accounts[]` array (min 1).

### Single creator

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"accounts": [{"platform": "tiktok", "handle": "charlidamelio"}]}' \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/creators"
```

### Batch (up to 50)

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "creators": [
      {"accounts": [{"platform": "tiktok", "handle": "charlidamelio"}]},
      {"display_name": "Khaby", "accounts": [{"platform": "tiktok", "handle": "khaby.lame", "tracking_mode": "selective"}]}
    ]
  }' \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/creators"
```

Each creator object accepts the same `display_name` (optional), `metadata` (optional) and `accounts[]` (1 to 20) fields as `POST /api/v2/creators`. Returns 402 if the account limit is reached, 409 if a handle is already tracked in the project, and 404 if the project is not found.

Each creator in a batch is created together with its accounts, so a failing creator never leaves an empty creator behind. Creators earlier in the batch than the failing one are kept.

---

## GET /api/v2/creators/{id}

Get a single creator with its accounts embedded.

```bash
curl -H "Authorization: Bearer tga_<key>" \
  https://www.trackagoat.com/api/v2/creators/<uuid>
```

Returns the same shape as a list item, plus `readme_version`: a version token for the creator's notes, needed to [replace the notes safely](#post-apiv2creatorsidnotes). 404 if the creator doesn't exist or isn't in your org.

---

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

Update **creator-level** fields only. At least one field is required. Requires the `write` scope.

> **Note:**
>
> Handles and tracking configuration live on accounts, not creators. To rename a handle or change tracking settings, use [`PATCH /api/v2/accounts/{id}`](https://www.trackagoat.com/docs/api-reference/accounts.md#patch-apiv2accountsid).

```bash
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"readme": "Focus on cooking content. Posts Tues/Thurs."}' \
  https://www.trackagoat.com/api/v2/creators/<uuid>
```

### Body

| Field | Type | Description |
|-------|------|-------------|
| `display_name` | string | Override the display name (max 100 chars). |
| `readme` | string \| null | Markdown notes visible in the UI. Pass `null` to clear. |
| `is_active` | boolean | `false` removes the creator, exactly like `DELETE` below. `true` has no effect: a removed creator can't be restored through the API. |
| `metadata` | object | Arbitrary JSON. **Replaces** the whole object, so read the current value first if you need to preserve existing keys. |

Returns 404 if the creator isn't found.

> **Tip:**
>
> `PATCH` with `readme` overwrites the notes unconditionally. When an agent or script writes notes that people also edit, use [`POST /api/v2/creators/{id}/notes`](#post-apiv2creatorsidnotes) instead.

---

## POST /api/v2/creators/{id}/notes

Add to or replace a creator's notes (`readme`) without clobbering human edits. Requires the `write` scope. Accepts an `Idempotency-Key` header, so a retried append isn't added twice.

**Append** (recommended) adds `content` as a new section at the end, signed with the API key's name and your org's local date:

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"mode": "append", "content": "Views dropped 40% week over week."}' \
  https://www.trackagoat.com/api/v2/creators/<uuid>/notes
```

The notes then end with:

```markdown
Views dropped 40% week over week.

*Added by Weekly review bot via API, 2026-10-08*
```

**Replace** overwrites the whole notes and requires the `readme_version` from your last `GET /api/v2/creators/{id}`. If the notes changed since that read, nothing is written and you get **409** `notes_conflict`:

```bash
-d '{"mode": "replace", "content": "Rewritten notes", "expected_readme_version": "9f2c4e1a7b3d5f60"}'
```

### Body

| Field | Type | Description |
|-------|------|-------------|
| `mode` | `append` \| `replace` | Required. |
| `content` | string | Markdown. For `append`, the new section (1 to 20,000 chars). For `replace`, the complete new notes (`""` clears them). Notes are capped at 50,000 characters. |
| `expected_readme_version` | string | Required for `replace`: the `readme_version` you read. |

### Response

```json
{
  "data": {
    "id": "uuid", "entity_type": "creator", "mode": "append", "changed": true,
    "readme": "…", "readme_version": "41d0c3b2a9e8f716", "previous_readme_version": "9f2c4e1a7b3d5f60"
  },
  "error": null,
  "meta": null
}
```

| HTTP | Cause |
|------|-------|
| 400 | Invalid body, or `notes_too_long`. |
| 403 | Key lacks the `write` scope. |
| 404 | Creator not found in your organization. |
| 409 | `notes_conflict`: the notes changed since your read (`meta.current_readme_version` is the new version; re-read, merge and retry). `notes_busy`: the creator kept changing during the write; retry. |

`readme_version` is a hash of the notes text, so it only changes when the notes do; scheduled scrapes don't cause conflicts. Each write is a compare-and-swap: if someone edits the notes while an append is being saved, the append is applied on top of their edit. Writes appear in the creator's change log as **Internal notes updated**.

---

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

Soft-delete a creator (sets `is_active = false`). The creator's accounts are removed with it, so they stop being scraped and no longer count toward `max_accounts_per_org`. The creator's history and data are preserved, and you can add the same handles again later. Requires the `write` scope.

```bash
curl -X DELETE \
  -H "Authorization: Bearer tga_<key>" \
  https://www.trackagoat.com/api/v2/creators/<uuid>
```

Returns `{ "deleted": true }`.

---

## POST /api/v2/creators/{id}/scrape

Trigger a manual scrape for a creator. By default the creator's primary account is scraped (its TikTok account if it has one, otherwise its earliest-added active account); pass `?account_id=<uuid>` to scrape a specific account. Returns 400 if the creator has no active account. Consumes from the org's daily manual-scrape quota. Requires the `write` scope.

```bash
# Scrape the creator's primary account
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"job_type": "discover_videos"}' \
  https://www.trackagoat.com/api/v2/creators/<uuid>/scrape

# Scrape a single account
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/creators/<uuid>/scrape?account_id=<accountUuid>"
```

| Field | Where | Values | Description |
|-------|-------|--------|-------------|
| `account_id` | query | uuid | Restrict the scrape to one account. |
| `job_type` | body | `creator_profiles` \| `video_stats` \| `discover_videos` | Which scrape job to dispatch. |

The response echoes `{ job_type, creator_id, account_id, job_id }` (`account_id` is `null` when scraping all accounts).

| HTTP | Cause |
|------|-------|
| 200 | Scrape dispatched. |
| 400 | Creator is inactive. |
| 429 | Daily scrape limit exceeded. |

---

## POST /api/v2/projects/{projectId}/creators/bulk-reenable

Re-enable creators that were automatically disabled. Requires the `write` scope. Subject to the 24-hour cooldown per creator.

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"creator_ids": ["<uuid1>", "<uuid2>"]}' \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/creators/bulk-reenable"
```

Returns `{ ok_count, skipped_count, failed_count }`.

---

## POST /api/v2/projects/{projectId}/creators/bulk-remove

Soft-delete multiple creators in one call (max 100). Each creator is removed exactly as with `DELETE /api/v2/creators/{id}`, including its accounts. Requires the `write` scope.

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"creator_ids": ["<uuid1>", "<uuid2>"]}' \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/creators/bulk-remove"
```

Returns `{ "removed_count": N }`, the number of creators actually removed. IDs that are unknown, already removed, or in another project are skipped and not counted.

---

## Analytics (time-series)

Creator time-series analytics live on the unified endpoint. Add `&platform=` to scope a creator's series to a single platform.

```bash
# Views for a creator: daily new, last 30 days
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/analytics?entity=creator&entity_id=<uuid>&metric=views&mode=new&granularity=day"

# Engagement rate (derived)
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/analytics?entity=creator&entity_id=<uuid>&metric=engagement_rate_by_views&mode=rate"

# Multi-entity overlay: compare up to 5 creators side-by-side
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/analytics?entity=creator&entity_ids=<uuid1>,<uuid2>&metric=views&granularity=week"
```

See the [Analytics API reference](https://www.trackagoat.com/docs/api-reference/analytics.md) for the full parameter and response documentation, and [Accounts](https://www.trackagoat.com/docs/api-reference/accounts.md#get-apiv2accountsidstats) for per-account follower history.

---
Source: https://www.trackagoat.com/docs/api-reference/creators
