# Accounts

> Platform accounts owned by a creator. List, add, update, deactivate, remove, scrape, and read follower history.

An **account** is a single platform presence: a TikTok or Instagram handle: owned by a [creator](https://www.trackagoat.com/docs/api-reference/creators.md). Tracking configuration (the handle, `tracking_mode`, and the tracked date window) lives on the account, and **videos hang off accounts**. Plan limits count accounts, since accounts are what actually get scraped. [Deactivated](https://www.trackagoat.com/docs/guides/deactivating.md) accounts don't count.

## Account fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | uuid | Account ID |
| `creator_id` | uuid | Creator that owns this account |
| `project_id` | uuid | Project this account belongs to |
| `org_id` | uuid | Owning organization ID |
| `platform` | `tiktok` \| `instagram` \| `youtube` | Which platform this account is on |
| `handle` | string | Platform `@username` |
| `platform_user_id` | string \| null | The platform's internal user ID |
| `display_name` | string \| null | Display name from the platform |
| `avatar_url` | string \| null | Profile image URL |
| `bio_link` | string \| null | Link in the account's bio |
| `follower_count` | number | Current follower count (latest snapshot) |
| `following_count` | number | Current following count |
| `total_likes_count` | number | Cumulative total likes |
| `video_count` | number | Current total video count |
| `tracking_mode` | `auto` \| `selective` \| `hashtag` | `auto`: track all videos. `selective`: new videos land in the inbox. `hashtag`: track videos whose caption contains a tracked hashtag. |
| `tracking_hashtags` | `string[]` | Hashtag-mode only. 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` | Hashtag-mode only. `true` (default) sends non-matching videos to the inbox; `false` excludes them. |
| `tracking_start_date` | YYYY-MM-DD \| null | Start of the tracked window |
| `tracking_end_date` | YYYY-MM-DD \| null | Last day of the tracked window, inclusive (`null` = no end). The window spans each date in both UTC and the org's analytics timezone, whichever is wider. |
| `is_active` | boolean | `false` once the account has been removed. A deactivated account is still `true` |
| `deactivation` | object \| null | `null` while the account is active. See [Deactivation](#deactivation) |
| `last_scraped_at` | ISO 8601 \| null | When the account was last scraped |
| `tags` | array | The account's [tags](https://www.trackagoat.com/docs/api-reference/tags.md), `[{ id, name, color }]` sorted by name. Returned by `GET /api/v2/accounts` and `GET /api/v2/accounts/{id}`. Change them with `/api/v2/accounts/{id}/tags` |
| `created_at` | ISO 8601 | When the account was added |
| `updated_at` | ISO 8601 | Last update |

### Deactivation

`deactivation` is `null` while an account is active. Once an org admin [deactivates](https://www.trackagoat.com/docs/guides/deactivating.md) the account, or its creator, it is:

| Field | Type | Description |
|-------|------|-------------|
| `deactivated_at` | ISO 8601 | When the account was deactivated |
| `deactivated_by` | uuid \| null | User who deactivated it |
| `video_choice` | `keep_tracking` \| `freeze` \| `stop_tracking` | What happened to its videos |
| `reason` | string \| null | The reason given, if any |
| `via_creator` | boolean | `true` when it was deactivated because its creator was. It comes back when the creator is reactivated |
| `video_counts` | `{ frozen, stopped }` | Single `GET` only. How many of its videos are frozen or stopped. When either is above 0, reactivating needs `existing_videos` |

The raw columns (`deactivated_at`, `deactivated_by`, `deactivated_via_cascade`, `deactivation_video_choice`, `deactivation_reason`) are also returned. Prefer `deactivation`.

---

## GET /api/v2/accounts

List accounts across your organization.

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

### Query parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `project_id` | uuid | None | Filter to a specific project |
| `creator_id` | uuid | None | Filter to a specific creator |
| `platform` | `tiktok` \| `instagram` \| `youtube` | None | Filter to one platform |
| `search` | string | None | Partial match on handle |
| `status` | `active` \| `deactivated` \| `all` | `active` | `active` hides [deactivated](https://www.trackagoat.com/docs/guides/deactivating.md) accounts (including those of a deactivated creator), `deactivated` lists only them, `all` lists both |
| `tags` | string | None | Comma-separated tag names (any casing) or ids, up to 20, as in `?tags=backup`. See [Filtering lists by tag](https://www.trackagoat.com/docs/api-reference/tags.md#filtering-lists-by-tag) |
| `tags_match` | `any` \| `all` | `any` | `any`: accounts with at least one of the tags. `all`: accounts with every one |
| `limit` | number | 50 | Items per page (max 100) |
| `cursor` | string | None | Pagination cursor |

Returns a paginated array of [account objects](#account-fields).

---

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

List all accounts belonging to a single creator (ordered oldest first).

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

Returns an array of [account objects](#account-fields). 404 if the creator doesn't exist or isn't in your org.

---

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

Add a platform account to an existing creator and dispatch a scrape. Requires the `write` scope. Enforces the per-org account limit.

If the creator is [deactivated](https://www.trackagoat.com/docs/guides/deactivating.md), the new account is deactivated with them, with the same choices (`deactivation.via_creator: true`), and comes back when the creator is reactivated. If you add the handle of an account that was removed while it was deactivated, that is a fresh add: the account comes back active (`deactivation: null`), its frozen or stopped videos are tracked again (views they gained in the meantime are still not paid), and any payout pause it started ends.

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

### Body

| 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` | Video discovery mode. |
| `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: route non-matching videos to the inbox (`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 | Account added; body is the full account object. |
| 400 | `invalid_account`: the handle or URL can't become an account. `meta.reason` is `invalid_handle`, `video_url`, `no_handle_in_url`, `unrecognized_url`, `platform_mismatch`, `short_url_unresolved` or `too_many_short_links`. |
| 402 | `max_accounts_per_org` limit reached. |
| 403 | The handle is banned on this platform (`creator_banned`). |
| 404 | Creator not found (or not in your org). |
| 409 | `already_tracked`: the handle is already tracked in this project. `creator_inactive`: the creator has been removed. |

---

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

Get a single account.

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

Returns an [account object](#account-fields). While the account is deactivated, `deactivation` also carries `video_counts`. 404 if not found.

---

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

Rename the handle or change tracking configuration. At least one field is required. Requires the `write` scope. **Changing the handle dispatches a re-scrape** of the account's profile and videos.

```bash
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"handle": "charlidamelio", "tracking_mode": "selective"}' \
  https://www.trackagoat.com/api/v2/accounts/<uuid>
```

### Body

| Field | Type | Description |
|-------|------|-------------|
| `handle` | string | Rename the account handle (or pass a profile URL). Normalized like account creation. Triggers a re-scrape. `409 handle_taken` if another account in the project, including a removed one, already has that handle; `404` if the account was removed. |
| `tracking_mode` | `auto` \| `selective` \| `hashtag` | Switch between auto-tracking, inbox review, and hashtag matching. |
| `tracking_hashtags` | `string[]` | Replace the tracked hashtag list. Must be non-empty if the resulting mode is `hashtag`. |
| `hashtag_inbox_non_matching` | `boolean` | Route non-matching videos to the inbox or exclude them. |
| `tracking_start_date` | YYYY-MM-DD \| null | Start of the tracked window. `null` clears it. |
| `tracking_end_date` | YYYY-MM-DD \| null | Last day of the tracked window, inclusive. `null` = no end. |

Returns the updated [account object](#account-fields). 404 if not found.

---

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

Soft-delete an account (its history and videos are preserved). This removes the account; it can't be undone through the API, but you can add the handle again. To pause it instead, [deactivate](#post-apiv2accountsiddeactivate) it. Requires the `write` scope.

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

Returns `{ "deleted": true }`.

---

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

Trigger a manual scrape for a single account. Consumes from the org's daily manual-scrape quota. Requires the `write` scope.

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"job_type": "video_stats"}' \
  https://www.trackagoat.com/api/v2/accounts/<uuid>/scrape
```

| Body field | Values | Description |
|-----------|--------|-------------|
| `job_type` | `creator_profiles` \| `video_stats` \| `discover_videos` | Which scrape job to dispatch (optional). |

The response echoes `{ job_type, account_id, job_id }`.

| HTTP | Cause |
|------|-------|
| 200 | Scrape dispatched. |
| 400 | Account is disabled. |
| 429 | Daily scrape limit exceeded. |

---

## POST /api/v2/accounts/{id}/deactivate

[Deactivate](https://www.trackagoat.com/docs/guides/deactivating.md) one account, for example a creator's Instagram while you keep tracking their TikTok. trackagoat stops looking for its new posts and updating its profile, and hides it from lists unless you ask for `status=deactivated`. The creator and their other accounts are not changed. The account keeps its campaign membership and you can reactivate it. While deactivated, it doesn't count toward `max_accounts_per_org`. To deactivate a whole creator, use [`POST /api/v2/creators/{id}/deactivate`](https://www.trackagoat.com/docs/api-reference/creators.md#post-apiv2creatorsiddeactivate).

Requires the `write` scope, and the member the key acts as must be an org admin (`403 insufficient_role` otherwise). Accepts an `Idempotency-Key` header.

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"videos": "stop_tracking", "reason": "Personal account"}' \
  https://www.trackagoat.com/api/v2/accounts/<uuid>/deactivate
```

### Body

| Field | Type | Description |
|-------|------|-------------|
| `videos` | `keep_tracking` \| `freeze` \| `stop_tracking` | Required. What happens to the account's videos already tracked. Same meaning as for [creators](https://www.trackagoat.com/docs/api-reference/creators.md#post-apiv2creatorsiddeactivate). |
| `payouts` | `pause` \| `keep` | Required only when this is the creator's last active account and the creator has payout structures assigned (`400 payouts_required` otherwise). Ignored in every other case: while the creator has another active account, their payouts carry on. Same meaning as for [creators](https://www.trackagoat.com/docs/api-reference/creators.md#post-apiv2creatorsiddeactivate). |
| `reason` | string | Optional, up to 1,000 characters. Shown in the creator's History. |

### Response

```json
{
  "data": {
    "account": {
      "id": "uuid",
      "platform": "instagram",
      "handle": "someuser",
      "deactivation": {
        "deactivated_at": "2026-10-12T09:30:00Z",
        "deactivated_by": "uuid",
        "video_choice": "stop_tracking",
        "reason": "Personal account",
        "via_creator": false
      }
    },
    "result": { "account_id": "uuid", "creator_id": "uuid", "videos_frozen": 0, "videos_stopped": 14, "payouts": null }
  },
  "error": null,
  "meta": null
}
```

`account` is the full [account object](#account-fields) after the change (shortened here). `result.creator_id` is `null` for an account without a creator. `result.payouts` is `pause` or `keep` when the payout question applied, otherwise `null`.

| HTTP | Cause |
|------|-------|
| 200 | Deactivated. |
| 400 | `invalid_body`. `payouts_required`: this is the creator's last active account, the creator has payout structures assigned, and `payouts` was left out; `meta.assignments` is how many. Nothing changed. |
| 403 | Key lacks the `write` scope (`insufficient_scope`), or acts as a member who isn't an org admin (`insufficient_role`). |
| 404 | `not_found`: the account doesn't exist, it or its creator was removed, or it isn't in your org. |
| 409 | `already_deactivated`, including when its creator is deactivated. |

---

## POST /api/v2/accounts/{id}/reactivate

Reactivate a deactivated account. New posts follow its tracking mode again, and a full check for posts made while it was deactivated runs with the next discovery cycle (usually within 12 hours). An account whose creator is deactivated can't be reactivated on its own: reactivate the creator with [`POST /api/v2/creators/{id}/reactivate`](https://www.trackagoat.com/docs/api-reference/creators.md#post-apiv2creatorsidreactivate). If the creator's payouts were paused, reactivating any one of their accounts starts them again from the next period.

The account counts toward `max_accounts_per_org` again, so reactivating needs room under the limit. If it doesn't fit, the call fails with `402 limit_reached` and nothing changes.

Requires the `write` scope, and the member the key acts as must be an org admin. Accepts an `Idempotency-Key` header.

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"missed_posts": "ignore", "existing_videos": "keep"}' \
  https://www.trackagoat.com/api/v2/accounts/<uuid>/reactivate
```

### Body

| Field | Type | Description |
|-------|------|-------------|
| `missed_posts` | `inbox` \| `track` \| `ignore` | Required. What happens to posts made while deactivated. Same meaning as for [creators](https://www.trackagoat.com/docs/api-reference/creators.md#post-apiv2creatorsidreactivate). |
| `existing_videos` | `resume` \| `keep` \| `stop` | Required when the account has frozen or stopped videos (`deactivation.video_counts` on `GET /api/v2/accounts/{id}`), ignored otherwise. |
| `note` | string | Optional, up to 1,000 characters. Shown in the creator's History. |

### Response

```json
{
  "data": {
    "account": { "id": "uuid", "deactivation": null },
    "result": {
      "account_id": "uuid",
      "creator_id": "uuid",
      "videos_resumed": 0,
      "videos_stopped": 0,
      "missed_posts_updated": 2
    }
  },
  "error": null,
  "meta": null
}
```

| HTTP | Cause |
|------|-------|
| 200 | Reactivated. |
| 400 | `invalid_body`. `existing_videos_required`: the account has frozen or stopped videos and `existing_videos` was left out; `meta.frozen` and `meta.stopped` say how many. Nothing changed. |
| 402 | `limit_reached`: you're at your plan's account limit. `meta.limitKey` is `max_accounts_per_org`, with `meta.current` (active accounts now, deactivated ones not counted) and `meta.max`. Nothing changed. |
| 403 | Key lacks the `write` scope, or acts as a member who isn't an org admin (`insufficient_role`). |
| 404 | `not_found`. |
| 409 | `not_deactivated`: the account is active. `creator_deactivated`: its creator is deactivated; reactivate the creator instead. |

---

## POST /api/v2/projects/{projectId}/accounts/bulk-deactivate

Deactivate up to 100 accounts in one project, each exactly like [`POST /api/v2/accounts/{id}/deactivate`](#post-apiv2accountsiddeactivate), with one `videos` choice for all of them. Requires the `write` scope, and the member the key acts as must be an org admin. Accepts an `Idempotency-Key` header.

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"account_ids": ["<uuid1>", "<uuid2>"], "videos": "keep_tracking"}' \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/accounts/bulk-deactivate"
```

Body: `account_ids` (1 to 100 UUIDs), `videos`, `payouts` and an optional `reason`. `payouts` is needed when the batch includes the last active account of a creator with payout structures assigned.

```json
{
  "data": {
    "done": [{ "account_id": "uuid", "creator_id": "uuid", "videos_frozen": 0, "videos_stopped": 0 }],
    "skipped": [{ "id": "uuid", "reason": "not_found" }],
    "done_count": 1,
    "skipped_count": 1
  },
  "error": null,
  "meta": null
}
```

Skipped reasons: `not_found` (unknown, removed, its creator removed, or in another project), `already_deactivated`, or `payouts_required` (with `assignments`: the batch would leave a creator with payout structures assigned and no active account, and `payouts` was left out; every account of that creator in the batch is skipped). The rest go ahead. `404` if the project isn't in your org.

---

## POST /api/v2/projects/{projectId}/accounts/bulk-reactivate

Reactivate up to 100 accounts in one project, each exactly like [`POST /api/v2/accounts/{id}/reactivate`](#post-apiv2accountsidreactivate), with one set of choices for all of them. Requires the `write` scope, and the member the key acts as must be an org admin. Accepts an `Idempotency-Key` header.

```bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"account_ids": ["<uuid1>", "<uuid2>"], "missed_posts": "inbox"}' \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/accounts/bulk-reactivate"
```

Body: `account_ids` (1 to 100 UUIDs), `missed_posts`, `existing_videos` and an optional `note`. The response has the same `done`, `skipped`, `done_count` and `skipped_count` shape, and each `done` item is a reactivation `result`. Each account that can't be reactivated is returned in `skipped` with a `reason`, and the rest go ahead:

| `reason` | Why |
|----------|-----|
| `not_found` | Unknown, removed, its creator removed, or in another project |
| `not_deactivated` | The account is active |
| `creator_deactivated` | Its creator is deactivated. Reactivate the creator instead |
| `existing_videos_required` | The account has frozen or stopped videos and `existing_videos` was left out. Includes `frozen` and `stopped` counts |
| `limit_reached` | You're at your plan's account limit. Includes `current` and `max`. Accounts are reactivated in order until the limit is reached |

---

## GET /api/v2/accounts/{id}/stats

Get an account's follower/stat snapshot history. This series is **append-only**: snapshots are only ever inserted, never updated.

```bash
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/accounts/<uuid>/stats?from=2026-01-01&to=2026-03-31"
```

### Query parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `from` | YYYY-MM-DD | None | Start of the range (inclusive) |
| `to` | YYYY-MM-DD | None | End of the range (inclusive) |
| `limit` | number | 365 | Max snapshots to return (max 1000) |

### Response fields

Each element of the returned array:

| Field | Type | Description |
|-------|------|-------------|
| `account_id` | uuid | Account this snapshot belongs to |
| `snapshot_at` | ISO 8601 | When the snapshot was taken |
| `follower_count` | number \| null | Followers at snapshot time |
| `following_count` | number \| null | Following count at snapshot time |
| `total_likes_count` | number \| null | Cumulative likes at snapshot time |
| `video_count` | number \| null | Total video count at snapshot time |
| `is_baseline` | boolean | Whether this is the initial baseline snapshot |

- [Creators](https://www.trackagoat.com/docs/api-reference/creators.md): Creators own accounts, create, list, and manage them.
- [Creators and accounts guide](https://www.trackagoat.com/docs/guides/accounts.md): How the multi-platform model works.

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