trackagoat logotrackagoat/Docs

Command Palette

Search for a command to run...

Getting started

  • Welcome
  • Quickstart
  • Core concepts

Guides

  • Creators & Accounts
  • Creators
  • Instagram tracking
  • YouTube tracking
  • Videos
  • Campaigns
  • Creator Goals
  • Tracking Inbox
  • Content calendar
  • How scraping works
  • Analytics & metrics
  • Similar creator pools
  • Over-posting & suppression
  • Program Health
  • Sentiment Radar
  • API keys
  • Limits & plan tiers
  • Notifications
  • Payouts
  • Conversions

API reference

  • Overview
  • Authentication
  • Errors
  • Projects
  • Creators
  • Accounts
  • Videos
  • Content Groups
  • Campaigns
  • Analytics
  • Aggregate Analytics
  • Goal Compliance
  • Payouts
  • Conversions
  • Schema

For agents

  • Agent guide
  • Data model
  • MCP & tooling

Platform

  • Brand
  • Changelog
  • Support
DocsAPI reference

Conversions

Define conversion event types per project and record conversions attributed to a creator, account, or video via the trackagoat v2 API.

PreviousPayoutsNextSchema

On this page

  • Conversion Events
  • GET /api/v2/projects/{projectId}/conversion-events
  • POST /api/v2/projects/{projectId}/conversion-events
  • GET /api/v2/projects/{projectId}/conversion-events/{eventId}
  • PATCH /api/v2/projects/{projectId}/conversion-events/{eventId}
  • DELETE /api/v2/projects/{projectId}/conversion-events/{eventId}
  • Conversions
  • GET /api/v2/projects/{projectId}/conversions
  • POST /api/v2/projects/{projectId}/conversions
  • Idempotency
  • GET /api/v2/projects/{projectId}/conversions/{conversionId}
  • DELETE /api/v2/projects/{projectId}/conversions/{conversionId}
  • Limits
  • Error codes

Trackagoat tracks creator output (views) and creator cost (payouts). Conversions add creator outcome: signups, purchases, installs, or whatever else you count as a result.

There are two resources:

  • Conversion events, the definitions, scoped to a project. signup, purchase, trial_started. Each carries a default value and a currency.
  • Conversions: the facts. Each one references a conversion event and is attributed to a creator, an account, or a video.

All endpoints require a Bearer API key. Responses follow the standard { data, error, meta } envelope. Money is in integer cents. Timestamps are ISO 8601.

Scopes

GET needs the read scope. POST and PATCH need write. DELETE needs write for the default (soft) mode and admin for ?mode=hard: conversions are financial records, and a write-scoped webhook key should not be able to erase revenue history. A key without the required scope is rejected with 403 insufficient_scope.


Conversion Events

GET /api/v2/projects//conversion-events

List the conversion event definitions in a project. Active events only by default.

bash
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/conversion-events"
 
# Include deactivated events
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/conversion-events?include_inactive=true"

Query parameters

ParameterTypeDescription
include_inactivetrueInclude events with is_active: false

Returns an array sorted newest first, with meta.count.

Response fields

FieldTypeDescription
iduuidConversion event ID
project_iduuidOwning project
org_iduuidOwning organization
keystringStable slug used by API callers (e.g. purchase)
namestringHuman label (e.g. Purchase)

POST /api/v2/projects//conversion-events

Define a new conversion event. Requires the write scope.

bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "key": "purchase",
    "name": "Purchase",
    "description": "A completed checkout attributed to a creator link",
    "default_value_cents": 0,
    "currency": "USD"
  }' \
  https://www.trackagoat.com/api/v2/projects/<projectId>/conversion-events

Body

FieldTypeRequiredDescription
keystring✓Lowercase slug, max 63 chars. Must start with a letter or digit and may contain letters, digits, ., -, and _. Unique per project (case-insensitive)
namestring✓Display name, max 100 chars
descriptionstringMax 500 chars
default_value_centsnumber

Returns 201 with the created event.

Currency is fixed at definition time

A conversion event's currency cannot be changed later, and individual conversions have no currency field of their own. They inherit the event's. One event type means one currency, which is what makes SUM(total_value_cents) well-defined for any set of conversions. If you need to record purchases in EUR as well as USD, define two events (purchase_usd, purchase_eur).

Reusing an existing key in the same project returns 409. Exceeding the plan's max_conversion_events_per_project returns 402 limit_reached. See Limits.


GET /api/v2/projects//conversion-events/

Fetch a single event. Returns 404 conversion_event_not_found if the event does not exist in that project.


PATCH /api/v2/projects//conversion-events/

Update an event. Requires the write scope. At least one field must be supplied.

bash
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"default_value_cents": 2500}' \
  https://www.trackagoat.com/api/v2/projects/<projectId>/conversion-events/<eventId>

Body: name?, description?, default_value_cents?, is_active?, metadata?

key and currency are deliberately not updatable. The key is the stable identifier you hard-code into your backend; changing the currency would retroactively reinterpret every conversion already recorded under the event.

Changing default_value_cents affects only conversions recorded after the change. The value is copied onto each conversion at write time.


DELETE /api/v2/projects//conversion-events/

Deactivates by default. Requires the write scope (admin for ?mode=hard).

bash
# Deactivate: the event stops accepting new conversions, history is preserved
curl -X DELETE -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/conversion-events/<eventId>"
 
# Hard delete: removes the definition AND every conversion recorded under it
curl -X DELETE -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/conversion-events/<eventId>?mode=hard"
ParameterTypeDescription
modehardAnything else (including omitting it) deactivates

Returns { id, mode } with meta.conversions_affected: the number of conversions currently attached to the event. Check that number before hard-deleting.

Hard delete cascades

?mode=hard deletes the event row, and conversions cascade with it. That is revenue history and it cannot be recovered. Deactivating (is_active: false) is almost always what you want, because the event stops accepting new conversions while every past conversion stays queryable.

Deactivating also frees a slot against max_conversion_events_per_project, which counts active events only.


Conversions

GET /api/v2/projects//conversions

List conversions in a project, newest occurred_at first.

bash
# All conversions rolled up to one creator, in a date window
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/conversions?creator_id=<uuid>&from=2026-07-01T00:00:00Z"

Query parameters

ParameterTypeDefaultDescription
conversion_event_iduuidOnly conversions of this event type
creator_iduuidRollup filter. See below
account_iduuidRollup filter. See below
video_iduuidOnly conversions attributed to this video
sourcestring

creator_id and account_id are rollup filters

creator_id and account_id match the rolled-up attribution, not just what the caller originally targeted. ?creator_id=X returns every conversion recorded against creator X plus those recorded against X's accounts and X's videos. See Rollup below.

from and to are full ISO 8601 timestamps (2026-07-01T00:00:00Z), not bare dates.

Response

meta carries { hasMore, nextCursor }. Cursors are opaque base64url strings. Pass nextCursor back verbatim as cursor, do not parse or modify it.

Conversion fields

FieldTypeDescription
iduuidConversion ID
conversion_event_iduuidThe event definition
conversion_event_keystring | nullThe event's key, denormalized for convenience
conversion_event_namestring | nullThe event's name
project_iduuidOwning project

POST /api/v2/projects//conversions

Record one conversion, or a batch. Requires the write scope.

bash
# Single conversion, attributed by platform + handle
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "conversion_event_key": "purchase",
    "platform": "tiktok",
    "handle": "charlidamelio",
    "value_cents": 4999,
    "quantity": 2,
    "external_id": "order_10021",
    "occurred_at": "2026-07-21T14:03:00Z"
  }' \
  https://www.trackagoat.com/api/v2/projects/<projectId>/conversions
bash
# Batch (up to 500 per request)
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "conversions": [
      {"conversion_event_key": "signup", "video_id": "<uuid>", "external_id": "user_881"},
      {"conversion_event_key": "signup", "creator_id": "<uuid>", "external_id": "user_882"}
    ]
  }' \
  https://www.trackagoat.com/api/v2/projects/<projectId>/conversions

Body: a single conversion object, or { "conversions": [ ... ] } with 1–500 objects. Each object:

FieldTypeRequiredDescription
conversion_event_iduuidone ofThe event definition, by ID
conversion_event_keystringone ofThe event definition, by key (case-insensitive)
creator_iduuidone ofAttribute to a creator
account_iduuidone ofAttribute to a platform account
video_id

Supply exactly one of conversion_event_id or conversion_event_key, and exactly one target: creator_id, account_id, video_id, or the platform + handle pair. Supplying none, or more than one, returns 400 invalid_body. platform and handle must be supplied together.

Response. A single-conversion request returns 201 with the created conversion. A batch returns 201 with an array in request order. meta.deduped_count reports how many rows in the request were deduplicated against an existing external_id.

Choosing a target

You supplytarget_typeResolution
video_idvideoVideo must be in the project and have an owning account, else 409 video_unattributed
account_idaccountAccount must be in the project and linked to a creator, else 409 account_unlinked
creator_idcreatorCreator must be in the project
platform + handle

platform + handle matching is case-insensitive and a leading @ is optional. It resolves only to an account that already exists in that project: a handle trackagoat does not know returns 404 account_handle_not_found and no account is created. A typo in a webhook should surface as an error, not silently spawn a junk account.

Rollup

Attribution rolls up the hierarchy, and the rollup is written onto the row at insert time:

video  →  account  →  creator

A conversion recorded against a video also counts for that video's account and that account's creator. A conversion recorded against an account also counts for its creator. target_type preserves what you actually attributed to, so a report can still say "attributed to video X" while creator-level totals include it.

That is why ?creator_id=X on the list endpoint returns conversions recorded against X's accounts and videos too.

Attribution is frozen

The rollup is computed once, when the conversion is received. If a video is later reassigned to a different account, its past conversions do not move by default. They were earned under the ownership that existed when they happened, and silently rewriting them would make past reports non-reproducible.

To override that for a single video, POST /api/videos/{videoId}/conversions with {"mode": "reassign"} rewrites every recorded conversion on that video to its current creator; {"mode": "freeze"} explicitly keeps history and dismisses the drift notice. Both require an org admin session. This endpoint is not part of the API-key surface.

Value and quantity

total_value_cents = value_cents × quantity

value_cents falls back to the conversion event's default_value_cents, so a count-style event (a signup) needs no value on every call. quantity defaults to 1. Currency comes from the event. A conversion has no currency field.


Idempotency

Two independent layers apply, and they answer different questions.

1. The Idempotency-Key header: replays the whole response for 24 hours.

Send a fresh UUID per logical request. If the call times out, retry with the same key: the server returns the original response body and status unchanged, with an Idempotency-Replay: true header, and creates nothing new. Reusing a key against a different endpoint returns 409 idempotency_conflict. Keys must be ≤ 255 characters.

2. The external_id field: permanent per-project dedupe.

external_id is unique per project, forever. If you POST a conversion whose external_id already exists in the project, trackagoat does not create a duplicate and does not error: it returns the original conversion's id with deduped: true.

The case that trips people up

A retry with a fresh Idempotency-Key but a repeated external_id is not an error. The header cache misses (new key), so the handler runs, but the external_id collides, so nothing is inserted. You get HTTP 200 with deduped: true and the original conversion's id. Treat that as success. The conversion is already recorded.

(Status 200 rather than 201 applies to the single-conversion form, where the write created nothing. A batch always returns 201; check each row's deduped flag and meta.deduped_count.)

Send external_id whenever you have a natural key: an order ID, a user ID, a webhook event ID. It is the difference between "my retry logic is correct" and "my revenue numbers are correct".


GET /api/v2/projects//conversions/

Fetch a single conversion. Returns 404 conversion_not_found if it does not exist in that project.


DELETE /api/v2/projects//conversions/

Two modes, because both are legitimately needed.

bash
# Void (default): soft, auditable, reversible in the sense that the row survives
curl -X DELETE -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/conversions/<conversionId>?reason=Refunded"
 
# Hard delete: irreversible, requires the admin scope
curl -X DELETE -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/projects/<projectId>/conversions/<conversionId>?mode=hard"
ParameterTypeDefaultDescription
modevoid | hardvoidhard requires the admin scope
reasonstringMax 500 chars. Stored as voided_reason (void mode only)

mode=void sets status: 'voided', records voided_at, voided_by, and voided_reason, and excludes the conversion from every total. The row stays queryable via ?status=voided or ?status=all. This is the right answer for a refund, a chargeback, or a fraudulent order. Voiding an already-voided conversion is a no-op, not an error: safe to retry. Returns the conversion with mode: "void".

mode=hard deletes the row outright. For genuine mistakes and test data only. Requires the admin scope. Returns { id, mode: "hard", deleted: true }.


Limits

max_conversion_events_per_project caps how many conversion event types a project may define. It counts active events only: deactivating one frees a slot.

PlanConversion events per project
Free3
Starter25
UltraUnlimited

Conversion volume is not capped. Silently dropping a customer's revenue data at a cap would be worse than the revenue the cap protects. Exceeding the event-type limit returns 402 limit_reached with meta.current and meta.max.


Error codes

CodeHTTPDescription
invalid_api_key401Missing, expired, or invalid Bearer token
insufficient_scope403Key lacks read, write, or (for ?mode=hard) admin
limit_reached402max_conversion_events_per_project exceeded
invalid_body400Zod validation failed: including a missing or ambiguous target. See meta.errors

A duplicate conversion event key returns 409 with a message naming the key.


See the Conversions guide for a walkthrough of defining your first event and sending your first conversion.

description
string | null
Free-form description
default_value_centsnumberValue applied to a conversion that does not supply its own
currencystringISO 4217 code. Applies to every conversion recorded under this event
is_activebooleanInactive events reject new conversions
metadataobjectAgent-writable structured data
created_atISO 8601
updated_atISO 8601
Integer ≥ 0. Default 0
currencystring3-letter ISO 4217 code, uppercased. Default USD
metadataobjectAgent-writable structured data. Default {}
Exact match on the source field
statusrecorded | voided | allrecordedVoided conversions are hidden unless you ask for them
fromISO 8601 with offsetoccurred_at on or after this instant. Bare dates and offset-less timestamps are rejected.
toISO 8601 with offsetoccurred_at on or before this instant. Bare dates and offset-less timestamps are rejected.
limitnumber50Items per page (max 200)
cursorstringPagination cursor from meta.nextCursor
target_type
creator | account | video
What the caller attributed to
creator_iduuid | nullAlways populated (rollup)
account_iduuid | nullPopulated for account and video targets
video_iduuid | nullPopulated for video targets only
value_centsnumberUnit value. Falls back to the event's default_value_cents
quantitynumberUnits. Default 1
total_value_centsnumbervalue_cents × quantity, computed by the database
currencystringInherited from the conversion event
external_idstring | nullYour own ID for this conversion (permanent dedupe key)
sourcestring | nullFree-form attribution source label
labelstring | nullShort label, max 200 chars
notestring | nullLonger note, max 1000 chars
metadataobjectAgent-writable structured data
statusrecorded | voidedVoided conversions are excluded from totals
voided_atISO 8601 | null
voided_reasonstring | null
occurred_atISO 8601 with offsetWhen the conversion happened (defaults to receipt time). Must carry an explicit offset: 2026-07-21T14:03:00Z or +01:00. A bare local timestamp is a 400.
created_atISO 8601When trackagoat received it
uuid
one of
Attribute to a video
platform + handlestring + stringone ofAttribute to an existing account by handle
value_centsnumberInteger ≥ 0. Defaults to the event's default_value_cents
quantitynumberInteger ≥ 1, max 1,000,000. Default 1
external_idstringYour own ID, max 255 chars. Permanent per-project dedupe key
sourcestringMax 100 chars
labelstringMax 200 chars
notestringMax 1000 chars
metadataobjectDefault {}
occurred_atISO 8601When it happened. Defaults to now
account
Matches an account already in the project
invalid_query400Bad query parameter
invalid_cursor400Cursor could not be decoded. Pass meta.nextCursor back verbatim
conversion_event_not_found404No conversion event with that ID or key in the project
creator_not_found404No creator with that ID in the project
account_not_found404No account with that ID in the project
video_not_found404No video with that ID in the project
account_handle_not_found404No account with that platform + handle in the project. Add the account first
conversion_not_found404No conversion with that ID in the project
conversion_event_inactive409The event is deactivated and cannot accept new conversions
account_unlinked409The account is standalone (not attached to a creator), so nothing can roll up
video_unattributed409The video has no owning account
idempotency_conflict409Idempotency-Key reused against a different endpoint
rate_limit_exceeded429Daily API quota or per-minute rate limit hit