Skip to content

Payouts

Manage payout methods, structures, assignments, accruals, and payments via the trackagoat API (v2).

Updated Oct 10, 2026 · 13 min read

All payout endpoints require a Bearer API key. Responses follow the standard { data, error, meta } envelope. Amounts are in cents (integer). Timestamps are ISO 8601 UTC.


Payout Methods#

Payout methods are org-scoped. Default methods (Venmo, Cash App, PayPal) cannot be deleted: use PATCH { is_active: false } to disable.

GET/api/v2/payout-methods#

List org's payout methods.

bash
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/payout-methods"
 
# Include disabled methods
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/payout-methods?include_disabled=1"

Query parameters

ParameterTypeDescription
include_disabled1 | trueInclude inactive methods (org admins only)

Response fields: id, org_id, name, icon_url, is_default, is_active, metadata, readme, created_at


POST/api/v2/payout-methods#

Create a custom payout method.

bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Zelle"}' \
  https://www.trackagoat.com/api/v2/payout-methods

Body

FieldTypeRequiredDescription
namestring✓Display name (max 100 chars)
icon_pathstring | nullStorage path of an uploaded icon. A new method's id isn't known until it's created, so add the icon afterwards: see Method icons
metadataobjectAgent-writable structured data
readmestring | nullMarkdown notes

Returns 201 with the created method row.


GET/api/v2/payout-methods/#

Fetch a single method.


PATCH/api/v2/payout-methods/#

Update name, active state, metadata, or readme.

bash
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}' \
  https://www.trackagoat.com/api/v2/payout-methods/<uuid>

Body: name?, is_active?, icon_path?, metadata?, readme? (at least one required). icon_path must be inside this method's folder (orgs/{orgId}/{methodId}/…), as returned by the icon upload URL below; null removes the icon.


Method icons#

Icons are PNG, JPEG or WebP images up to 1 MB, stored in a public bucket. Only org admins can change them (403 forbidden otherwise). Two ways to upload:

Inline (one call). POST /api/v2/payout-methods/{id}/icon with the image as base64. It is checked against content_type (content_type_mismatch otherwise), stored, and set as the method's icon_path; the updated method is returned.

bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d "{\"content_type\": \"image/png\", \"content_base64\": \"$(base64 < wise.png | tr -d '\n')\"}" \
  https://www.trackagoat.com/api/v2/payout-methods/<uuid>/icon

Signed URL. POST /api/v2/payout-methods/{id}/icon/upload-url with { "content_type": "image/png" } returns signed_url, path, max_bytes and expires_in_seconds (2 hours). PUT the raw image to signed_url with the same Content-Type, then PATCH /api/v2/payout-methods/{id} with { "icon_path": "<path>" }.

bash
curl -X PUT -H "Content-Type: image/png" --data-binary @wise.png "<signed_url>"

DELETE/api/v2/payout-methods/#

Delete a custom method. Returns 409 default_method for seeded default methods.


Payout Structures#

Payout structures define earning rules for creators. Scoped to a project. Rule edits create new immutable versions.

GET/api/v2/payout-structures#

List structures in the org (optionally filtered to a project).

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

Query parameters

ParameterTypeDescription
project_iduuidFilter to a project
is_activetrue | falseFilter by active state
limitnumberItems per page (max 100, default 50)
cursorstringPagination cursor

POST/api/v2/payout-structures#

Create a payout structure (creates version 1 atomically).

bash
# Recurring $50/month
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "<uuid>",
    "name": "Monthly base",
    "period": "monthly",
    "amount_cents": 5000,
    "currency": "USD",
    "criteria_type": "recurring",
    "criteria_params": {},
    "proration_mode": "skip"
  }' \
  https://www.trackagoat.com/api/v2/payout-structures
 
# Instagram-only recurring $50/month (platform scope)
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "<uuid>",
    "name": "Instagram base",
    "period": "monthly",
    "amount_cents": 5000,
    "currency": "USD",
    "criteria_type": "recurring",
    "criteria_params": {},
    "proration_mode": "skip",
    "platform_scope": "platform",
    "platform": "instagram"
  }' \
  https://www.trackagoat.com/api/v2/payout-structures
 
# Goals-based monthly with partial completion
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "<uuid>",
    "name": "Monthly goals bonus",
    "period": "monthly",
    "amount_cents": 50000,
    "currency": "USD",
    "criteria_type": "goals",
    "criteria_params": {
      "mode": "all",
      "partial_allowed": true,
      "partial_multiplier": 1.0,
      "min_completion_pct": 50
    },
    "proration_mode": "skip"
  }' \
  https://www.trackagoat.com/api/v2/payout-structures
 
# CPM-based weekly
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "<uuid>",
    "name": "Weekly CPM",
    "period": "weekly",
    "amount_cents": 0,
    "currency": "USD",
    "criteria_type": "cpm",
    "criteria_params": {
      "source": "views_gained",
      "view_lifetime_days": null,
      "min_impressions": 10000,
      "max_payout_cents": 50000,
      "tiers": [
        {"up_to_impressions": 1000000, "cents_per_thousand": 50},
        {"up_to_impressions": null, "cents_per_thousand": 75}
      ]
    },
    "proration_mode": "skip"
  }' \
  https://www.trackagoat.com/api/v2/payout-structures
 
# View threshold: $20 at 1M views, $100 at 5M views
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "<uuid>",
    "name": "Viral video milestones",
    "period": null,
    "amount_cents": null,
    "currency": "USD",
    "criteria_type": "view_threshold",
    "criteria_params": {
      "thresholds": [
        {"view_count": 1000000, "amount_cents": 2000, "currency": "USD"},
        {"view_count": 5000000, "amount_cents": 10000, "currency": "USD"}
      ]
    },
    "proration_mode": "skip",
    "backfill_on_assignment": false
  }' \
  https://www.trackagoat.com/api/v2/payout-structures

Body

FieldTypeRequiredDescription
project_iduuid✓Target project
namestring✓Structure name
perioddaily | weekly | monthly | null✓Payout frequency. Pass null for view_threshold structures.
amount_centsnumber | null✓Amount in cents. Pass null for view_threshold and conversion structures (their rates live in criteria_params). Ignored for cpm: the tiers carry the money, so pass 0.
currencyUSD✓Currently only USD
criteria_typerecurring | goals | cpm | view_threshold | conversion✓Earning rule type
criteria_paramsobject✓Rule-specific params (see below)
proration_modeskip | prorate | full✓Mid-period assignment behavior for recurring and goals. Ignored for cpm (earnings already reflect only the views posted in the period); view_threshold and conversion always use skip.
backfill_on_assignmentbooleanview_threshold and conversion only. If true, also counts what the creator had already earned before assignment (milestones already crossed, or conversions already recorded).
period_anchor1-7 | nullconversion with a weekly cadence only. ISO day of week (1=Mon) the week starts on, so "every Tuesday" is expressible. null keeps the default Monday alignment.
platform_scopeall | platform | accountDefault all. Which of an assigned creator's platform accounts the rule earns against (see below).
platformtiktok | instagram | youtube | nullRequired when platform_scope is platform; omit/null otherwise.
account_iduuid | nullRequired when platform_scope is account; omit/null otherwise.
metadataobjectAgent-writable data
readmestring | nullMarkdown notes

criteria_params shapes

TypeScript
// conversion: pay for recorded outcomes
criteria_params: {
  cadence: "every_conversion" | "daily" | "weekly" | "monthly",
  rates: [
    { conversion_event_id: uuid, rate_type: "flat",    amount_cents: number },
    { conversion_event_id: uuid, rate_type: "percent", percent_bps: number },  // 1000 = 10%
  ],
}
// `period` must equal `cadence`, or be null when cadence is "every_conversion".
// `amount_cents` is always null: the rates carry the money.
// Max 25 rates; each conversion event may appear at most once.
 
// recurring: no params
criteria_params: {}
 
// goals: pay if creator met their goals
criteria_params: {
  mode: "all" | "any" | "n_of_m",
  n?: number,               // required when mode = "n_of_m"
 
  // Partial completion (only valid when mode = "all")
  partial_allowed?: boolean,      // default false: enable fractional payouts on missed goals
  partial_multiplier?: number,    // default 1.0: scales the partial amount (0 to 1)
  min_completion_pct?: number,    // default 0: 0 to 100; below this avg completion%, no payout fires
}
 
// cpm: pay per 1,000 views. Each period pays for the views each video gained
// since it was last paid, so videos keep earning after their first period.
// The tiers carry the money: top-level `amount_cents` (pass 0) and
// `proration_mode` are both ignored for this type.
criteria_params: {
  source?: "views_gained",          // default; the retired "posts_in_period" is accepted and stored as "views_gained"
  view_lifetime_days?: number | null, // days after posting a video keeps earning (1 to 3650); null/omitted = forever
  min_impressions: number,          // views gained in a period before it pays; smaller periods carry over
  max_payout_cents: number | null,  // cap (null = unlimited)
  tiers: [                          // sorted ascending; last tier must have null up_to_impressions
    { up_to_impressions: number | null, cents_per_thousand: number }
  ]
}
 
// view_threshold: one-time bonus when a video crosses a view count
// period, amount_cents, and proration_mode must be null/skip for this type
criteria_params: {
  thresholds: [                  // 1 to 10 milestones; view_count values must be unique
    {
      view_count: number,        // minimum lifetime views on the video to trigger
      amount_cents: number,      // bonus amount in cents
      currency: "USD"
    }
  ]
}

Partial completion formula (goals, mode = "all"):

When partial_allowed is true and the creator does not fully meet all goals:

  1. Each goal's completion fraction = min(actual / target, 1.0).
  2. Average those fractions → completion_pct.
  3. If completion_pct < min_completion_pct / 100 → no accrual.
  4. Otherwise: payout = amount × completion_pct × partial_multiplier.

When all goals are fully met, the full amount is paid regardless of partial_allowed.

Returns 201 with { structure_id, version_id }.


GET/api/v2/payout-structures/#

Fetch structure detail including current_version, full versions[] history, and assignment_count.


PATCH/api/v2/payout-structures/#

Update a structure. Use kind to distinguish cosmetic edits from rule changes.

bash
# Cosmetic: update name and metadata without creating a new version
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"kind": "cosmetic", "name": "Monthly base v2", "metadata": {"owner": "growth-team"}}' \
  https://www.trackagoat.com/api/v2/payout-structures/<uuid>
 
# Rule edit: creates a new immutable version; existing accruals keep their version
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "rule",
    "period": "monthly",
    "amount_cents": 7500,
    "currency": "USD",
    "criteria_type": "recurring",
    "criteria_params": {},
    "proration_mode": "skip"
  }' \
  https://www.trackagoat.com/api/v2/payout-structures/<uuid>

A rule PATCH accepts the same platform_scope / platform / account_id fields as create (same validation rules), letting a new version re-scope the payout to a platform or account. Omitting them keeps the current version's scope.

A rule PATCH replaces the whole rule, so send every field. To change only how long videos earn on a CPM structure, read the structure first and send its current criteria_params with view_lifetime_days changed (the MCP tool set_cpm_view_lifetime does this for you):

bash
# Stop paying for a video's views 30 days after it's posted (null = forever)
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "rule",
    "period": "weekly",
    "amount_cents": 0,
    "currency": "USD",
    "criteria_type": "cpm",
    "criteria_params": {
      "source": "views_gained",
      "view_lifetime_days": 30,
      "min_impressions": 10000,
      "max_payout_cents": 50000,
      "tiers": [
        {"up_to_impressions": 1000000, "cents_per_thousand": 50},
        {"up_to_impressions": null, "cents_per_thousand": 75}
      ]
    },
    "proration_mode": "skip"
  }' \
  https://www.trackagoat.com/api/v2/payout-structures/<uuid>

The new version applies from the period in progress; periods that already ended keep the version they were calculated with.

Rule PATCH returns { version_id, version_number }. Cosmetic PATCH returns the updated structure row.

A rule PATCH cannot change period (or a conversion cadence) or criteria_type once the structure has any accruals or has ever had a creator assigned: it returns 409 structure_shape_locked. Periods are generated from the structure's current shape, so changing it on a live structure would create overlapping accruals for periods that were already paid under the old shape. Create a new structure instead, assign creators to it, and archive the old one. Amounts, criteria parameters and proration can still be edited, and apply to future periods only.


DELETE/api/v2/payout-structures/#

Archive (default) or hard-delete a structure.

bash
# Archive: stops new accruals, preserves historical ones
curl -X DELETE -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/payout-structures/<uuid>"
 
# Hard delete: blocked with 409 has_accruals if accruals exist
curl -X DELETE -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/payout-structures/<uuid>?mode=hard"

Assignments#

Assign or unassign creators to a payout structure.

GET/api/v2/payout-structures//assignments#

List currently assigned creators.

bash
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/payout-structures/<uuid>/assignments"
ParameterTypeDescription
include_inactive1 | trueInclude historically unassigned creators
limitnumberDefault 50, max 100
cursorstringPagination cursor

POST/api/v2/payout-structures//assignments#

Bulk-assign creators. Returns counts of newly assigned, already-assigned, and not-in-project IDs.

bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"creator_ids": ["<uuid1>", "<uuid2>"]}' \
  https://www.trackagoat.com/api/v2/payout-structures/<uuid>/assignments
FieldTypeRequiredDescription
creator_idsuuid[]✓Creators to assign
effective_atISO 8601 datetimeWhen earning begins. Defaults to now. Pass a future period boundary to start on a later period. Cannot be earlier than the start of the period currently in progress (400); earning cannot be backdated into periods that have already closed.
first_period_modeprorate | fullHow the period containing effective_at is treated. Omitted inherits the structure's proration_mode. Ignored when effective_at is already a period boundary, since the period is whole either way.

first_period_mode means different things by criteria type. For recurring and goals it selects how the amount is prorated. For cpm there is no amount to prorate, so it moves the start of the window instead: prorate counts only videos posted, and views gained, at or after effective_at; full counts every video posted since the period started. That start holds for every later period.

bash
# Start on next Monday, taking that period whole
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{
    "creator_ids": ["<uuid1>"],
    "effective_at": "2026-08-24T05:00:00Z",
    "first_period_mode": "full"
  }' \
  https://www.trackagoat.com/api/v2/payout-structures/<uuid>/assignments

Returns { assigned, already_assigned, not_in_project, results }.


DELETE/api/v2/payout-structures//assignments#

Bulk-unassign creators. Existing earned accruals remain payable.

bash
curl -X DELETE \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"creator_ids": ["<uuid1>"]}' \
  https://www.trackagoat.com/api/v2/payout-structures/<uuid>/assignments

Payout Accruals#

Accruals are computed by an hourly background job: they cannot be created via the API.

GET/api/v2/payout-accruals#

List accruals with optional filters.

bash
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/payout-accruals?project_id=<uuid>&status=earned"

Query parameters

ParameterTypeDescription
project_iduuidFilter to a project
creator_iduuidFilter to a creator
structure_iduuidFilter to a structure
statusearned | paid | rejected | voidedFilter by status
fromYYYY-MM-DDPeriod start on or after this date
toYYYY-MM-DDPeriod end on or before this date
limitnumberDefault 50, max 100
cursorstringPagination cursor (period_end_at ISO)

GET/api/v2/payout-accruals/#

Fetch a single accrual including inputs_snapshot.

For a CPM accrual, inputs_snapshot.video_gains lists each video that earned in the period as { video_id, views_start, views_end, gained }: the view count it was already paid up to, the count this accrual paid up to, and the difference. The next period pays from views_end. Rejecting or voiding a CPM accrual does not hand its views to a later period. Accruals calculated before 2026-10-09 have source: "posts_in_period" and no video_gains.


PATCH/api/v2/payout-accruals/#

Manually reject or void an earned accrual.

bash
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"status": "rejected"}' \
  https://www.trackagoat.com/api/v2/payout-accruals/<uuid>

Body: { status: "rejected" | "voided" }

Returns 409 cannot_set_status_on_paid_accrual if the accrual is already paid: void the parent payment first.


Payout Payments#

GET/api/v2/payout-payments#

List payments with optional filters.

bash
curl -H "Authorization: Bearer tga_<key>" \
  "https://www.trackagoat.com/api/v2/payout-payments?project_id=<uuid>&status=paid"

Query parameters

ParameterTypeDescription
project_iduuidFilter to a project
creator_iduuidFilter to a creator
statuspaid | rejected | voidedFilter by status
fromYYYY-MM-DDPaid at or after this date
toYYYY-MM-DDPaid at or before this date
limitnumberDefault 50, max 100
cursorstringPagination cursor (paid_at ISO)

Response rows include payout_payment_lines[] (linked accruals) and payout_adjustments[] (over/underpayment lines).


POST/api/v2/payout-payments#

Record a payment. Requires an Idempotency-Key header.

bash
# Settle all outstanding accruals for a creator
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "project_id": "<uuid>",
    "creator_id": "<uuid>",
    "method_id": "<uuid>",
    "total_amount_cents": 15000,
    "currency": "USD",
    "auto_apply_outstanding": true,
    "note": "April batch payment"
  }' \
  https://www.trackagoat.com/api/v2/payout-payments
 
# Ad-hoc payment with no linked accruals
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "project_id": "<uuid>",
    "creator_id": "<uuid>",
    "total_amount_cents": 5000,
    "note": "Bonus for viral video"
  }' \
  https://www.trackagoat.com/api/v2/payout-payments

Body

FieldTypeRequiredDescription
project_iduuid✓Target project
creator_iduuid | nullCreator receiving payment (null for true ad-hoc). Required when accrual_ids or auto_apply_outstanding is set
method_iduuid | nullPayout method used
total_amount_centsnumber✓Total paid in cents (≥ 0)
currencystringISO 4217, default USD
tracking_urlstring | nullExternal receipt link
notestring | nullFree-form note
paid_atISO 8601When the payment was sent. Defaults to now; may be backdated to record a historical payment, but cannot be in the future
accrual_idsuuid[]Explicit accruals to settle (takes precedence over auto_apply_outstanding). Every accrual must belong to creator_id, otherwise 409 accrual_creator_mismatch
supplemental_idsuuid[]Owed supplemental amounts to settle with this payment. They count toward the amount the payment applies, like accruals. Each must belong to creator_id (409 supplemental_creator_mismatch) and be owed in this project and currency (409 supplemental_not_settleable)
auto_apply_outstandingbooleanSettles all of the creator's earned accruals and owed supplemental amounts in this project and currency (when accrual_ids and supplemental_ids are empty). Requires creator_id. Returns 409 nothing_outstanding and records nothing when there are none
adjustment_reasonstring | nullRequired when total differs from accrual sum
metadataobjectAgent-writable data
readmestring | nullMarkdown notes
payment_iduuidYour own id for the payment. Required with attachment_path, because the receipt is uploaded under it before the payment exists. Returns 409 payment_id_in_use if another payment has it
attachment_pathstringA receipt's path from Payment receipts. Must be in this payment_id's folder, otherwise 400 invalid_attachment_path

Returns 201 with the payment row, or 200 with the original payment when the Idempotency-Key was already used.


GET/api/v2/payout-payments/#

Fetch a payment including lines, adjustments and the supplemental amounts it settled (payout_supplemental_amounts).


Send a payout through a connected provider#

POST /api/v2/payout-payments/send pays a creator through the organization's connected provider (Dots) and records the payment. POST /api/v2/payout-payments only records a payment made somewhere else; this endpoint moves money.

It works only when all of these hold, otherwise nothing is sent:

  • the key has the write scope and acts as an org admin (403 insufficient_role);
  • an org admin turned on Agent payments in Org settings → Payment providers (403 agent_payments_disabled);
  • the amount fits the org's agent limits (403 agent_cap_exceeded, with meta.cap = per_payment or daily and meta.limit_cents);
  • the creator is linked to a payee (409 payee_not_linked; check with GET /creators/{id}/payee-links);
  • an Idempotency-Key header is sent. Reusing it returns the original payment and never pays twice.
bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "project_id": "<uuid>",
    "creator_id": "<uuid>",
    "auto_apply_outstanding": true,
    "provider_options": { "memo": "September CPM payout" }
  }' \
  https://www.trackagoat.com/api/v2/payout-payments/send

Body

FieldTypeRequiredDescription
project_iduuid✓Project the accruals belong to
creator_iduuid✓Creator to pay
providerstringdots (default)
accrual_idsuuid[]Earned accruals to pay
supplemental_idsuuid[]Owed supplemental amounts to pay with them
auto_apply_outstandingbooleanPay everything the creator is owed in this project and currency: earned accruals and owed supplemental amounts. 409 nothing_outstanding if there is nothing
total_amount_centsintegerAmount to send (≥ 1). Defaults to the total of what is being paid
adjustment_reasonstringRequired when total_amount_cents differs from the accruals' total
currencystringUSD (Dots pays USD only)
notestringInternal note on the trackagoat payment
provider_optionsobjectProvider options, below

Dots provider_options

FieldTypeDescription
memostring (≤ 500)Note shown to the creator with the payout
messagestring (≤ 300)Text of the SMS Dots sends with a claim link (creators linked by phone)
descriptionstring (≤ 500)Description saved on the payout in Dots
tax_exemptbooleanLeave this payout out of the creator's 1099

Unknown fields are rejected with 400 invalid_provider_options.

Response. 201 with the payment row. Provider fields:

FieldDescription
providerdots
provider_statuscompleted, awaiting_claim (creator was texted a claim link), processing, unconfirmed (Dots didn't answer in time; trackagoat keeps checking: don't send again with a new key), failed or canceled
provider_payment_idDots payout link id
provider_error_code, provider_error_messageWhy a payout failed, or a note such as "Waiting for funds in your Dots balance"
provider_optionsThe options sent
initiated_viaui, api or mcp

When Dots refuses the payout, the response is 422 provider_send_failed with the payment in data: it is already voided, so its accruals are owed again, and provider_error_code says why (for example transaction-insufficient-funds). Retrying needs a new Idempotency-Key.

failed and canceled payments are always voided. Read the status later with GET /api/v2/payout-payments/{id}. A payment that is awaiting_claim or still in flight can't be voided (409 provider_cancel_required / 409 provider_in_flight); cancel an unclaimed payout from the creator's Payouts tab.


Read scope and an org admin (403 insufficient_role otherwise), like the creator's payout account in the app. Whether a creator can be paid through each connected provider, and who they are paid as (phone masked). meta carries the org's agent-payment switch and limits.

JSON
{
  "data": [
    {
      "provider": "dots",
      "provider_name": "Dots",
      "environment": "production",
      "connection_status": "active",
      "linked": true,
      "payee": { "provider_payee_id": "a1b2…", "name": "Sarah Brown", "phone": "+1 ••• 4567", "source": "claimed", "linked_at": "2026-10-09T18:22:04Z" }
    }
  ],
  "meta": { "agent_payments_enabled": true, "agent_payment_max_cents": 50000, "agent_payment_daily_max_cents": 200000 }
}

PATCH/api/v2/payout-payments/#

Void a payment or update metadata/readme.

bash
# Void: reverts linked accruals to "earned" and settled supplemental amounts to "owed"
curl -X PATCH \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d '{"status": "voided"}' \
  https://www.trackagoat.com/api/v2/payout-payments/<uuid>

Body: { status?: "voided", metadata?: object, readme?: string | null } (at least one field required). A receipt can only be attached when the payment is recorded, as in the app.


Payouts tracker#

GET/api/v2/payouts/tracker#

Per-creator balances for a project, as on the app's Payouts page. Query: project_id (required), creator_ids (comma-separated), structure_id, campaign_id, from, to (YYYY-MM-DD, org time zone), outstanding_only (default false), include_removed (default false), limit (1 to 200, default 50), cursor (meta.nextCursor). Largest outstanding first.

Each row has total_earned_cents, total_paid_cents, adjustment_cents (signed: the part of paid that settled no accrual), adjustment_breakdown ([{ reason, amount_cents, count }], largest first), outstanding_cents, last_paid_at, is_removed and counts. meta.totals has earned_cents, paid_cents, adjustment_cents, adjustment_breakdown, outstanding_cents, creator_count_outstanding and the removed-creator summary (removed_creator_count_outstanding, removed_outstanding_cents). Without from/to, every row and the totals satisfy earned − paid + adjustments = outstanding. MCP: get_payouts_tracker.


Supplemental amounts#

A supplemental amount is an owed correction on top of a paid accrual, for example posts found after the period was paid. The accrual and its payment are never changed. The amount is owed, counts in the creator's outstanding balance, and is settled by recording a payment (supplemental_ids or auto_apply_outstanding). MCP: list_payout_supplementals, create_payout_supplemental, void_payout_supplemental.

GET/api/v2/payout-supplementals#

Query: project_id, creator_id, structure_id, accrual_id, status (owed | paid | void), cursor, limit (1 to 100, default 50). Newest first.

POST/api/v2/payout-supplementals#

Requires the write scope. Send an Idempotency-Key header: a retry with the same key returns the original row.

bash
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1d0c1e-…" \
  -d '{"project_id": "<uuid>", "accrual_id": "<paid accrual uuid>", "amount_cents": 1333, "reason": "Posts found after the week was paid"}' \
  https://www.trackagoat.com/api/v2/payout-supplementals
FieldTypeRequiredDescription
project_iduuid✓Project
accrual_iduuidThe paid accrual this corrects; its creator, structure and period are used. An unpaid accrual returns 409 accrual_not_paid
creator_id, structure_id, period_start_at, period_end_atInstead of accrual_id: all four are required
amount_centsinteger✓Amount owed, ≥ 1
currencystringDefault USD; must match the accrual
reasonstring✓1 to 500 characters; shown as "Supplemental: <reason>"

Returns 201 with the row (status: "owed"). A removed creator returns 404 not_found: restore the creator first.

GET/api/v2/payout-supplementals/#

Fetch one supplemental amount.

PATCH/api/v2/payout-supplementals/#

Void an owed amount: { "status": "void", "reason"?: string }. A paid one returns 409 supplemental_paid: void the payment that settled it instead (it becomes owed again).


Payment receipts#

A receipt is a file attached to a payment: PNG, JPEG, WebP, PDF, CSV, Word or Excel, up to 5 MB, stored privately. Generate a payment_id UUID first, upload the receipt under it, then record the payment with the same payment_id and attachment_path.

Inline, up to 3 MB. POST /api/v2/payouts/payment-receipts:

bash
PAYMENT_ID=$(uuidgen | tr 'A-Z' 'a-z')
curl -X POST \
  -H "Authorization: Bearer tga_<key>" \
  -H "Content-Type: application/json" \
  -d "{\"payment_id\": \"$PAYMENT_ID\", \"project_id\": \"<project>\", \"filename\": \"receipt.pdf\", \"content_type\": \"application/pdf\", \"content_base64\": \"$(base64 < receipt.pdf | tr -d '\n')\"}" \
  https://www.trackagoat.com/api/v2/payouts/payment-receipts

Returns 201 with { path, content_type, size_bytes }. The file's contents must match content_type (content_type_mismatch); larger files get 413 file_too_large.

Signed URL, up to 5 MB. POST /api/v2/payouts/payment-receipts/upload-url with { payment_id, project_id, filename, content_type } returns signed_url, path, max_bytes and expires_in_seconds (2 hours). PUT the raw file to signed_url with the same Content-Type:

bash
curl -X PUT -H "Content-Type: application/pdf" --data-binary @receipt.pdf "<signed_url>"

Then record the payment with "payment_id": "$PAYMENT_ID" and "attachment_path": "<path>".


Error codes#

CodeHTTPDescription
invalid_api_key401Missing, expired, or invalid Bearer token
insufficient_scope403Write attempted with a key that lacks the write scope
rate_limit_exceeded429Daily API quota or per-minute (60 req/min) rate limit hit
limit_reached402Org plan limit for methods or structures
invalid_body400Zod validation failed: see meta.errors
invalid_query400Bad query parameter
default_method409Cannot delete a seeded default method
conflict_name409Method name already exists in org
structure_archived409Cannot assign creators to an archived structure
has_accruals409Cannot hard-delete a structure with existing accruals
cannot_set_paid_directly400Use POST /payout-payments instead
cannot_set_status_on_paid_accrual409Void the parent payment first
currency_mismatch400Payment currency doesn't match accrual currency
accrual_not_settleable409Accrual is wrong project, status, or currency
accrual_creator_mismatch409An accrual belongs to a different creator than the payment's creator_id
nothing_outstanding409auto_apply_outstanding found no earned accruals; nothing was recorded
idempotency_key_required400POST /payout-payments was sent without an Idempotency-Key header
idempotency_conflict409Idempotency-Key already used for a payment in another project
structure_shape_locked409Rule edit changes period or criteria_type on a structure that has accruals or assignments: create a new structure instead
adjustment_reason_required400Total differs from accrual sum: provide adjustment_reason
already_voided400Payment is already voided
accrual_in_other_payment409Accrual has been re-paid: void that payment first
payment_id_in_use409payment_id already belongs to another payment
invalid_attachment_path400attachment_path isn't a receipt uploaded for this payment_id
invalid_mime_type400Upload type not allowed (see meta.allowed)
content_type_mismatch400Uploaded file's contents don't match content_type
invalid_base64400content_base64 isn't valid base64
file_too_large413Inline upload over 3 MB (receipts) or 1 MB (icons)
agent_payments_disabled403An org admin hasn't turned on agent payments
agent_cap_exceeded403The payout is over the org's per-payout or 24-hour agent limit
insufficient_role403The key acts as a member who isn't an org admin: payout setup, recording or sending payments, method icons and payout accounts need one
provider_not_connected409No payment provider is connected
provider_connection_error409The provider rejected the stored keys; an admin must update them
payee_not_linked409The creator isn't linked to a payee
invalid_provider_options400provider_options has an unknown or too-long field
provider_send_failed422The provider refused the payout; the payment was voided
provider_cancel_required409The payout is awaiting claim: cancel it instead of voiding
provider_in_flight409The provider hasn't settled the payout yet
not_found404Resource not found or not in org