# MCP server

> Connect Claude, Cursor and other MCP clients to trackagoat with the hosted MCP server.

## Overview

trackagoat runs a hosted [Model Context Protocol](https://modelcontextprotocol.io) server. Connect Claude.ai or Claude Desktop by signing in with your trackagoat account, or point any MCP client at it with an API key. Your agent can then check a program's health, browse projects, creators, videos, campaigns, goals, conversions and payouts, pull analytics, and (with write access) do what the REST API does: add, change and remove creators, accounts, videos, campaigns, goals and content groups, record conversions, set up payout structures, assign creators to them, and record or void payments. It can also read and write the [Markdown documents](#notes-and-documents) on every record and export any CSV the app exports.

| | |
|---|---|
| **URL** | `https://www.trackagoat.com/api/mcp` |
| **Transport** | Streamable HTTP (stateless). The legacy HTTP+SSE transport is not supported; use [`mcp-remote`](#stdio-only-clients) for clients that only speak stdio. |
| **Auth** | Either sign in with trackagoat (OAuth 2.1, for [Claude.ai and Claude Desktop](#claudeai-and-claude-desktop-custom-connector) and other OAuth-capable clients), or `Authorization: Bearer tga_<key>` with the same org API keys as the [REST API](https://www.trackagoat.com/docs/api-reference/authentication.md) |
| **Data scope** | The organization you pick when you sign in, or the one that owns the API key. Other organizations' data is never visible. |

Every tool is a thin adapter over one `/api/v2` endpoint, so MCP and REST return the same data and enforce the same permissions and limits.

A connection always acts as one member of the organization: the person who signed in, or the member an API key acts as. Everything the agent does is attributed to them, and it can never do more than they can do in the app. When the agent connects, the server sends that person's [personalization document](https://www.trackagoat.com/docs/agents/notes.md#your-personalization-document) along with its instructions, so the agent starts out knowing who it is helping and how they want help.

> **Tip:**
>
> Sign-in is the best way for each person to connect their own agent: it identifies them directly and nobody shares a key. Use API keys for scripts, servers and agents that can't sign in.

**Install with your coding agent.** Paste this prompt into Claude Code, Codex, Cursor or another coding agent. It installs the MCP server, has you sign in, and checks the connection.

Prompt to paste:

```text
Install the trackagoat MCP server in this client and connect it to my trackagoat account. trackagoat tracks creators' public TikTok and Instagram posts, works out what each creator is owed from the payout structures I set up, and shows which creators need attention. Its MCP server can read all of that and do the day-to-day work too: add creators and videos, set goals, manage payout structures and record payments. Every record in trackagoat, and each person, has a Markdown document: read the relevant ones before you act, and add what you find.

Details: https://www.trackagoat.com/docs/agents/mcp.md. If I don't have an account yet, follow https://www.trackagoat.com/docs/agents/setup.md instead.

Every plan includes MCP and API access. New organizations start on Free, which allows 200 requests a day (each MCP tool call counts as one) and 1 API key. Starter and Ultra raise those limits; to upgrade, I email support@trackagoat.com with my organization name and the plan I want. If a call fails with rate_limit_exceeded, wait the retry_after_seconds it returns before trying again.

Rules:
- Don't create accounts, sign in for me or type passwords. Tell me what to open and click, then wait for me.
- Don't ask me to paste an API key into this chat, and never print, log or commit one. If a key is needed, I'll set the TRACKAGOAT_API_KEY environment variable myself.

Steps:

1. Connect the trackagoat MCP server: https://www.trackagoat.com/api/mcp (Streamable HTTP). Use sign-in (OAuth) first, because it needs no API key: a trackagoat page opens, I pick the organization and choose "View data", plus "Make changes" if you should be able to change things, such as adding creators, setting goals or recording payouts. Set it up for the client you are running in:
   - Claude Code: run `claude mcp add --transport http trackagoat https://www.trackagoat.com/api/mcp`. Then I run /mcp, choose trackagoat and sign in.
   - Codex CLI: run `codex mcp add trackagoat --url https://www.trackagoat.com/api/mcp`, then `codex mcp login trackagoat`.
   - Cursor: add `"trackagoat": { "url": "https://www.trackagoat.com/api/mcp" }` under "mcpServers" in ~/.cursor/mcp.json. Then I sign in from Cursor's MCP settings.
   - VS Code: add `"trackagoat": { "type": "http", "url": "https://www.trackagoat.com/api/mcp" }` under "servers" in .vscode/mcp.json. Then I start the server and sign in.
   - Claude.ai or Claude Desktop: I add a custom connector with that URL in Settings → Connectors. You cannot do this step.
   Sign-in is the best way for each person to connect their own agent: it identifies them directly, so nobody shares a key. Only if this client can't sign in, use an API key instead. An org admin creates one in trackagoat under Org settings → API keys; the key acts as one member (under "Acts as"), so it should act as me. I set it as TRACKAGOAT_API_KEY. You configure the client to send `Authorization: Bearer <key>`, read from that variable, never written out. The setup guide has the exact config for each client.

2. Check the connection: call the list_projects tool and show me my project names. If the trackagoat tools don't appear yet, ask me to restart this session or reload the client, then try again. Then call get_my_profile to see who you're connected as and whether I already have a personalization document; if I do, follow it.

3. Offer to write my personalization document together, so every future session starts knowing me. It's the Markdown document in trackagoat under Settings → My README, and trackagoat sends it to you at the start of each session. Ask me a few short questions, one or two at a time:
   - who I am and my role;
   - what my job is in running the creator program;
   - the program, creators and goals I care about most;
   - how I most want you to help;
   - preferences: tone, how often to check in, what to look at first, and what you should never do without asking.
   Draft it in Markdown with these headings: About me, My job in the program, What I care about, How to help me, Preferences, Never without asking. Show me the draft, and save it with update_my_readme only after I approve. Everyone in my organization, and their agents, can read it, so leave out anything private. If I say no, move on. Later, when my role or preferences change, offer to update it.
```

New to trackagoat? [Set up with a coding agent](https://www.trackagoat.com/docs/agents/setup.md) also covers sign-up and plan limits.

## Claude.ai and Claude Desktop (custom connector)

Claude connects natively: no API key, no config file, no `mcp-remote`. You sign in with your trackagoat account and choose what Claude may do.

**Step 1: Add the connector**

In Claude, open **Settings → Connectors** (on claude.ai: **Customize → Connectors**) and choose **Add custom connector**. Enter:

- **Name**: `trackagoat`
- **Remote MCP server URL**: `https://www.trackagoat.com/api/mcp`

Leave the advanced OAuth client ID and secret fields empty. On Team and Enterprise plans, an Owner adds the connector once for the organization and each person then connects their own account.

**Step 2: Sign in and approve**

Click **Connect**. A trackagoat window opens. Sign in if you aren't already, then:

- pick the **organization** Claude may see (one per connection), and
- choose **View data** (always on) and, optionally, **Make changes** for the [write tools](#write-tools).

Click **Allow access**. You're returned to Claude and the trackagoat tools appear in the connector list.

**Step 3: Use it**

Enable the connector in a chat (the **+** / tools menu) and ask about your creators. Claude Desktop, Claude mobile and claude.ai share the same connection.

Good to know:

- Claude acts **as you**, limited to what you allowed and to your current role. If your role changes or you leave the organization, its access changes or stops at once.
- If you allowed only **View data** and later ask Claude to change something, Claude asks you to reconnect and approve **Make changes**.
- To use a second organization, add the connector again and pick the other organization.
- To disconnect, remove the connector in Claude, or go to **Settings → Connected Apps** in trackagoat. Org admins can also disconnect any member's app under **Org settings → API keys → Connected apps**.
- Connected apps don't use up your plan's API key limit, but every tool call counts toward the org's daily API requests, like a key. Every plan can connect apps; Free allows 200 requests a day.

The same sign-in works for other MCP clients that support OAuth, including Claude Code, Codex, Cursor and VS Code: give them the URL above and they discover everything else from it. See [Connect a client](#connect-a-client).

## Get an API key

Use an API key for scripts, and for clients that can't sign in with OAuth. In trackagoat, go to **Org settings → API keys → + New API key**. Name it after the client (e.g. `Claude Code laptop`) and choose scopes:

- **`read`**: required for every read tool.
- **`write`**: required for the [write tools](#write-tools). Leave it off if the agent should only read.

Copy the key: it's shown once. See [Authentication and API keys](https://www.trackagoat.com/docs/api-reference/authentication.md) for expiry and revocation. A key acts on behalf of one member: the admin who created it, or the member picked under **Acts as**. It can never do more than that member can in the app, and it stops working if they leave the org. A key made for someone else also stops working if the admin who created it leaves, is changed to member or is suspended (see [Who a key acts as](https://www.trackagoat.com/docs/api-reference/authentication.md#who-a-key-acts-as)).

> **Tip:**
>
> For a teammate's coding agent, have them sign in over MCP, or create a key that acts as them. Give each client or teammate its own key. You can then revoke one without breaking the others, and **Last used** shows which keys are live.

## Connect a client

Each client below can sign in (no key) or send an API key. Keep keys in an environment variable such as `TRACKAGOAT_API_KEY`, never in a committed file.

### Claude Code

Sign in:

```bash
claude mcp add --transport http trackagoat https://www.trackagoat.com/api/mcp
```

Then run `/mcp` in Claude Code, select **trackagoat** and sign in. With an API key instead:

```bash
claude mcp add --transport http trackagoat https://www.trackagoat.com/api/mcp \
  --header "Authorization: Bearer $TRACKAGOAT_API_KEY"
```

Add `--scope user` to make it available in every project. To share the setup with your team without committing a key, put this in the repo's `.mcp.json` and have each person export `TRACKAGOAT_API_KEY`:

```json
{
  "mcpServers": {
    "trackagoat": {
      "type": "http",
      "url": "https://www.trackagoat.com/api/mcp",
      "headers": { "Authorization": "Bearer ${TRACKAGOAT_API_KEY}" }
    }
  }
}
```

Run `/mcp` in Claude Code to confirm `trackagoat` shows as connected.

### Codex CLI

Sign in:

```bash
codex mcp add trackagoat --url https://www.trackagoat.com/api/mcp
codex mcp login trackagoat
```

With an API key instead, Codex reads the variable each time it starts:

```bash
codex mcp add trackagoat --url https://www.trackagoat.com/api/mcp \
  --bearer-token-env-var TRACKAGOAT_API_KEY
```

### Cursor

Add to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project). To sign in, leave out `headers` and sign in from Cursor's MCP settings. With an API key:

```json
{
  "mcpServers": {
    "trackagoat": {
      "url": "https://www.trackagoat.com/api/mcp",
      "headers": { "Authorization": "Bearer ${env:TRACKAGOAT_API_KEY}" }
    }
  }
}
```

### VS Code

Add to `.vscode/mcp.json`, then start the server. Without `headers` (and `inputs`), VS Code asks you to sign in. With an API key, VS Code prompts for it once and stores it securely:

```json
{
  "inputs": [
    { "type": "promptString", "id": "trackagoat-key", "description": "trackagoat API key", "password": true }
  ],
  "servers": {
    "trackagoat": {
      "type": "http",
      "url": "https://www.trackagoat.com/api/mcp",
      "headers": { "Authorization": "Bearer ${input:trackagoat-key}" }
    }
  }
}
```

### Stdio-only clients

For clients that only launch local (stdio) servers, bridge with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) (requires Node.js 18+). For example, in Claude Desktop's `claude_desktop_config.json` (**Settings → Developer → Edit config**), though the [custom connector](#claudeai-and-claude-desktop-custom-connector) is simpler there:

```json
{
  "mcpServers": {
    "trackagoat": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://www.trackagoat.com/api/mcp",
        "--header",
        "Authorization:${TRACKAGOAT_AUTH}"
      ],
      "env": { "TRACKAGOAT_AUTH": "Bearer tga_<key>" }
    }
  }
}
```

Keep `Authorization:${TRACKAGOAT_AUTH}` without spaces: some clients split arguments on spaces, so the `Bearer ` prefix goes in the env var.

### Other clients

Any client that supports Streamable HTTP works: either let it sign in with OAuth (it discovers the authorization server from the `401` response, per the [MCP authorization spec](https://modelcontextprotocol.io/specification/latest/basic/authorization)), or send `Authorization: Bearer tga_<key>` on every request.

## Tools

Each tool's description and input schema are served to your client, so the agent sees parameter docs directly. Schemas are strict: a parameter a tool doesn't have (say `range` on `get_analytics`, which takes `from` and `to`) is rejected with `unknown_parameter` instead of being silently ignored. Tool results are compact JSON: image URLs are dropped, and list tools preview long `readme` notes (call the matching `get_*` tool for the full text).

### Read tools

All require the `read` scope, except `get_changelog`, which works on any connection. Tools marked **Org admin only** also need the connection to act as an org admin, like the matching page in the app; for anyone else they return `insufficient_role`. Each tool's description says the same.

**Projects, creators, accounts and videos**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `list_projects` | Projects in the org, with their `readme` | `GET /api/v2/projects` |
| `list_creators` | Creators with accounts and totals; filter by project, platform, search | `GET /api/v2/creators` |
| `get_creator` | One creator: full `readme` and its `readme_version`, `metadata`, every account's stats, tracking config and `last_scraped_at` | `GET /api/v2/creators/{id}` |
| `list_accounts` | Platform accounts with counts and tracking mode; filter by project, creator, platform, handle | `GET /api/v2/accounts` |
| `get_account` | One platform account in full | `GET /api/v2/accounts/{id}` |
| `get_account_stats` | Follower, following, like and video-count snapshots over time for one account | `GET /api/v2/accounts/{id}/stats` |
| `list_videos` | Tracked videos, newest added first, with counts and the start of each caption; filter by project, creator, account, platform, content group | `GET /api/v2/videos` |
| `get_video_stats` | One video's latest counts, caption, notes (`readme` + `readme_version`), tracking status and flags | `GET /api/v2/videos/{id}` |
| `list_content_groups` | Content groups: the same content posted on several platforms, linked as one | `GET /api/v2/content-groups` |
| `get_content_group` | One content group and its video ids | `GET /api/v2/content-groups/{id}` |
| `get_content_group_stats` | Views, likes, comments and shares summed across a content group, and per platform | `GET /api/v2/content-groups/{id}/stats` |
| `get_share_link` | A creator's shareable page: url, enabled, sections shown. Pass `include_pin: true` to also get the entrance PIN, which needs the `write` scope. **Org admin only.** | `GET /api/v2/creators/{id}/share-link` |
| `get_schema` | The data model: entities, fields, relationships | `GET /api/v2/schema` |

**Comments** (paid plans; `403 plan_feature_unavailable` on Free)

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `list_comments` | Comments collected on a project's videos, most liked first: text, likes, sentiment and signals (`app_question`, `signup_intent`, `ad_disclosure`, `risk`, `pay_talk`, `referral_spam`, `brand_mention`, `competitor`). Filter by `signal` (`flagged` = ad doubts or brand-safety words), sentiment, creator, campaign, platform, dates, text. Paginate with `next_cursor` | `GET /api/v2/projects/{id}/comments` |
| `list_video_comments` | One video's Comments tab: every collected comment with sentiment and signals, the sentiment split and per-signal counts, and coverage (platform count vs collected, last collected). Filter by `signal`, sentiment, text; paginate with `next_offset` | `GET /api/v2/videos/{id}/comments` |
| `get_comment_signals_summary` | Comment Insights in one call: coverage, sentiment and signal counts, a row per creator (comments per 1,000 views, positive share, app questions, sign-ups, flagged, top comment) and positive share over time. `creator_sort` ranks creators (e.g. `app_question_count` for "who gets the most app questions"), `creator_limit` keeps the top N | `GET /api/v2/projects/{id}/comment-signals` |
| `get_comment_flags` | The weekly flag digest on demand: comments in the last `days` (default 7) asking if a post is an ad or using words like scam, the most-liked of them, and creators whose positive share fell 20+ points against the 30 days before | `GET /api/v2/projects/{id}/comment-flags` |
| `get_comment_settings` | The org's minimum comment count and brand / competitor terms (Org settings → Comments), and whether the plan collects comments. Works on every plan. **Org admin only.** | `GET /api/v2/comment-settings` |

**Program health, goals and campaigns**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `get_program_health` | A project's (or one campaign's) health in one call: healthy / at-risk / degraded verdict and the signals behind it, KPIs, creators who stopped posting, goal compliance, alerts (sudden view drops, tracking gaps, private accounts, disabled creators, over-posting, flagged comments), top and under-performers. Optional `campaign_id` and `range` (`7d`, `14d`, `30d` default, `90d`) | `GET /api/v2/projects/{id}/program-health` |
| `get_goal_compliance` | Goal target vs actual and status per creator in a project | `GET /api/v2/projects/{id}/goal-compliance` |
| `get_goal_compliance_summary` | Count of a project's goals by status (on track, at risk, missed) | `GET /api/v2/analytics/aggregate/goal-compliance` |
| `list_campaigns` | Campaigns with their `readme` | `GET /api/v2/campaigns` |
| `get_campaign_stats` | Per-campaign totals for tracked posts posted in a date range (default the last 30 days including today, org time zone), with prior-period delta. Don't add rows up (shared videos appear in several rows): use `get_metric_totals` with `campaigns=all`. Pass `campaign_id` for one campaign (`project_id` is then optional: it comes from the campaign), or `project_id` alone to compare every campaign in the project | `GET /api/v2/analytics/aggregate/campaign-leaderboard` |
| `get_metric_totals` | Headline totals for a project, one campaign or every campaign at once (`campaigns=all`, each video counted once): tracked posts, views, likes, comments, shares, saves, weighted engagement rate, creators who posted, followers and follower growth, posts awaiting review, and stats freshness. The same numbers as the dashboard and Insights. Default window: the last 30 days including today, org time zone | `GET /api/v2/analytics/aggregate/summary` |
| `get_campaign_progress` | Each campaign's view target, current views, percent done, days left and status | `GET /api/v2/analytics/aggregate/campaign-progress` |
| `get_campaign_trends` | Views gained, posts, creators posting and first-time creators per day, week, month or year, for one campaign or the whole project | `GET /api/v2/analytics/aggregate/campaign-timeseries` |
| `get_member_overlap` | Creators or videos that are in more than one campaign | `GET /api/v2/analytics/aggregate/member-overlap` |

**Analytics**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `get_analytics` | Time series for a video, creator or campaign: any metric, granularity, prior-period comparison, top-N breakdown | `GET /api/v2/analytics` |
| `list_top_videos` | Rank a project's videos by views, likes, comments, shares, saves or posted date; filter by posted date range, platform, campaign | `GET /api/v2/analytics/aggregate/video-leaderboard` |
| `list_top_creators` | Rank creators by views on videos posted in a range, with follower change, posts per week and engagement | `GET /api/v2/analytics/aggregate/creator-leaderboard` |
| `list_top_accounts` | The same ranking per platform account; filter by platform or campaign | `GET /api/v2/analytics/aggregate/account-leaderboard` |
| `list_outlier_videos` | Videos that beat their creator's median views by a multiple (default 1.5×) | `GET /api/v2/analytics/aggregate/outliers` |
| `get_creator_scatter` | Follower change vs engagement rate per creator | `GET /api/v2/analytics/aggregate/creator-scatter` |
| `get_posting_heatmap` | Posts and average views by weekday and hour | `GET /api/v2/analytics/aggregate/posting-heatmap` |
| `get_posting_consistency` | Posts per creator per day | `GET /api/v2/analytics/aggregate/posting-consistency` |
| `get_over_posting_risks` | Days a creator posted so often that some videos were held back from tracking | `GET /api/v2/analytics/aggregate/over-posting-risks` |
| `get_view_distribution` | Histogram and percentiles of video views | `GET /api/v2/analytics/aggregate/view-distribution` |
| `list_annotations` | Dated notes on a creator's, campaign's or video's charts, added by people or automatically | `GET /api/v2/analytics/annotations` |

The heatmap, consistency, over-posting, view-distribution and campaign-trend tools default to the last 30 days when `from` and `to` are left out.

**Conversions**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `list_conversion_events` | A project's conversion events (e.g. signup, purchase) and their default values | `GET /api/v2/projects/{id}/conversion-events` |
| `get_conversion_event` | One conversion event | `GET /api/v2/projects/{id}/conversion-events/{eventId}` |
| `list_conversions` | Recorded conversions; filter by event, creator, account, video, source, status and time | `GET /api/v2/projects/{id}/conversions` |
| `get_conversion` | One recorded conversion | `GET /api/v2/projects/{id}/conversions/{conversionId}` |
| `get_conversion_summary` | Count and value of conversions in a range, one row per currency | `GET /api/v2/analytics/aggregate/conversion-summary` |
| `list_top_converters` | Creators ranked by conversion value | `GET /api/v2/analytics/aggregate/conversion-leaderboard` |
| `get_conversion_timeseries` | Conversions per day, one row per day and currency | `GET /api/v2/analytics/aggregate/conversion-timeseries` |

**Payouts**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `list_payout_structures` | Payout structures with their current rule: type, period, amount and `criteria_params` (for CPM: tiers, minimum, cap and `view_lifetime_days`); filter by project and active state | `GET /api/v2/payout-structures` |
| `get_payout_structure` | One structure with its current rule, earlier versions and assignment count | `GET /api/v2/payout-structures/{id}` |
| `list_payout_assignments` | Creators assigned to a structure | `GET /api/v2/payout-structures/{id}/assignments` |
| `list_payout_accruals` | Earned / paid / rejected / voided payout accruals; filter by project, creator, structure, status, period | `GET /api/v2/payout-accruals` |
| `get_payout_accrual` | One accrual, including how it was calculated (`inputs_snapshot`) | `GET /api/v2/payout-accruals/{id}` |
| `list_payout_methods` | The org's payout methods (PayPal, Venmo, …) | `GET /api/v2/payout-methods` |
| `get_payout_method` | One payout method | `GET /api/v2/payout-methods/{id}` |
| `list_payout_payments` | Recorded payments and the accruals each settled; filter by project, creator, status, date | `GET /api/v2/payout-payments` |
| `get_payout_payment` | One recorded payment | `GET /api/v2/payout-payments/{id}` |
| `get_payout_account` | Whether a creator can be paid through the connected provider (Dots), who they're paid as, and the org's agent-payment switch and limits. **Org admin only.** | `GET /api/v2/creators/{id}/payee-links` |

**Notes and documents**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `list_notes` | Every document with text in the org (organization, projects, campaigns, creators, videos, payout methods, structures, payments and members), with a preview; filter by type, project or text | `GET /api/v2/notes` |
| `get_notes` | One document in full with its `readme_version`; `entity_type=me` is the person you're helping, `member` a teammate | `GET /api/v2/notes/{entity_type}/{id}` |
| `get_my_profile` | Who the connection acts as: name, org and role, scopes, and their personalization document | `GET /api/v2/me` |
| `list_members` | The org's members and whether each has a personalization document | `GET /api/v2/members` |

**Exports**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `export_csv` | Any CSV the app exports (creators, videos, campaigns, goals, the campaign leaderboard, the payouts tracker, payments, accruals and Insights charts) with the same columns and filters, as a file for the agent to save. See [Exporting CSVs](#exporting-csvs) | `GET /api/v2/exports/{dataset}` |

**What's new in trackagoat**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `get_changelog` | Changelog entries, newest first: date, title, link to the entry in the docs and its text as Markdown. Filter by `since` (a date) and `q` (search words). Needs no scope and doesn't count toward the daily request limit. Ask your agent "what's new in trackagoat?" and it uses this tool | `GET /api/v2/changelog` |

The data model is also available as the MCP resource `trackagoat://schema`.

### Write tools

All require the `write` scope (**Make changes** when connecting with sign-in). A key without it gets an `insufficient_scope` error and nothing is changed; a signed-in client is asked to reconnect with **Make changes**. Tools marked **can't be undone** are annotated `destructiveHint: true`, so most clients ask before running them. Tools marked **Org admin only** need the connection to act as an org admin (`insufficient_role` otherwise).

**Creators and accounts**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `add_creator` | Track a creator with one or more platform accounts; starts a profile + video-discovery scrape | `POST /api/v2/creators` |
| `add_creators` | Track up to 50 creators in one project in one call | `POST /api/v2/projects/{id}/creators` |
| `add_account` | Add another platform account to an existing creator | `POST /api/v2/creators/{id}/accounts` |
| `update_creator` | Rename a creator or replace their `metadata` | `PATCH /api/v2/creators/{id}` |
| `update_account` | Rename an account's handle, change its tracking mode, hashtags or tracking dates | `PATCH /api/v2/accounts/{id}` |
| `reenable_creators` | Turn tracking back on for creators trackagoat disabled automatically. **Org admin only.** | `POST /api/v2/projects/{id}/creators/bulk-reenable` |
| `remove_creator` | Stop tracking a creator and all their accounts. **Can't be undone** through MCP or the API. **Org admin only.** | `DELETE /api/v2/creators/{id}` |
| `bulk_remove_creators` | The same for up to 100 creators. **Can't be undone**. **Org admin only.** | `POST /api/v2/projects/{id}/creators/bulk-remove` |
| `remove_account` | Stop tracking one account of a creator. **Can't be undone** (re-add with `add_account`). **Org admin only.** | `DELETE /api/v2/accounts/{id}` |
| `trigger_scrape` | Refresh one account (profile, video stats or new-video discovery), a creator's primary account, or one video now. **Org admin only.** | `POST /api/v2/accounts/{id}/scrape`, `POST /api/v2/creators/{id}/scrape`, `POST /api/v2/videos/{id}/scrape` |
| `update_notes` | Add to or replace any document (`readme`) without overwriting human edits: creators, videos, campaigns, projects, the organization, payout records, or `me`. Organization, project and payout documents are **org admin only**. See [Notes and documents](#notes-and-documents) | `POST /api/v2/notes/{entity_type}/{id}` (creators and videos: `POST /api/v2/creators/{id}/notes`, `POST /api/v2/videos/{id}/notes`) |
| `update_my_readme` | Save the personalization document of the person you're helping, after they approve the draft. Needs `expected_readme_version` | `PATCH /api/v2/me` |

**Videos**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `add_video_by_url` | Track 1 to 50 videos by URL (TikTok or Instagram; YouTube isn't live yet) | `POST /api/v2/projects/{id}/videos` |
| `update_video` | Change a video's title or tracking status (notes go through `update_notes`) | `PATCH /api/v2/videos/{id}` |
| `review_inbox` | Include or exclude videos waiting in the Tracking Inbox, by id or for a whole creator | `PATCH /api/v2/projects/{id}/inbox` |
| `set_video_tracking` | Stop or resume stats tracking for one video, or clear its age-rule exemption. **Org admin only.** | `POST /api/v2/videos/{id}/tracking-disable`, `…/tracking-enable`, `…/tracking-unexempt` |
| `bulk_set_video_tracking` | Stop or resume tracking for up to 200 videos (optionally exempting them from age rules). **Org admin only.** | `POST /api/v2/projects/{id}/videos/bulk-tracking-disable`, `…/bulk-tracking-enable` |

**Campaigns, goals, content groups and share pages**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `create_campaign` | Create an empty campaign in a project | `POST /api/v2/projects/{id}/campaigns` |
| `update_campaign` | Rename a campaign, replace its `readme` (pass `expected_readme_version` to get `notes_conflict` instead of overwriting a newer edit), or set its view target and deadline | `PATCH /api/v2/campaigns/{id}` |
| `add_to_campaign` | Add a creator, video or sub-campaign to a campaign | `POST /api/v2/campaigns/{id}/items` |
| `remove_from_campaign` | Take a creator, video or sub-campaign out of a campaign (the item itself is kept) | `DELETE /api/v2/campaigns/{id}/items` |
| `delete_campaign` | Permanently delete a campaign and its membership list. **Can't be undone**. **Org admin only.** | `DELETE /api/v2/campaigns/{id}` |
| `create_goal` | Give a creator a posts / views / engagement / followers goal per day, week or month | `POST /api/v2/creators/{id}/goals` |
| `update_goal` | Change a goal's metric, period, target or dates, or pause it (`is_active: false`) | `PATCH /api/v2/creators/{id}/goals/{goalId}` |
| `delete_goal` | Permanently delete a goal. **Can't be undone**, and goal-based payouts for periods not yet calculated are judged without it; ending it with `update_goal` (`end_date`) is safer | `DELETE /api/v2/creators/{id}/goals/{goalId}` |
| `create_content_group` | Link the same content posted on several platforms | `POST /api/v2/content-groups` |
| `update_content_group` | Rename a content group, change its notes, add or remove videos | `PATCH /api/v2/content-groups/{id}` |
| `delete_content_group` | Permanently delete a content group (its videos are kept). **Can't be undone** | `DELETE /api/v2/content-groups/{id}` |
| `create_share_link` | Create or replace a creator's PIN-protected page; returns the url and PIN. **Org admin only.** | `POST /api/v2/creators/{id}/share-link` |
| `update_share_link` | Turn a share page off or on, change its sections, or set a new PIN. **Org admin only.** | `PATCH /api/v2/creators/{id}/share-link` |
| `bulk_create_share_links` | Create share pages for up to 200 creators; returns every url and PIN. **Org admin only.** | `POST /api/v2/share-links/bulk` |
| `delete_share_link` | Permanently delete a creator's share page. **Can't be undone**; prefer `update_share_link` with `enabled: false`. **Org admin only.** | `DELETE /api/v2/creators/{id}/share-link` |
| `create_annotation` | Add a dated note to a creator's, campaign's or video's charts | `POST /api/v2/analytics/annotations` |
| `update_annotation` | Change an annotation someone added | `PATCH /api/v2/analytics/annotations/{id}` |
| `delete_annotation` | Permanently delete an annotation someone added. **Can't be undone** | `DELETE /api/v2/analytics/annotations/{id}` |

**Comment settings**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `update_comment_settings` | Set the minimum comment count and replace the brand or competitor terms behind the `brand_mention` and `competitor` signals. **Org admin only.** | `PATCH /api/v2/comment-settings` |

**Conversions**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `create_conversion_event` | Define a kind of conversion (e.g. `signup`) with a default value | `POST /api/v2/projects/{id}/conversion-events` |
| `update_conversion_event` | Change an event's name, description, default value or metadata, or deactivate / reactivate it | `PATCH /api/v2/projects/{id}/conversion-events/{eventId}` (deactivating is the same as `DELETE …/{eventId}`) |
| `record_conversions` | Record 1 to 500 conversions credited to creators, accounts or videos; `external_id` dedupes repeats | `POST /api/v2/projects/{id}/conversions` |
| `void_conversion` | Void a conversion (refund, chargeback, fraud): excluded from totals, kept for audit. **Can't be undone**. **Org admin only.** | `DELETE /api/v2/projects/{id}/conversions/{conversionId}` (`mode=void`) |

**Payouts**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `create_payout_structure` | Create a payout rule: recurring, goals, CPM, view-threshold bonus or per conversion. **Org admin only.** | `POST /api/v2/payout-structures` |
| `update_payout_rule` | Replace a structure's rule with a new version, effective from the period in progress. **Org admin only.** | `PATCH /api/v2/payout-structures/{id}` (`kind: "rule"`) |
| `set_cpm_view_lifetime` | Set how long after posting a video keeps earning on a CPM structure (`view_lifetime_days`, or `null` for forever). Saves a new version with nothing else changed, effective from the period in progress. Counts as two API requests. **Org admin only.** | `GET` then `PATCH /api/v2/payout-structures/{id}` |
| `update_payout_structure` | Rename a structure, replace its notes or metadata, or un-archive it. **Org admin only.** | `PATCH /api/v2/payout-structures/{id}` (`kind: "cosmetic"`) |
| `archive_payout_structure` | Stop a structure earning and end every assignment; past accruals stay payable. Un-archiving doesn't bring the assignments back. **Org admin only.** | `DELETE /api/v2/payout-structures/{id}` (`mode=archive`) |
| `delete_payout_structure` | Permanently delete a structure that never produced an accrual. **Can't be undone**. **Org admin only.** | `DELETE /api/v2/payout-structures/{id}?mode=hard` |
| `assign_payout_structure` | Put creators on a structure, from now or a later date. **Org admin only.** | `POST /api/v2/payout-structures/{id}/assignments` |
| `unassign_payout_structure` | Take creators off a structure; what they earned stays payable. **Org admin only.** | `DELETE /api/v2/payout-structures/{id}/assignments` |
| `set_accrual_status` | Reject or void an earned accrual so it is no longer owed. **Can't be undone**. **Org admin only.** | `PATCH /api/v2/payout-accruals/{id}` |
| `create_payout_method` | Add a payout method (e.g. Wise). **Org admin only.** | `POST /api/v2/payout-methods` |
| `update_payout_method` | Rename a method, replace its notes or metadata, set or remove its icon (`icon_path`), or disable / re-enable it. **Org admin only.** | `PATCH /api/v2/payout-methods/{id}` |
| `delete_payout_method` | Permanently delete a payout method. **Can't be undone**; prefer disabling it. **Org admin only.** | `DELETE /api/v2/payout-methods/{id}` |
| `record_payout_payment` | Record a payment made outside trackagoat and mark the accruals it covers as paid, optionally with a receipt (`payment_id` + `attachment_path`). It doesn't send money. Requires `idempotency_key`. **Org admin only.** | `POST /api/v2/payout-payments` |
| `update_payout_payment` | Replace a payment's notes or metadata. **Org admin only.** | `PATCH /api/v2/payout-payments/{id}` |
| `send_payout` | **Sends real money** through the org's connected provider (Dots) and records the payment. Only works after an org admin turns on agent payments, within the org's limits. Supports a note to the creator, SMS text, description and 1099 exemption. Requires `idempotency_key`. **Org admin only.** | `POST /api/v2/payout-payments/send` |
| `void_payout_payment` | Void a payment; its accruals and supplemental amounts go back to owed. **Can't be undone**. **Org admin only.** | `PATCH /api/v2/payout-payments/{id}` (`status: "voided"`) |
| `get_payouts_tracker` | Per-creator earned, paid, adjustments and outstanding for a project, with totals (earned − paid + adjustments = outstanding) | `GET /api/v2/payouts/tracker` |
| `list_payout_supplementals` | List supplemental amounts (owed corrections on top of paid accruals) | `GET /api/v2/payout-supplementals` |
| `create_payout_supplemental` | Record that a creator is owed more for a paid accrual; settled by `record_payout_payment`. Requires `idempotency_key`. **Org admin only.** | `POST /api/v2/payout-supplementals` |
| `void_payout_supplemental` | Void an owed supplemental amount. **Can't be undone**. **Org admin only.** | `PATCH /api/v2/payout-supplementals/{id}` (`status: "void"`) |

**Receipts and method icons**

| Tool | What it does | REST equivalent |
|------|--------------|-----------------|
| `upload_receipt` | Upload a payment receipt (up to 3 MB) as base64; returns the `path` to pass to `record_payout_payment`. **Org admin only.** | `POST /api/v2/payouts/payment-receipts` |
| `get_receipt_upload_url` | Get a signed URL to PUT a receipt of up to 5 MB to (valid 2 hours), and the `path` to pass back. **Org admin only.** | `POST /api/v2/payouts/payment-receipts/upload-url` |
| `upload_payout_method_icon` | Upload a PNG, JPEG or WebP icon (up to 1 MB) as base64 and set it on a payout method. **Org admin only.** | `POST /api/v2/payout-methods/{id}/icon` |
| `get_payout_method_icon_upload_url` | Get a signed URL to PUT an icon to, and the `path` to set with `update_payout_method`. **Org admin only.** | `POST /api/v2/payout-methods/{id}/icon/upload-url` |

To attach a receipt, the agent generates a UUID as `payment_id`, uploads the file under it (`upload_receipt`, or `get_receipt_upload_url` and a `PUT` of the raw bytes with the same `Content-Type`), then calls `record_payout_payment` with that `payment_id` and `attachment_path` set to the returned `path`. A receipt can only be attached when the payment is recorded, as in the app. Allowed receipt types: PNG, JPEG, WebP, PDF, CSV, Word and Excel. Inline uploads are checked: the file's contents must match `content_type`.

Tools that create something or act in bulk accept an optional `idempotency_key` (it is sent as the REST `Idempotency-Key` header). Have the agent pass a fresh UUID per logical write: if a call times out and is retried with the same key and arguments within 24 hours, you get the original result (marked `"replayed": true`) instead of a duplicate. A retry while the first call is still running fails with `idempotency_in_progress` (try again in a couple of seconds), and reusing a key with different arguments fails with `idempotency_key_reused`. `record_payout_payment`, `send_payout` and `create_payout_supplemental` require a key. Tools that set a value (`update_*`, `set_*`, deletes, voids, assignments) have no key because repeating them has the same effect; `set_cpm_view_lifetime` takes one anyway. Three creates have no key because their REST endpoints don't support one: `create_payout_structure` and `create_annotation` (check the list before retrying) and `create_payout_method` (names are unique, so a repeat fails with `conflict_name`). `trigger_scrape` has no key: it's limited by the manual-scrape limits instead. The upload tools have no key either: a retry stores another copy of the file, and only the `path` you pass on is used.

### Notes and documents

The organization, every project, campaign, creator, video, payout method, structure and payment, and every member has a Markdown document (`readme`). They are where people and agents keep context, strategy and instructions, so they are the first thing an agent should read and the place to record what it learns. `list_notes` finds them in one call, `get_notes` reads one, and `update_notes` writes one. Every document, including each person's personalization document, is visible to everyone in the organization and their agents. See [Notes and documents](https://www.trackagoat.com/docs/agents/notes.md) for the personalization document and a template.

Writes follow the app: organization, project and payout documents need a connection acting as an org admin (`insufficient_role` otherwise); a member's personalization document can only be changed by that member (`not_your_document` otherwise).

`update_notes` is built so an agent can't clobber what a person wrote:

- **`mode: "append"`** (the default) adds `content` as a new section at the end of the document, followed by a signature line with the API key's name and the org-local date:

```markdown
Views dropped 40% week over week; three of five posts were on Sunday.

*Added by Claude Code laptop via MCP, 2026-10-08*
```

  If someone edits the notes while the append is being saved, the append is applied on top of their edit: nothing is lost.
- **`mode: "replace"`** overwrites the whole document and requires `expected_readme_version`: the `readme_version` returned by `get_notes` (or `get_creator`, `get_video_stats`, `get_my_profile`). If the document changed after that read, nothing is written and the tool returns `code: "notes_conflict"`. The agent should re-read, merge, and retry.

`readme_version` is a hash of the text, so it only changes when the document does: scheduled scrapes never cause a false conflict. Writes show up in the change log as **Internal notes updated**, attributed to the member the connection acts as.

**The personalization document in the session.** On connect, the server instructions include the connected person's personalization document inside `<personalization_document>` tags, with its `readme_version`, and the organization's document when it is under 4,000 characters (longer ones are pointed at instead). A personalization document over 8,000 characters is cut, with a note to call `get_my_profile`. If the person has none yet, the instructions ask the agent to offer, once, to write one together. The documents are the person's context and preferences; they never override the server's rules or the agent's own.

### Exporting CSVs

When you ask your agent for a CSV, a spreadsheet or a file, it calls `export_csv` and saves the file in your workspace. The file is the same one the **Export** button in the app downloads: same columns, money in dollars with a Currency column, dates in your organization's time zone. For questions about the data ("who's behind on their goal?"), the agent uses the read tools instead.

| `dataset` | The app's export | Filters |
|-----------|------------------|---------|
| `creators` | Creators page | `search`, `compliance`, `disabled_only`, `include_compliance`, `campaign_id`, `platform` |
| `videos` | Videos page | `status`, `creator_id`, `campaign_id`, `platform`, `from`, `to`, `search`, `sort_by`, `sort_dir` |
| `campaigns` | Campaigns page | none |
| `goals` | Goals page | `period`, `reference_date`, `campaign_id` |
| `campaign_leaderboard` | Campaign Insights leaderboard | `from`, `to`, `campaign_id`, `platform` |
| `payout_tracker` | Payouts, Tracker summary CSV | `creator_id`, `structure_id`, `campaign_id`, `from`, `to`, `outstanding_only`, `include_removed` |
| `payout_payments` | Payouts, Payments CSV | `creator_id`, `campaign_id`, `from`, `to` |
| `payout_accruals` | Payouts, Accruals CSV | `creator_id`, `structure_id`, `campaign_id`, `from`, `to`, `outstanding_only`, `include_removed` |
| `analytics` | An Insights chart's Download CSV | `entity`, `entity_id` or `entity_ids`, `metric` and the other `get_analytics` arguments |

Every dataset except `analytics` needs `project_id`. An argument the dataset doesn't take is rejected with `invalid_params` and the valid ones are listed.

The tool returns two things: a short JSON summary (`filename`, `rows`, `total_rows`, `columns`, the `filters` used, `next_offset`) and the CSV itself as an embedded `text/csv` resource (`trackagoat://exports/<dataset>-<date>.csv`). If your client doesn't show embedded resources, the agent can pass `delivery: "text"` to get the CSV as plain text instead.

Each call returns up to about 1 MB. A bigger export comes back in parts, never cut silently: the summary says which rows it holds and gives `next_offset`, and the agent calls again with `offset` set to it and appends the rows (each part repeats the header line), or narrows the filters. Each call counts as one API request.

Examples:

- "Export September payouts to a CSV." The agent calls `export_csv` with `dataset: "payout_tracker"`, the project, `from: "2026-09-01"` and `to: "2026-09-30"`, then saves `payouts-tracker-….csv`.
- "Give me all videos from the *Spring Launch* campaign as a spreadsheet." The agent finds the campaign with `list_campaigns`, then calls `export_csv` with `dataset: "videos"` and its `campaign_id`.

### What the MCP server doesn't do

Everything an organization's API key can do through `/api/v2` can also be done over MCP, except the following, which are left out on purpose:

| Not available over MCP | Why |
|------|-----|
| Creating and revoking API keys (`/api/v2/organizations/{orgId}/api-keys`) | Keys are credentials. An agent shouldn't mint or revoke them; an org admin does that in **Org settings → API keys**. |
| Organization administration: creating an organization, creating or renaming projects, inviting, changing or removing members, cancelling invitations (`/api/v2/user/organizations`, `/api/v2/organizations/{orgId}/…`) | These need the `admin` scope. Sign-in connections never get it, and MCP tools only use `read` and `write`. Creating an organization also acts outside the connected organization. |
| Permanently deleting a conversion or conversion event (`mode=hard`) | Needs the `admin` scope, because it erases revenue history. Use `void_conversion` or `update_conversion_event` with `is_active: false`. |
| Removing a video (`DELETE /api/v2/videos/{id}`) | Use `set_video_tracking` or `review_inbox` to stop counting a video (or `update_video` with `tracking_status: "excluded"`, which is what the REST `DELETE` does). |
| trackagoat staff endpoints (`/api/v2/admin/…`) | Platform administration, not part of an organization's data. |
| The OpenAPI documents (`/api/v2/openapi.json`, `/api/v2/analytics/schema`) | They describe the REST API itself; MCP clients get each tool's schema from the server, and `get_schema` describes the data model. |
| The retired `/api/v2/creators/{id}/stats` and `/api/v2/campaigns/{id}/stats` | They return `410 Gone`. Use `get_analytics`. |

## Limits

- **Each tool call counts as one API request** toward your org's daily `max_api_requests_per_day` limit and the per-key limit of 60 requests per minute, the same as a REST call. Connecting and listing tools are free. A signed-in connection is limited like one key.
- `get_changelog` is the exception: it reads public content, so it never counts toward the daily limit. It has its own limit of 60 calls per minute per key or connection.
- Protocol traffic (connect, list tools, ping) is capped at 120 requests per minute per key or connection.
- `trigger_scrape` also counts against your plan's daily manual-scrape limit.
- `add_creator`, `add_creators`, `add_account` and `add_video_by_url` count against your plan's account and video limits; `create_campaign`, `create_conversion_event`, `create_payout_structure` and `create_payout_method` count against their plan limits.
- `set_cpm_view_lifetime` counts as two API requests (it reads the structure, then saves it).
- Analytics ranges can't start before your plan's data-retention window.

When a limit is hit the tool returns an error with `code: "rate_limit_exceeded"` and `retry_after_seconds`. See [Limits & plan tiers](https://www.trackagoat.com/docs/guides/limits.md).

## Errors

Tool failures come back as MCP tool errors (`isError: true`) whose text is JSON:

```json
{
  "error": "add_creator requires the 'write' scope. Granted: [read]",
  "code": "insufficient_scope",
  "status": 403,
  "hint": "This API key lacks the 'write' scope. An org admin can create a key with that scope in Org settings → API keys."
}
```

`code` and `status` match the [REST error codes](https://www.trackagoat.com/docs/api-reference/errors.md); every error has a `code`, so an agent can branch on it instead of parsing `error`. When the route returns extra context, it's in `details`. Codes you'll see most:

| Code | Status | Meaning |
|------|--------|---------|
| `unknown_parameter` | 400 | The call passed a parameter the tool doesn't have. `error` names it and lists the valid ones; `details` has `unknown_parameters`, `valid_parameters` and, for near-misses like `projectId`, `did_you_mean`. Nothing ran. |
| `invalid_params` | 400 | A parameter has the wrong type or value; `details` lists the fields. |
| `already_tracked` | 409 | `add_creator`: a handle is already tracked in the project. `error` names the handle and the creator; `details` has `creator_id` and `account_id`. Nothing was created. |
| `duplicate_account` | 400 | `add_creator`: the same platform + handle appears twice in `accounts`. |
| `not_found` | 404 | No such id in your organization. |
| `insufficient_scope` | 403 | The key or connection lacks the scope the tool needs. |
| `insufficient_role` | 403 | Only org admins can do this in the app, and the connection acts as a member who isn't one: for example payments and payout setup, removing or re-enabling creators, removing accounts, stopping or resuming video tracking, `trigger_scrape`, organization and project documents, share pages, `send_payout`, `get_payout_account` and the comment settings. Every such tool says **Org admin only** above and in its description. |
| `not_your_document` | 403 | `update_notes` on another member's personalization document. Only its owner can change it; read it with `get_notes`. |
| `notes_conflict` | 409 | A replace whose `expected_readme_version` is out of date. Nothing was written: re-read, merge, retry. |
| `plan_feature_unavailable` | 403 | The organization's plan doesn't include the feature: Comment Insights tools on the Free plan. Retrying won't help. |
| `limit_reached` | 402 | A plan limit; `details` has the limit and current usage. |
| `rate_limit_exceeded` | 429 | Wait `retry_after_seconds`. |

```json
{
  "error": "Unknown parameter 'range'. Valid parameters: entity, entity_id, entity_ids, metric, granularity, bucket_days, mode, from, to, compare, breakdown, top_n, base_metric.",
  "code": "unknown_parameter",
  "status": 400,
  "hint": "get_analytics accepts only the parameters listed above; unknown ones are rejected rather than ignored. Remove or rename them and call again.",
  "details": { "unknown_parameters": ["range"], "valid_parameters": ["entity", "entity_id", "…"] }
}
```
An invalid, expired or revoked key is rejected before any tool runs, with HTTP `401`; your client will show the server as failing to connect. So is a key whose member left the org (`key_member_removed`), or a key made for someone else whose creator left, was changed to member or was suspended (`key_creator_removed`, `key_creator_not_admin`, `key_creator_banned`). **Org settings → API keys** shows why a key stopped working. A signed-in client gets the same `401` with a `WWW-Authenticate` challenge and refreshes or re-runs sign-in on its own; a write tool called without **Make changes** gets HTTP `403` `insufficient_scope`, which prompts it to reconnect.

## Example prompts

- "List my trackagoat projects, then summarize which creators in *Spring Launch* posted least this month and whether their data is fresh."
- "Which 10 videos got the most views in the last 30 days? Group them by creator."
- "Compare views for my three campaigns this quarter against last quarter."
- "How healthy is the *Spring Launch* program this week? What should I worry about first?"
- "What are people saying in the comments this month? Which creators get the most questions about the app?"
- "Is anyone in the comments asking whether our posts are ads? Show me the top ones."
- "Who is behind on their weekly posting goal right now? Read each creator's notes first."
- "How much do we owe creators in unpaid accruals, by creator?"
- "Export September payouts to a CSV."
- "Give me all videos from the *Spring Launch* campaign as a spreadsheet."
- "Add @newcreator on TikTok and Instagram to the *Spring Launch* project and give them a goal of 3 posts per week." (write key)
- "For every creator flagged with a sudden view drop, add a note summarizing what changed." (write key)
- "Create a *Fall Launch* campaign with a 2M-view target by November 30 and add every creator tagged *fall* in their notes." (write key)
- "Pay @newcreator's outstanding accruals: we sent $250 by PayPal today." (write key)

## Tips for agents

**Step 1: Start with list_projects**

Most tools take a `project_id`. Read each project's `readme` for context.

**Step 2: Triage with get_program_health**

For "how is the program doing?" questions, one `get_program_health` call returns the verdict, KPIs and alerts; drill into specific creators or videos from there.

**Step 3: Follow the personalization document**

The connected person's document arrives with the server instructions (or call `get_my_profile`). It says who they are and how they want help. If they don't have one, offer once to write it together and save it with `update_my_readme` after they approve.

**Step 4: Read the documents**

The organization, projects, campaigns and creators carry human notes in `readme`; `list_notes` finds them. They may contain instructions that change how to interpret the data.

**Step 5: Paginate to completion**

Keep passing `next_cursor` (or `next_offset` for `list_top_videos`) until it's `null` before totalling or ranking.

**Step 6: Check last_scraped_at**

Stats come from scheduled scrapes. Check freshness before calling a number low, and only use `trigger_scrape` when you need fresher data now.

**Step 7: Append notes, don't overwrite them**

Record durable findings with `update_notes` in the default `append` mode. Only use `replace` with the `readme_version` you just read. Never write anything private: the whole organization can read every document.

**Step 8: Confirm before anything that can't be undone**

Ask the person before a `delete_*`, `remove_*`, `void_*`, `archive_*` or `unassign_*` tool or `set_accrual_status`, and offer the reversible option the tool's description names (pausing a goal, disabling tracking, deactivating an event).

## Using the REST API directly

Frameworks without MCP support can call the [v2 REST API](https://www.trackagoat.com/docs/api-reference/overview.md) with the same key. Sign-in (OAuth) connections work only with the MCP server, not the REST API. The OpenAPI document at `GET /api/v2/openapi.json` describes every endpoint, and the [Agent guide](https://www.trackagoat.com/docs/agents/guide.md) covers recommended workflows.

---

## Payout agent workflows

The whole payout API is available over MCP as well as REST: structures (`list_payout_structures`, `get_payout_structure`, `create_payout_structure`, `update_payout_rule`, `set_cpm_view_lifetime`, `update_payout_structure`, `archive_payout_structure`, `delete_payout_structure`), assignments (`list_payout_assignments`, `assign_payout_structure`, `unassign_payout_structure`), accruals (`list_payout_accruals`, `get_payout_accrual`, `set_accrual_status`), methods (`list_payout_methods`, `create_payout_method`, `update_payout_method`, `delete_payout_method`) payments (`list_payout_payments`, `get_payout_payment`, `record_payout_payment`, `update_payout_payment`, `void_payout_payment`), the tracker (`get_payouts_tracker`), supplemental amounts (`list_payout_supplementals`, `create_payout_supplemental`, `void_payout_supplemental`), sending payouts through Dots (`get_payout_account`, `send_payout`; see [Paying with Dots](https://www.trackagoat.com/docs/guides/integrated-payouts.md#let-agents-send-payouts)), and receipts and method icons (`upload_receipt`, `get_receipt_upload_url`, `upload_payout_method_icon`, `get_payout_method_icon_upload_url`). The workflows below show the REST calls; each step names the MCP tool that does the same. **Reads need the `read` scope; writes (recording payments, voiding, assigning structures) need the `write` scope**: a read-only key gets `403 insufficient_scope` on any mutation. See the [Payouts API reference](https://www.trackagoat.com/docs/api-reference/payouts.md) for full endpoint documentation.

### Workflow 1: Weekly payout review

**Goal:** find all creators with outstanding accruals, summarize total owed, and surface any that have been outstanding more than 30 days.

Over MCP: `list_projects`, then `list_payout_accruals` with `status: "earned"`.

**Step 1: Discover the project**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/projects" | jq '.data[0].id'
```

Store the project ID as `$PROJECT_ID`.

**Step 2: List outstanding accruals**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/payout-accruals?project_id=$PROJECT_ID&status=earned" \
  | jq '{total: (.data | length), creators: [.data[] | .creator_id] | unique | length}'
```

Paginate using `meta.nextCursor` until `meta.hasMore` is false.

**Step 3: Flag stale accruals**

Filter `data[].period_end_at` older than 30 days ago. Report creator handle + amount + how many days outstanding.

**Expected response shape:**
```json
{
  "data": [{
    "id": "uuid",
    "creator_id": "uuid",
    "structure_id": "uuid",
    "amount_cents": 5000,
    "currency": "USD",
    "status": "earned",
    "period_start_at": "2026-04-07T00:00:00Z",
    "period_end_at": "2026-04-13T23:59:59Z"
  }],
  "meta": { "hasMore": false, "nextCursor": null }
}
```

> **Warning: Common pitfalls**
>
> - `amount_cents` is an integer in cents: divide by 100 for display.
> - Voided and rejected accruals are excluded by the `status=earned` filter; don't double-count by omitting it.

---

### Workflow 2: Audit unpaid creators across an org

**Goal:** identify which creators have earned money but received zero payments, broken down by project.

Over MCP: `list_projects`, `list_payout_structures`, `list_payout_accruals` and `list_payout_payments`.

**Step 1: List all projects**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/projects" | jq '[.data[] | {id, name}]'
```

**Step 2: For each project, fetch payout structures**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/payout-structures?project_id=$PROJECT_ID&is_active=true"
```

**Step 3: Fetch earned accruals per project**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/payout-accruals?project_id=$PROJECT_ID&status=earned"
```

**Step 4: Fetch all payments**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/payout-payments?project_id=$PROJECT_ID"
```

**Step 5: Compute the gap**

Group accruals by `creator_id`. For each creator, sum `amount_cents` of `earned` accruals. Compare against `total_amount_cents` of `paid` payments. Report the delta.

> **Warning: Common pitfalls**
>
> - Don't compare across currencies: all amounts are `USD` (cents). The `currency` field is included for future-proofing.
> - A creator with no payment rows is fully outstanding; don't confuse "no payments" with "no debt."

---

### Workflow 3: Record a payment for a creator via Venmo

**Goal:** settle outstanding accruals for a specific creator and record the payment against the Venmo method.

Over MCP: `list_creators` with `search`, `list_payout_accruals`, `list_payout_methods`, optionally `upload_receipt`, then `record_payout_payment` with a fresh `idempotency_key` (the tool requires one).

**Step 1: Find the creator**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/creators?project_id=$PROJECT_ID&search=creatorhandle" \
  | jq '.data[0].id'
```

**Step 2: List outstanding accruals for this creator**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/payout-accruals?project_id=$PROJECT_ID&status=earned" \
  | jq '[.data[] | select(.creator_id == $CREATOR_ID)]'
```

Sum the `amount_cents` values to get `total_amount_cents`.

**Step 3: Find the Venmo method ID**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/payout-methods" \
  | jq '.data[] | select(.name == "Venmo") | .id'
```

**Step 4: Record the payment**

```bash
curl -s -X POST -H "Authorization: Bearer $TGA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  "https://www.trackagoat.com/api/v2/payout-payments" \
  -d '{
    "creator_id": "CREATOR_UUID",
    "method_id": "VENMO_METHOD_UUID",
    "total_amount_cents": 5000,
    "currency": "USD",
    "accrual_ids": ["ACCRUAL_UUID_1", "ACCRUAL_UUID_2"],
    "note": "Recorded by automated weekly review agent",
    "paid_at": "2026-05-07T15:00:00Z"
  }'
```

**Expected 201 response:**
```json
{
  "data": {
    "id": "payment-uuid",
    "creator_id": "...",
    "total_amount_cents": 5000,
    "status": "paid",
    "paid_at": "2026-05-07T15:00:00Z"
  }
}
```

> **Warning: Common pitfalls**
>
> - An `Idempotency-Key` header (a UUID) is **required**; the request is rejected with 400 without one. If the request times out, retry with the same key: the server returns the original payment row without double-counting.
> - If `total_amount_cents` differs from the sum of linked accruals, the API requires an `adjustment_reason` field explaining the difference (e.g. rounding, partial payment).
> - Verify the method is active (`is_active: true`) before sending: pass `include_disabled=1` to `GET /api/v2/payout-methods` to confirm. A method that has been hard-removed returns `404 not_found`.

---

### Workflow 4: Set up a new creator with a goal-based payout structure

**Goal:** assign an existing payout structure to a newly-tracked creator and confirm the assignment is active.

Over MCP: `list_payout_structures`, `assign_payout_structure`, then `list_payout_assignments`.

**Step 1: Confirm the structure exists and is active**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/payout-structures?project_id=$PROJECT_ID&is_active=true" \
  | jq '[.data[] | select(.criteria_type == "goals") | {id, name, period, amount_cents}]'
```

Pick the structure ID you want to assign.

**Step 2: Assign the creator**

```bash
curl -s -X POST -H "Authorization: Bearer $TGA_KEY" \
  -H "Content-Type: application/json" \
  "https://www.trackagoat.com/api/v2/payout-structures/$STRUCTURE_ID/assignments" \
  -d '{"creator_ids": ["CREATOR_UUID"]}'
```

**Expected 201 response:**
```json
{
  "data": {
    "assigned": 1,
    "already_assigned": 0,
    "not_in_project": 0,
    "results": [{ "creator_id": "...", "status": "assigned", "assignment_id": "..." }]
  }
}
```

**Step 3: Verify the assignment is active**

```bash
curl -s -H "Authorization: Bearer $TGA_KEY" \
  "https://www.trackagoat.com/api/v2/payout-structures/$STRUCTURE_ID/assignments" \
  | jq '[.data[] | select(.unassigned_at == null) | .creator_id]'
```

> **Warning: Common pitfalls**
>
> - For goal-based structures, the creator must have at least one goal whose period matches the structure's period. If not, the assignment is created but accruals won't be computed. Check with `get_goal_compliance` or the goal compliance endpoint.
> - If the creator was previously unassigned from this structure, a new assignment row is created: past accruals from the prior assignment remain payable.
> - `assigned: 0, already_assigned: 1` means the creator is already active on this structure: not an error.

---
Source: https://www.trackagoat.com/docs/agents/mcp
