# Authentication and API keys

> Create an API key, choose its scopes and authenticate with a Bearer token. Covers revocation, limits and testing safely.

Every v2 API request needs an API key. Create one in the app, send it as a Bearer token, and give it only the scopes it needs. Every plan includes API access. Free and Starter allow 1 key each, Ultra allows 5. Free allows 200 requests a day, Starter 1,000 and Ultra 50,000.

Keys are:

- **Org-scoped**: a key can only reach data in the org it was created in.
- **Shown once**: the plaintext key is displayed at creation and can't be recovered.
- **Hashed at rest**: trackagoat stores only a SHA-256 hash of the key, never the plaintext.
- **Acting as one member**: a key acts on behalf of the person who created it, or of the member an org admin picked under **Acts as**. Every call is attributed to that member and can never do more than they can in the app. If they leave or are removed from the org, the key stops working. If they stop being an org admin, the key loses the `admin` scope and admin-only actions.

## Creating an API key

**Step 1: Open org settings**

Go to **Org settings → API keys** (org admins only).

**Step 2: Click + New API key**

Give the key a descriptive name, such as `Claude MCP access` or `Analytics script`, choose who it **acts as** (you, by default, or any member of the org) and choose its [scopes](#permission-scopes).

**Step 3: Copy your key immediately**

After you click **Create**, the full key (`tga_<64 hex chars>`) is shown once. Copy it now.

> **Warning:**
>
> If you lose the key, create a new one. trackagoat stores only a hash and can't recover the plaintext.

## Using the key

Include the key as a Bearer token on every request:

```bash
Authorization: Bearer tga_<your-key>
```

Example:

```bash
curl -H "Authorization: Bearer tga_abc123" \
  https://www.trackagoat.com/api/v2/projects
```

The one endpoint that needs no key is [`GET /api/v2/changelog`](https://www.trackagoat.com/docs/api-reference/changelog.md): the changelog is public.

## Key format

```text
tga_<64 lowercase hex characters>
```

Example: `tga_a1b2c3d4e5f6...` (64 hex chars after the prefix).

## Org scoping

Keys are org-scoped: all data returned is limited to the organization the key belongs to. A key can only read and write data inside its own org.

Scoping stops at the org boundary; it does **not** go down to the project level. A key grants access to *every* project in its org, including ones created after the key was issued. There is no way to issue a key that is limited to a single project.

### Testing against the API

Because keys are org-wide, do not run integration tests with a key from an org that holds real data: a test `POST` or `DELETE` will hit production records.

Create a **separate organization** for testing and use a key from that org. It gives you complete isolation: separate creators, campaigns, conversions, and analytics, with no path back to your live org. Ask support to disable scraping on it, which also guarantees test conversions can never generate payout accruals.

This is how you get a sandbox on trackagoat: there is no separate sandbox host or test-mode key prefix. The base URL and key format are the same; the org is what separates test from live.

## Permission scopes

Each key carries one or more permission scopes, chosen at creation time. The scope controls which HTTP methods the key may use:

| Scope | Grants |
|-------|--------|
| `read` | All `GET` endpoints |
| `write` | All `POST`, `PATCH`, and `DELETE` endpoints (in addition to reads if `read` is also granted) |
| `admin` | Org administration: projects, members, invitations, API keys and creating organizations; also entrance-PIN reads and hard deletes. Platform-admin endpoints under `/api/v2/admin/*` additionally need the owner to be a platform admin |

New keys default to `['read', 'write']` unless you narrow the scopes at creation.

### Who a key acts as

A key acts on behalf of one member of the org. By default that is the org admin who created it. An admin can instead pick another member under **Acts as**, for example for a script that a teammate runs. Every request is attributed to that member: change logs, notes signatures, the audit log and `GET /api/v2/me` all show them. The keys list shows each key as "Acts as *name* · created by *admin*".

The key can never do more than that member can do in the app. Its scopes are a ceiling rather than a grant, and every request is checked against the member's **current** role in the org:

- **Member is an org admin**: the key gets all of its scopes.
- **Member is not an org admin** (for example, demoted after the key was created): `admin` is dropped, and everything only org admins can do or see in the app returns `403 insufficient_role`: payments and payout setup, a creator's payout account, removing or re-enabling creators, removing accounts or videos, stopping video tracking, manual scrapes, share pages, comment settings, and organization and project documents. Everything else that `read` and `write` allow still works. The [OpenAPI spec](https://www.trackagoat.com/api/v2/openapi.json) marks each of these endpoints "Requires an org admin".
- **Member left or was removed from the org**: the key is rejected with `401 key_member_removed`. Removing a member also deactivates the keys that act as them.
- **The key was made for someone else, and the admin who created it lost access**: the admin has seen the key, so it only works while they are still an org admin of this org. If they leave or are removed, the key is rejected with `401 key_creator_removed`; if they are changed to member, `401 key_creator_not_admin`; if their account is suspended, `401 key_creator_banned`. Removing, demoting or suspending that person also deactivates those keys. Keys a person created for themselves keep working after they are changed to member, limited to what a member can do.

Deactivated keys stay in **Org settings → API keys** with the reason: revoked, the member it acts as left or was suspended, the admin who created it left, was changed to member or was suspended, or the organization was suspended. To keep a script running after its key's creator loses access, an org admin creates a new key for it.

> **Tip:**
>
> To connect a teammate's coding agent, have them sign in to the [MCP server](https://www.trackagoat.com/docs/agents/mcp.md) with their own trackagoat account. Sign-in identifies them directly and nobody has to share a key. Use API keys for scripts, servers and agents that can't sign in.

### Creating keys over the API

`POST /api/v2/organizations/{orgId}/api-keys` needs the `admin` scope, and a key can only create keys whose scopes it holds itself. Asking for a scope the calling key lacks returns `403 insufficient_scope` with `meta.required` listing the missing scopes. Pass `acts_as_user_id` to create a key that acts as another member; it must be a current member of the org (`400 acts_as_not_member` otherwise), and the `admin` scope needs that member to be an org admin. The response includes `acts_as` and `created_by`.

A write request made with a read-only key is rejected with **403** and `meta.code = "insufficient_scope"`:

```json
{
  "data": null,
  "error": "This action requires the 'write' scope. Granted: [read]",
  "meta": { "code": "insufficient_scope", "required": "write", "granted": ["read"] }
}
```

This applies to every mutating endpoint, including all payout writes (`payout-methods`, `payout-structures`, `payout-payments`, `payout-accruals`, their `[id]` sub-routes, and annotation writes).

## Errors

| Status | `meta.code` | Cause |
|--------|-------------|-------|
| `401` | `invalid_api_key` | Missing, malformed, invalid, revoked or expired key |
| `401` | `key_member_removed` | The member the key acts as is no longer in the org |
| `401` | `key_creator_removed` | The key was made for someone else, and the admin who created it is no longer in the org |
| `401` | `key_creator_not_admin` | The key was made for someone else, and the admin who created it is no longer an org admin |
| `401` | `key_creator_banned` | The key was made for someone else, and the account that created it is suspended |
| `403` | `insufficient_scope` | Key is valid but lacks the scope required for this endpoint (or `admin` was dropped because its member is no longer an org admin) |
| `403` | `insufficient_role` | Only org admins can do this in the app, and the key acts as a member who isn't one |
| `403` | None | Key is valid but the org or user is banned |
| `429` | `rate_limit_exceeded` | Per-key sliding-window (60 req/min) or daily quota exceeded |

```json
{
  "data": null,
  "error": "Invalid or expired API key",
  "meta": { "code": "invalid_api_key" }
}
```

## Revoking a key

Go to **Org settings → API keys** and revoke the key. Revocation is immediate: requests with that key get `401 invalid_api_key`. The key stays in the list, marked **Revoked**.

Removing a member from the org (or a member leaving) deactivates every key that acts as them and every key they created in that org. Changing an admin to member deactivates the keys they created for someone else. For integrations, have an admin who will stay in the org create the key, acting as a member who will stay too.

## Limits

- **Number of keys:** your plan sets `max_api_keys_per_org`. Delete unused keys if you reach it.
- **Rate limit:** 60 requests a minute per key (sliding window).
- **Daily quota:** `max_api_requests_per_day` per org, set by your plan.

See [Plans and limits](https://www.trackagoat.com/docs/guides/limits.md) for current values and [Errors and limits](https://www.trackagoat.com/docs/api-reference/errors.md) for the `402` and `429` responses.

- [API overview](https://www.trackagoat.com/docs/api-reference/overview.md): Base URL, response envelope, pagination and the OpenAPI spec.
- [Errors and limits](https://www.trackagoat.com/docs/api-reference/errors.md): Status codes, rate limits and error response shapes.

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