API reference

Every endpoint of the ViralPayouts REST API: 45 for brand keys and 21 for partner keys. Each is also an MCP tool, named on its card. This page and the OpenAPI document are generated from the same source. How keys, errors, pagination and idempotency work is in the overview.

Account

Check a key: which brand it acts for and its role.

EndpointWhat it does
GET/api/v1/meThe key and brand making the call

GET/api/v1/me

The key and brand making the call

Which brand this key acts for, its role, and the network. A cheap first call to check a key works.

Brand key, viewer role or higherMCP tool: whoamigetBrandKey
Request
curl https://viralpayouts.com/api/v1/me \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "brand": {
      "id": "brd_4e1d",
      "name": "Acme",
      "status": "active"
    },
    "key": {
      "id": "key_2a9f",
      "name": "Zapier",
      "role": "manager"
    },
    "network": "mainnet"
  }
}

Programs

Create, read and change partner programs; their results and how sales reach them.

GET/api/v1/programs

List programs

This brand's partner programs, oldest first, with their terms and the pages partners apply on.

Brand key, viewer role or higherMCP tool: list_programsPaginatedlistPrograms
FieldInTypeRequiredDescription
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/programs \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "id": "prg_9f2c1a",
      "name": "Acme Pro",
      "slug": "acme-pro",
      "status": "live",
      "currency": "USD",
      "openToAll": false,
      "commission": "25% of each sale",
      "recruiterOverride": "10% of the commission of partners they recruit",
      "holdDays": 30,
      "attributionWindowDays": 30,
      "renewals": "Every renewal, for the life of the subscription",
      "minimumPayout": 10,
      "newCustomersOnly": true,
      "countries": [],
      "termsVersion": 1,
      "scheduledChange": null,
      "applicationPage": "https://viralpayouts.com/apply/prg_9f2c1a"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

POST/api/v1/programs

Create a program

Creates a program as a draft. Needs a name and a destinationUrl; everything else takes the program terms defaults. Commission and the other terms must stay inside the program terms limits.

Brand key, owner role or higherMCP tool: create_programcreateProgram
FieldInTypeRequiredDescription
namebodystringYesThe program name partners see. up to 120 chars
destinationUrlbodystringYesWhere partner links send visitors: the brand's site, https on mainnet.
descriptionbodystringNoup to 4000 chars
currencybodystringNoThree-letter currency code. Default USD.
commissionPercentbodynumberNoCommission as a percent of each sale, 1 to 95. Use this or commissionFixed. min 1, max 95
commissionFixedbodynumberNoA flat fee per approved customer, in the program currency (e.g. 30 for $30). Use this or commissionPercent. min 1
recruiterOverridePercentbodynumberNoWhat a partner's recruiter earns, as a percent of that partner's commission: 0 for none (the default), or 1 to 95. min 0, max 95
holdDaysbodyintegerNoPayment schedule: partners are paid this many days after the sale (the refund window), 15 to 30 on mainnet. Default 15. min 0, max 30
attributionWindowDaysbodyintegerNoCookie window: how long after a click a sale still counts, 7 to 365 days. Default 30. min 7, max 365
renewalMonthsbodyinteger or string: lifetimeNoRenewals that earn commission: 1 for the first payment only (a one-off fee), 2 to 36 months, or "lifetime" (the default).
minimumPayoutbodynumberNoThe balance a partner must reach before they are paid, in the program currency: 10 to 100 on mainnet. Default 10. min 0, max 100
newCustomersOnlybodybooleanNoTrue (the default): only customers new to the brand earn a commission.
countriesbodyarray of stringNoTwo-letter country codes the program is open to. Empty for every country (the default).
trafficRulesbodyobjectNoHow partners may promote: allowed, ask (ask first) or banned, per source. Only the sources sent change.
brandRulesbodystringNoUp to 300 words of the brand's own rules for partners. ViralPayouts reviews them before partners see them. up to 2400 chars
attributionbodystring: last, firstNoWho is credited when a buyer followed several partners' links. Default last.
openToAllbodybooleanNoTrue: anyone who applies is approved at once. False (default): the brand reviews each application.
statusbodystring: draft, live, pausedNodraft (default) until you are ready; live lets partners join and earn.
Idempotency-KeyheaderstringNoA unique value per request; a retry with the same value is answered once.
Request
curl -X POST https://viralpayouts.com/api/v1/programs \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme Pro","destinationUrl":"https://yourbrand.com/pricing"}'
Answer (201)
{
  "data": {
    "id": "prg_9f2c1a",
    "name": "Acme Pro",
    "slug": "acme-pro",
    "status": "live",
    "currency": "USD",
    "openToAll": false,
    "commission": "25% of each sale",
    "recruiterOverride": "10% of the commission of partners they recruit",
    "holdDays": 30,
    "attributionWindowDays": 30,
    "renewals": "Every renewal, for the life of the subscription",
    "minimumPayout": 10,
    "newCustomersOnly": true,
    "countries": [],
    "termsVersion": 1,
    "scheduledChange": null,
    "applicationPage": "https://viralpayouts.com/apply/prg_9f2c1a"
  }
}

GET/api/v1/programs/{programId}

Get a program

One program with its terms, its machine-readable rates (basis points and cents) and its product rates.

Brand key, viewer role or higherMCP tool: get_programgetProgram
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "id": "prg_9f2c1a",
    "name": "Acme Pro",
    "slug": "acme-pro",
    "status": "live",
    "currency": "USD",
    "openToAll": false,
    "commission": "25% of each sale",
    "recruiterOverride": "10% of the commission of partners they recruit",
    "holdDays": 30,
    "attributionWindowDays": 30,
    "renewals": "Every renewal, for the life of the subscription",
    "minimumPayout": 10,
    "newCustomersOnly": true,
    "countries": [],
    "termsVersion": 1,
    "scheduledChange": null,
    "applicationPage": "https://viralpayouts.com/apply/prg_9f2c1a",
    "rewardType": "percent",
    "directRateBps": 2500,
    "directFixedCents": 0,
    "overrideBase": "commission",
    "overrideRateBps": 1000,
    "rateRules": {
      "products": [],
      "events": []
    }
  }
}

PATCH/api/v1/programs/{programId}

Update a program

Changes only the fields sent. On a live program with approved partners, a cut takes effect 3 business days later (see scheduledChange) and an increase applies at once; partners are emailed either way. A program with no partners changes at once.

Brand key, owner role or higherMCP tool: update_programupdateProgram
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
namebodystringNoThe program name partners see. up to 120 chars
destinationUrlbodystringNoWhere partner links send visitors: the brand's site, https on mainnet.
descriptionbodystringNoup to 4000 chars
currencybodystringNoThree-letter currency code. Default USD.
commissionPercentbodynumberNoCommission as a percent of each sale, 1 to 95. Use this or commissionFixed. min 1, max 95
commissionFixedbodynumberNoA flat fee per approved customer, in the program currency (e.g. 30 for $30). Use this or commissionPercent. min 1
recruiterOverridePercentbodynumberNoWhat a partner's recruiter earns, as a percent of that partner's commission: 0 for none (the default), or 1 to 95. min 0, max 95
holdDaysbodyintegerNoPayment schedule: partners are paid this many days after the sale (the refund window), 15 to 30 on mainnet. Default 15. min 0, max 30
attributionWindowDaysbodyintegerNoCookie window: how long after a click a sale still counts, 7 to 365 days. Default 30. min 7, max 365
renewalMonthsbodyinteger or string: lifetimeNoRenewals that earn commission: 1 for the first payment only (a one-off fee), 2 to 36 months, or "lifetime" (the default).
minimumPayoutbodynumberNoThe balance a partner must reach before they are paid, in the program currency: 10 to 100 on mainnet. Default 10. min 0, max 100
newCustomersOnlybodybooleanNoTrue (the default): only customers new to the brand earn a commission.
countriesbodyarray of stringNoTwo-letter country codes the program is open to. Empty for every country (the default).
trafficRulesbodyobjectNoHow partners may promote: allowed, ask (ask first) or banned, per source. Only the sources sent change.
brandRulesbodystringNoUp to 300 words of the brand's own rules for partners. ViralPayouts reviews them before partners see them. up to 2400 chars
attributionbodystring: last, firstNoWho is credited when a buyer followed several partners' links. Default last.
openToAllbodybooleanNoTrue: anyone who applies is approved at once. False (default): the brand reviews each application.
statusbodystring: draft, live, pausedNodraft (default) until you are ready; live lets partners join and earn.
Request
curl -X PATCH https://viralpayouts.com/api/v1/programs/prg_9f2c1a \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme Pro"}'
Answer (200)
{
  "data": {
    "id": "prg_9f2c1a",
    "name": "Acme Pro",
    "slug": "acme-pro",
    "status": "live",
    "currency": "USD",
    "openToAll": false,
    "commission": "25% of each sale",
    "recruiterOverride": "10% of the commission of partners they recruit",
    "holdDays": 30,
    "attributionWindowDays": 30,
    "renewals": "Every renewal, for the life of the subscription",
    "minimumPayout": 10,
    "newCustomersOnly": true,
    "countries": [],
    "termsVersion": 1,
    "scheduledChange": null,
    "applicationPage": "https://viralpayouts.com/apply/prg_9f2c1a"
  }
}

GET/api/v1/programs/{programId}/stats

Program results

Clicks, sales, revenue, commission, fee, ROAS and EPC over a period, all-time totals, tracking health and the 7-day funding forecast. Money in cents.

Brand key, viewer role or higherMCP tool: get_program_statsgetProgramStats
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
daysqueryinteger: 7, 30, 90, 365, 0NoPeriod in days: 7, 30, 90 or 365, or 0 for all time. Default 30.
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/stats \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "programId": "prg_9f2c1a",
    "currency": "USD",
    "days": 30,
    "period": {
      "clicks": 1200,
      "sales": 31,
      "revenueCents": 310000,
      "directCents": 77500,
      "overrideCents": 3100,
      "commissionCents": 80600,
      "feeCents": 8060,
      "costCents": 88660,
      "roas": 3.5
    }
  }
}

GET/api/v1/programs/{programId}/integration

How sales reach ViralPayouts

The signed event API (with example bodies), the Stripe webhook URL and the landing parameters. Only an owner key gets the signing secret.

Brand key, viewer role or higherMCP tool: get_integrationgetIntegration
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/integration \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "programId": "prg_9f2c1a",
    "eventApi": {
      "url": "https://viralpayouts.com/api/v1/events"
    },
    "signingSecret": "vpsk_...",
    "stripeWebhookUrl": "https://viralpayouts.com/api/stripe/webhook/prg_9f2c1a"
  }
}

Partners

Review and manage the partners of a program.

GET/api/v1/programs/{programId}/partners

List partners

The partners of one program, newest first, with status, contact, clicks, customers, recruits, payout readiness and balance (cents). For results over a period use the stats endpoint.

Brand key, viewer role or higherMCP tool: browse_partnersPaginatedlistPartners
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
statusquerystring: pending, approved, suspended, rejectedNoOnly partners with this status.
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/partners \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "id": "prt_51ab0c",
      "accountId": "acc_7d10e2",
      "handle": "sam",
      "name": "Sam Lee",
      "email": "sam@example.com",
      "status": "approved",
      "clicks": 412,
      "approvedCustomers": 9,
      "recruits": 2,
      "payoutConnected": true,
      "balance": {
        "pending": 2500,
        "inHold": 7500,
        "payable": 12000,
        "paid": 30000,
        "inFlight": 0,
        "earnedYtd": 42000
      }
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET/api/v1/partners/{partnerId}

Get a partner

One partner: status and reason, application, terms accepted, payment schedule, any warning, payout readiness and balance (cents).

Brand key, viewer role or higherMCP tool: get_partnergetPartner
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
Request
curl https://viralpayouts.com/api/v1/partners/prt_51ab0c \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "id": "prt_51ab0c",
    "accountId": "acc_7d10e2",
    "handle": "sam",
    "name": "Sam Lee",
    "email": "sam@example.com",
    "status": "approved",
    "clicks": 412,
    "approvedCustomers": 9,
    "recruits": 2,
    "payoutConnected": true,
    "balance": {
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "paid": 30000,
      "inFlight": 0,
      "earnedYtd": 42000
    }
  }
}

PATCH/api/v1/partners/{partnerId}

Set a partner's payment schedule

A payment schedule agreed with this one partner: they are paid this many days after each sale (0 to 30) instead of the program's. null goes back to the program's. Commission rates are set per program and per product, not per partner.

Brand key, owner role or higherMCP tool: set_partner_payment_scheduleupdatePartner
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
holdDaysbodyinteger or nullYesDays after the sale, 0 to 30, or null for the program's schedule. min 0, max 30
Request
curl -X PATCH https://viralpayouts.com/api/v1/partners/prt_51ab0c \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"holdDays":7}'
Answer (200)
{
  "data": {
    "id": "prt_51ab0c",
    "accountId": "acc_7d10e2",
    "handle": "sam",
    "name": "Sam Lee",
    "email": "sam@example.com",
    "status": "approved",
    "clicks": 412,
    "approvedCustomers": 9,
    "recruits": 2,
    "payoutConnected": true,
    "balance": {
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "paid": 30000,
      "inFlight": 0,
      "earnedYtd": 42000
    },
    "holdDays": 7
  }
}

POST/api/v1/partners/{partnerId}/approve

Approve a partner

Approves a pending (or earlier rejected) application. The partner is emailed and their links start earning.

Brand key, manager role or higherMCP tool: decide_partnerapprovePartner
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
Request
curl -X POST https://viralpayouts.com/api/v1/partners/prt_51ab0c/approve \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "id": "prt_51ab0c",
    "accountId": "acc_7d10e2",
    "handle": "sam",
    "name": "Sam Lee",
    "email": "sam@example.com",
    "status": "approved",
    "clicks": 412,
    "approvedCustomers": 9,
    "recruits": 2,
    "payoutConnected": true,
    "balance": {
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "paid": 30000,
      "inFlight": 0,
      "earnedYtd": 42000
    }
  }
}

POST/api/v1/partners/{partnerId}/reject

Reject an application

Rejects a pending application. Needs a reason, which the partner sees.

Brand key, manager role or higherMCP tool: decide_partnerrejectPartner
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
reasonbodystringYesWhy. The partner sees it. up to 500 chars
Request
curl -X POST https://viralpayouts.com/api/v1/partners/prt_51ab0c/reject \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"reason":"Brand bidding on our name"}'
Answer (200)
{
  "data": {
    "id": "prt_51ab0c",
    "accountId": "acc_7d10e2",
    "handle": "sam",
    "name": "Sam Lee",
    "email": "sam@example.com",
    "status": "rejected",
    "clicks": 412,
    "approvedCustomers": 9,
    "recruits": 2,
    "payoutConnected": true,
    "balance": {
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "paid": 30000,
      "inFlight": 0,
      "earnedYtd": 42000
    }
  }
}

POST/api/v1/partners/{partnerId}/suspend

Suspend a partner

Suspends an approved partner: their links stop earning. Needs a reason, which the partner sees.

Brand key, manager role or higherMCP tool: decide_partnersuspendPartner
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
reasonbodystringYesWhy. The partner sees it. up to 500 chars
Request
curl -X POST https://viralpayouts.com/api/v1/partners/prt_51ab0c/suspend \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"reason":"Brand bidding on our name"}'
Answer (200)
{
  "data": {
    "id": "prt_51ab0c",
    "accountId": "acc_7d10e2",
    "handle": "sam",
    "name": "Sam Lee",
    "email": "sam@example.com",
    "status": "suspended",
    "clicks": 412,
    "approvedCustomers": 9,
    "recruits": 2,
    "payoutConnected": true,
    "balance": {
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "paid": 30000,
      "inFlight": 0,
      "earnedYtd": 42000
    }
  }
}

POST/api/v1/partners/{partnerId}/reinstate

Reinstate a partner

Reinstates a suspended partner.

Brand key, manager role or higherMCP tool: decide_partnerreinstatePartner
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
Request
curl -X POST https://viralpayouts.com/api/v1/partners/prt_51ab0c/reinstate \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "id": "prt_51ab0c",
    "accountId": "acc_7d10e2",
    "handle": "sam",
    "name": "Sam Lee",
    "email": "sam@example.com",
    "status": "approved",
    "clicks": 412,
    "approvedCustomers": 9,
    "recruits": 2,
    "payoutConnected": true,
    "balance": {
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "paid": 30000,
      "inFlight": 0,
      "earnedYtd": 42000
    }
  }
}

POST/api/v1/partners/{partnerId}/warn

Warn a partner

Warns an approved partner about a minor breach of the program terms. They get 7 days to fix it and their links keep working.

Brand key, manager role or higherMCP tool: decide_partnerwarnPartner
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
reasonbodystringYesWhy. The partner sees it. up to 500 chars
Request
curl -X POST https://viralpayouts.com/api/v1/partners/prt_51ab0c/warn \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"reason":"Brand bidding on our name"}'
Answer (200)
{
  "data": {
    "id": "prt_51ab0c",
    "accountId": "acc_7d10e2",
    "handle": "sam",
    "name": "Sam Lee",
    "email": "sam@example.com",
    "status": "approved",
    "clicks": 412,
    "approvedCustomers": 9,
    "recruits": 2,
    "payoutConnected": true,
    "balance": {
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "paid": 30000,
      "inFlight": 0,
      "earnedYtd": 42000
    },
    "warning": {
      "reason": "Remove the coupon-site listing",
      "dueAt": "2026-10-25T10:00:00.000Z"
    }
  }
}

POST/api/v1/partners/{partnerId}/clear-warning

Clear a warning

Clears a partner's open warning.

Brand key, manager role or higherMCP tool: decide_partnerclearPartnerWarning
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
Request
curl -X POST https://viralpayouts.com/api/v1/partners/prt_51ab0c/clear-warning \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "id": "prt_51ab0c",
    "accountId": "acc_7d10e2",
    "handle": "sam",
    "name": "Sam Lee",
    "email": "sam@example.com",
    "status": "approved",
    "clicks": 412,
    "approvedCustomers": 9,
    "recruits": 2,
    "payoutConnected": true,
    "balance": {
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "paid": 30000,
      "inFlight": 0,
      "earnedYtd": 42000
    }
  }
}

GET/api/v1/partners/{partnerId}/stats

One partner's results

One partner's clicks, sales, revenue, commission, fee and ROAS over a period, with their best sub IDs. Money in cents.

Brand key, viewer role or higherMCP tool: get_partner_statsgetPartnerStats
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
daysqueryinteger: 7, 30, 90, 365, 0NoPeriod in days: 7, 30, 90 or 365, or 0 for all time. Default 30.
Request
curl https://viralpayouts.com/api/v1/partners/prt_51ab0c/stats \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "programId": "prg_9f2c1a",
    "days": 30,
    "currency": "USD",
    "partner": {
      "partnerId": "prt_51ab0c",
      "clicks": 400,
      "sales": 9,
      "revenueCents": 90000,
      "commissionCents": 22500,
      "roas": 3.6
    },
    "topSubIds": []
  }
}

Clicks

Counted clicks and results by country, device and sub ID.

EndpointWhat it does
GET/api/v1/programs/{programId}/breakdownResults by country or device
GET/api/v1/partners/{partnerId}/sub-idsA partner's results by sub ID
GET/api/v1/programs/{programId}/clicksList clicks

GET/api/v1/programs/{programId}/breakdown

Results by country or device

Clicks, sales, sales value and commission grouped by the visitor's country or device. A sale with no click (coupon or referral code) is grouped as not known.

Brand key, viewer role or higherMCP tool: get_program_breakdowngetProgramBreakdown
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
byquerystring: country, deviceNoGroup by country (default) or device.
daysqueryinteger: 7, 30, 90, 365, 0NoPeriod in days: 7, 30, 90 or 365, or 0 for all time. Default 30.
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/breakdown \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "by": "country",
    "days": 30,
    "totalClicks": 1200,
    "rows": [
      {
        "value": "GB",
        "clicks": 700,
        "sales": 20,
        "salesValueCents": 200000,
        "directCents": 50000,
        "commissionCents": 52000,
        "clickShare": 0.58,
        "conversionRate": 0.029
      }
    ]
  }
}

GET/api/v1/partners/{partnerId}/sub-ids

A partner's results by sub ID

Clicks, sales, sales value, commission earned and EPC grouped by one of the partner's sub IDs (sub1 to sub5).

Brand key, viewer role or higherMCP tool: get_partner_sub_idsgetPartnerSubIds
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
byquerystring: sub1, sub2, sub3, sub4, sub5NoWhich sub ID to group by. Default sub1.
daysqueryinteger: 7, 30, 90, 365, 0NoPeriod in days: 7, 30, 90 or 365, or 0 for all time. Default 30.
Request
curl https://viralpayouts.com/api/v1/partners/prt_51ab0c/sub-ids \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "by": "sub1",
    "days": 30,
    "rows": [
      {
        "value": "youtube",
        "clicks": 300,
        "sales": 6,
        "salesValueCents": 60000,
        "earnedCents": 15000,
        "conversionRate": 0.02,
        "epcCents": 50
      }
    ]
  }
}

GET/api/v1/programs/{programId}/clicks

List clicks

Counted clicks on the program's partner links, newest first: partner, country, device, referrer, landing page and sub IDs. Bots, previews and repeats are filtered out before they are counted.

Brand key, viewer role or higherMCP tool: list_clicksPaginatedlistClicks
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
partnerIdquerystringNoOnly this partner's clicks. up to 120 chars
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/clicks \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "id": "clk_a1b2",
      "partnerId": "prt_51ab0c",
      "partnerName": "Sam Lee",
      "createdAt": "2026-10-11T09:58:00.000Z",
      "country": "GB",
      "device": "mobile",
      "referrer": "https://www.youtube.com/",
      "landing": "https://acme.com/pricing",
      "subs": {
        "sub1": "youtube",
        "sub2": null,
        "sub3": null,
        "sub4": null,
        "sub5": null
      }
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Conversions

Sales and app events credited to partners: approve, reject, refund, test.

GET/api/v1/programs/{programId}/conversions

List conversions

Sales and app events credited to partners, newest first, with the commission, override and fee on each (cents, after refunds).

Brand key, viewer role or higherMCP tool: list_conversionsPaginatedlistConversions
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
statusquerystring: pending, approved, rejectedNoOnly conversions with this status.
partnerIdquerystringNoOnly this partner's conversions. up to 120 chars
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/conversions \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "id": "cnv_0c88aa",
      "partnerName": "Sam Lee",
      "externalRef": "ord_1001",
      "customer": "a***@example.com",
      "amountCents": 10000,
      "refundedCents": 0,
      "status": "pending",
      "attributedBy": "click",
      "subId": "youtube",
      "isTest": false,
      "occurredAt": "2026-10-11T10:00:00.000Z",
      "holdUntil": "2026-11-10T10:00:00.000Z",
      "directCents": 2500,
      "overrideCents": 250,
      "feeCents": 275
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET/api/v1/conversions/{conversionId}

Get a conversion

One conversion with its attribution, sub IDs, coupon, product, hold date and the money on it.

Brand key, viewer role or higherMCP tool: get_conversiongetConversion
FieldInTypeRequiredDescription
conversionIdpathstringYesThe conversion id (cnv_...).
Request
curl https://viralpayouts.com/api/v1/conversions/cnv_0c88aa \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "id": "cnv_0c88aa",
    "partnerName": "Sam Lee",
    "externalRef": "ord_1001",
    "customer": "a***@example.com",
    "amountCents": 10000,
    "refundedCents": 0,
    "status": "pending",
    "attributedBy": "click",
    "subId": "youtube",
    "isTest": false,
    "occurredAt": "2026-10-11T10:00:00.000Z",
    "holdUntil": "2026-11-10T10:00:00.000Z",
    "directCents": 2500,
    "overrideCents": 250,
    "feeCents": 275
  }
}

POST/api/v1/conversions/{conversionId}/approve

Approve a conversion

Approves a pending conversion. Its commission becomes payable on its hold date.

Brand key, manager role or higherMCP tool: decide_conversionapproveConversion
FieldInTypeRequiredDescription
conversionIdpathstringYesThe conversion id (cnv_...).
Request
curl -X POST https://viralpayouts.com/api/v1/conversions/cnv_0c88aa/approve \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "id": "cnv_0c88aa",
    "partnerName": "Sam Lee",
    "externalRef": "ord_1001",
    "customer": "a***@example.com",
    "amountCents": 10000,
    "refundedCents": 0,
    "status": "approved",
    "attributedBy": "click",
    "subId": "youtube",
    "isTest": false,
    "occurredAt": "2026-10-11T10:00:00.000Z",
    "holdUntil": "2026-11-10T10:00:00.000Z",
    "directCents": 2500,
    "overrideCents": 250,
    "feeCents": 275
  }
}

POST/api/v1/conversions/{conversionId}/reject

Reject a conversion

Rejects a conversion and reverses its commission, override and fee. Needs a reason the partner sees, or a reasonCode. Once a commission is past its payment date it can only be rejected with reasonCode fraud.

Brand key, manager role or higherMCP tool: decide_conversionrejectConversion
FieldInTypeRequiredDescription
conversionIdpathstringYesThe conversion id (cnv_...).
reasonbodystringNoWhy. The partner sees it. up to 500 chars
reasonCodebodystring: refund, chargeback, not_valid, duplicate, self_or_employee, banned_source, leaked_code, fraudNorefund: The customer was refunded; chargeback: The payment was charged back or disputed; not_valid: The customer did not meet the valid customer rule; duplicate: The sale was a duplicate or a test order; self_or_employee: The customer was the partner, someone in their household, or the brand's employee; banned_source: The sale came from a banned source or banned conduct; leaked_code: The coupon code was posted on a leak or deal site without permission; fraud: The sale was fraudulent
Request
curl -X POST https://viralpayouts.com/api/v1/conversions/cnv_0c88aa/reject \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"reason":"Brand bidding on our name"}'
Answer (200)
{
  "data": {
    "id": "cnv_0c88aa",
    "partnerName": "Sam Lee",
    "externalRef": "ord_1001",
    "customer": "a***@example.com",
    "amountCents": 10000,
    "refundedCents": 0,
    "status": "rejected",
    "attributedBy": "click",
    "subId": "youtube",
    "isTest": false,
    "occurredAt": "2026-10-11T10:00:00.000Z",
    "holdUntil": "2026-11-10T10:00:00.000Z",
    "directCents": 0,
    "overrideCents": 0,
    "feeCents": 0
  }
}

POST/api/v1/conversions/{conversionId}/refund

Record a refund

Records a refund of the order behind a conversion, exactly as a refund event from the signed event API: commission, override and fee are reversed in proportion. Send amountCents (this refund) or refundedTotalCents (everything refunded so far). Leave both out for a full refund. A refund after the partner was paid is recovered from their next commissions. Needs an Idempotency-Key.

Brand key, owner role or higherMCP tool: refund_conversionIdempotency-Key requiredrefundConversion
FieldInTypeRequiredDescription
conversionIdpathstringYesThe conversion id (cnv_...).
amountCentsbodyintegerNoThe amount of this refund, in cents. min 1
refundedTotalCentsbodyintegerNoThe total refunded on the order so far, in cents. min 0
Idempotency-KeyheaderstringYesA unique value per request; a retry with the same value is answered once.
Request
curl -X POST https://viralpayouts.com/api/v1/conversions/cnv_0c88aa/refund \
  -H "Authorization: Bearer vpk_..." \
  -H "Idempotency-Key: <a new UUID>" \
  -H "Content-Type: application/json" \
  -d '{"amountCents":2500}'
Answer (200)
{
  "data": {
    "id": "cnv_0c88aa",
    "partnerName": "Sam Lee",
    "externalRef": "ord_1001",
    "customer": "a***@example.com",
    "amountCents": 10000,
    "refundedCents": 10000,
    "status": "pending",
    "attributedBy": "click",
    "subId": "youtube",
    "isTest": false,
    "occurredAt": "2026-10-11T10:00:00.000Z",
    "holdUntil": "2026-11-10T10:00:00.000Z",
    "directCents": 0,
    "overrideCents": 0,
    "feeCents": 0
  }
}

POST/api/v1/programs/{programId}/test-sales

Send a test sale

Runs a test sale through the real pipeline for one partner (the first approved one if none is named). It is marked as a test and never becomes payable.

Brand key, manager role or higherMCP tool: send_test_salecreateTestSale
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
partnerIdbodystringNoA partner of this program. Optional. up to 120 chars
amountbodynumberNoSale amount in the program currency (major units, 100 = $100). Default 100. min 0.01, max 1000000
Idempotency-KeyheaderstringNoA unique value per request; a retry with the same value is answered once.
Request
curl -X POST https://viralpayouts.com/api/v1/programs/prg_9f2c1a/test-sales \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"partnerId":"..."}'
Answer (201)
{
  "data": {
    "recorded": true,
    "conversionId": "cnv_0c88aa",
    "partner": "Sam Lee",
    "amount": "100.00 USD"
  }
}

Commissions

The append-only ledger and what each partner is owed.

EndpointWhat it does
GET/api/v1/programs/{programId}/ledgerCommission ledger
GET/api/v1/programs/{programId}/balancesWhat each partner is owed

GET/api/v1/programs/{programId}/ledger

Commission ledger

Every money entry of the program, newest first. The ledger is append-only: a correction is a new entry with the opposite sign. kind is direct (commission), override (recruiter) or platform_fee; entry is accrual, reversal, payout or payout_reversal. Cents.

Brand key, viewer role or higherMCP tool: list_ledgerPaginatedlistLedger
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
partnerIdquerystringNoOnly this partner's entries. up to 120 chars
kindquerystring: direct, override, platform_feeNoOnly this kind.
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/ledger \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "id": 5120,
      "conversionId": "cnv_0c88aa",
      "partnerId": "prt_51ab0c",
      "partnerName": "Sam Lee",
      "kind": "direct",
      "entry": "accrual",
      "amountCents": 2500,
      "payoutId": null,
      "note": null,
      "isTest": false,
      "createdAt": "2026-10-11T10:00:01.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET/api/v1/programs/{programId}/balances

What each partner is owed

Per partner: pending (awaiting approval), in hold (approved, before its payment date), payable, being paid, paid, and earned this year. Cents; test sales left out.

Brand key, viewer role or higherMCP tool: list_balanceslistBalances
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/balances \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "partnerId": "prt_51ab0c",
      "name": "Sam Lee",
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "inFlight": 0,
      "paid": 30000,
      "earnedYtd": 42000
    }
  ]
}

Recruiting

The second tier: recruiter overrides, recruits, product rates and bounties.

EndpointWhat it does
GET/api/v1/partners/{partnerId}/recruitsWho a partner recruited
GET/api/v1/programs/{programId}/ratesCommission, recruiter override and product rates
PUT/api/v1/programs/{programId}/ratesSet product rates and event bounties
GET/api/v1/programs/{programId}/recruiter-overridesRecruiters and the overrides they earned

GET/api/v1/partners/{partnerId}/recruits

Who a partner recruited

The partners this partner recruited into the program, whether each is active, and the recruiter override this partner has earned on each (cents).

Brand key, viewer role or higherMCP tool: get_partner_recruitsgetPartnerRecruits
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
Request
curl https://viralpayouts.com/api/v1/partners/prt_51ab0c/recruits \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "id": "prt_8812aa",
      "name": "Jo Park",
      "status": "approved",
      "active": true,
      "approvedCustomers": 3,
      "myOverrideCents": 750
    }
  ]
}

GET/api/v1/programs/{programId}/rates

Commission, recruiter override and product rates

What partners earn on this program: the commission, the recruiter override (the second tier) with its base and period, and any product rates and app-event bounties.

Brand key, viewer role or higherMCP tool: get_ratesgetRates
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/rates \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "programId": "prg_9f2c1a",
    "currency": "USD",
    "commission": {
      "text": "25% of each sale",
      "rewardType": "percent",
      "rateBps": 2500,
      "fixedCents": 0
    },
    "recruiterOverride": {
      "on": true,
      "base": "commission",
      "rateBps": 1000,
      "durationDays": null
    },
    "rateRules": {
      "products": [
        {
          "match": "annual",
          "rewardType": "percent",
          "directRateBps": 3000,
          "directFixedCents": 0
        }
      ],
      "events": []
    }
  }
}

PUT/api/v1/programs/{programId}/rates

Set product rates and event bounties

Replaces the program's product rates (a different commission for a product_id or plan) and app-event bounties (a fixed amount per signup, install...). On a live program with approved partners a cut takes effect 3 business days later (scheduledFor in the answer) and an increase at once; every partner is emailed either way. A change starts a new terms version when it applies. The program's own commission and recruiter override are changed with PATCH /programs/{programId}.

Brand key, owner role or higherMCP tool: set_rate_rulessetRateRules
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
productsbodyarray of objectNo
eventsbodyarray of objectNo
Request
curl -X PUT https://viralpayouts.com/api/v1/programs/prg_9f2c1a/rates \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"products":[]}'
Answer (200)
{
  "data": {
    "rateRules": {
      "products": [
        {
          "match": "annual",
          "rewardType": "percent",
          "directRateBps": 3000,
          "directFixedCents": 0
        }
      ],
      "events": [
        {
          "name": "signup",
          "directFixedCents": 200
        }
      ]
    },
    "changed": true
  }
}

GET/api/v1/programs/{programId}/recruiter-overrides

Recruiters and the overrides they earned

Every partner of the program who recruited someone: how many recruits (and how many approved), and the recruiter override they earned on recruits' sales in the period, approved and pending (cents, after refunds). Test sales are left out.

Brand key, viewer role or higherMCP tool: list_recruiter_overrideslistRecruiterOverrides
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
daysqueryinteger: 7, 30, 90, 365, 0NoPeriod in days: 7, 30, 90 or 365, or 0 for all time. Default 30.
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/recruiter-overrides \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "days": 30,
    "override": {
      "on": true,
      "base": "commission",
      "rateBps": 1000
    },
    "recruiters": [
      {
        "partnerId": "prt_51ab0c",
        "name": "Sam Lee",
        "status": "approved",
        "recruits": 3,
        "approvedRecruits": 2,
        "approvedOverrideCents": 1500,
        "pendingOverrideCents": 250
      }
    ]
  }
}

Payouts

Payouts, payout batches, funding and invoices (read only).

GET/api/v1/programs/{programId}/funding

Funding, forecast and invoices

Money paid in for this program and what is left, whether it covers the next 7 days of payouts, the next payout day, and the program's invoices.

Brand key, viewer role or higherMCP tool: get_fundinggetFunding
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/funding \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "funding": {
      "depositedCents": 500000,
      "sentCents": 300000,
      "feesCents": 30000,
      "availableCents": 170000
    },
    "forecast": {
      "shortfallDay": null
    },
    "calendar": {
      "cutoff": "2026-10-24T23:59:59.000Z",
      "payoutAt": "2026-11-01T00:00:00.000Z"
    },
    "invoices": []
  }
}

GET/api/v1/programs/{programId}/payouts

List payouts

Payouts to partners, newest first: amount, method (whop or manual bank transfer), status (pending, paid, failed, held) and where a rail payout stands.

Brand key, viewer role or higherMCP tool: list_payoutsPaginatedlistPayouts
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
statusquerystring: pending, paid, failed, heldNoOnly payouts with this status.
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/payouts \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "id": "pay_77e1f0",
      "batchId": "bat_31c9d2",
      "partnerName": "Sam Lee",
      "amountCents": 12000,
      "currency": "USD",
      "method": "whop",
      "status": "paid",
      "createdAt": "2026-10-01T09:00:00.000Z",
      "settledAt": "2026-10-01T09:02:00.000Z",
      "railState": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET/api/v1/payouts/{payoutId}

Get a payout

One payout.

Brand key, viewer role or higherMCP tool: get_payoutgetPayout
FieldInTypeRequiredDescription
payoutIdpathstringYesThe payout id.
Request
curl https://viralpayouts.com/api/v1/payouts/pay_77e1f0 \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "id": "pay_77e1f0",
    "batchId": "bat_31c9d2",
    "partnerName": "Sam Lee",
    "amountCents": 12000,
    "currency": "USD",
    "method": "whop",
    "status": "paid",
    "createdAt": "2026-10-01T09:00:00.000Z",
    "settledAt": "2026-10-01T09:02:00.000Z",
    "railState": null
  }
}

GET/api/v1/programs/{programId}/payout-batches

List payout batches

Payout runs of the program, newest first, each with its total and what happened to its payouts: sent, sending, needing attention, by bank transfer or held.

Brand key, viewer role or higherMCP tool: list_payout_batchesPaginatedlistPayoutBatches
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/payout-batches \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "id": "bat_31c9d2",
      "createdAt": "2026-10-01T09:00:00.000Z",
      "payouts": 12,
      "totalCents": 145000,
      "summary": {
        "sent": 11,
        "sending": 0,
        "needsAttention": 0,
        "refused": 0,
        "byBankTransfer": 0,
        "held": 1
      }
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET/api/v1/payout-batches/{batchId}

Get a payout batch

One payout run with its summary and its payouts (up to 500).

Brand key, viewer role or higherMCP tool: get_payout_batchgetPayoutBatch
FieldInTypeRequiredDescription
batchIdpathstringYesThe payout batch id.
Request
curl https://viralpayouts.com/api/v1/payout-batches/bat_31c9d2 \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "id": "bat_31c9d2",
    "programId": "prg_9f2c1a",
    "createdAt": "2026-10-01T09:00:00.000Z",
    "summary": {
      "sent": 11,
      "sending": 0,
      "needsAttention": 0,
      "refused": 0,
      "byBankTransfer": 0,
      "held": 1
    },
    "payouts": [
      {
        "id": "pay_77e1f0",
        "batchId": "bat_31c9d2",
        "partnerName": "Sam Lee",
        "amountCents": 12000,
        "currency": "USD",
        "method": "whop",
        "status": "paid",
        "createdAt": "2026-10-01T09:00:00.000Z",
        "settledAt": "2026-10-01T09:02:00.000Z",
        "railState": null
      }
    ]
  }
}

Messages

Messages between the brand and its partners.

EndpointWhat it does
GET/api/v1/partners/{partnerId}/messagesMessages with a partner
POST/api/v1/partners/{partnerId}/messagesMessage a partner
POST/api/v1/programs/{programId}/messagesMessage every approved partner

GET/api/v1/partners/{partnerId}/messages

Messages with a partner

The latest messages between the brand and one partner, oldest first. Reading marks the partner's messages read.

Brand key, viewer role or higherMCP tool: list_messageslistPartnerMessages
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
Request
curl https://viralpayouts.com/api/v1/partners/prt_51ab0c/messages \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "messages": [
      {
        "id": "msg_1",
        "sender": "partner",
        "senderName": "Sam Lee",
        "body": "Can I run search ads?",
        "createdAt": "2026-10-11T10:00:00.000Z",
        "readAt": null
      }
    ]
  }
}

POST/api/v1/partners/{partnerId}/messages

Message a partner

Sends a message to one partner. They see it in their portal and are emailed.

Brand key, manager role or higherMCP tool: send_messagesendPartnerMessage
FieldInTypeRequiredDescription
partnerIdpathstringYesThe partner id (prt_...): one person in one program.
bodybodystringYesThe message, up to 4,000 characters. up to 4000 chars
Idempotency-KeyheaderstringNoA unique value per request; a retry with the same value is answered once.
Request
curl -X POST https://viralpayouts.com/api/v1/partners/prt_51ab0c/messages \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"body":"Hello!"}'
Answer (201)
{
  "data": {
    "sent": 1,
    "messageIds": [
      "msg_2"
    ],
    "broadcastId": null
  }
}

POST/api/v1/programs/{programId}/messages

Message every approved partner

Sends one message to every approved partner of a program. Up to 10 broadcasts an hour per brand.

Brand key, manager role or higherMCP tool: broadcast_messagebroadcastMessage
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
bodybodystringYesThe message, up to 4,000 characters. up to 4000 chars
Idempotency-KeyheaderstringNoA unique value per request; a retry with the same value is answered once.
Request
curl -X POST https://viralpayouts.com/api/v1/programs/prg_9f2c1a/messages \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"body":"Hello!"}'
Answer (201)
{
  "data": {
    "sent": 42,
    "messageIds": [
      "msg_3"
    ],
    "broadcastId": "bcast_1"
  }
}

Webhooks

Where sales come in (event API, Stripe, Shopify) and what each event did.

GET/api/v1/programs/{programId}/events

Incoming sale events

The sale and refund events this program received (event API, Stripe, Shopify, console), newest first, each with what happened to it: processed, duplicate, unmatched or error, and why.

Brand key, viewer role or higherMCP tool: list_eventsPaginatedlistEvents
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/events \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": [
    {
      "id": 1042,
      "source": "api",
      "external_id": "evt_1",
      "type": "sale",
      "status": "processed",
      "note": null,
      "conversion_id": "cnv_0c88aa",
      "received_at": "2026-10-11T10:00:01.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET/api/v1/programs/{programId}/webhooks

Webhook settings

Where sales reach this program (the signed event API, the Stripe and Shopify webhook URLs) and whether each Stripe or Shopify signing secret is set. Secrets are write-only: only their last 4 characters come back.

Brand key, viewer role or higherMCP tool: get_webhook_settingsgetWebhooks
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
Request
curl https://viralpayouts.com/api/v1/programs/prg_9f2c1a/webhooks \
  -H "Authorization: Bearer vpk_..."
Answer (200)
{
  "data": {
    "eventApi": {
      "url": "https://viralpayouts.com/api/v1/events"
    },
    "stripe": {
      "url": "https://viralpayouts.com/api/stripe/webhook/prg_9f2c1a",
      "secretSet": true,
      "last4": "a1B2"
    },
    "shopify": {
      "url": "https://viralpayouts.com/api/shopify/webhook/prg_9f2c1a",
      "secretSet": false,
      "last4": null
    }
  }
}

PUT/api/v1/programs/{programId}/webhooks/stripe

Set the Stripe signing secret

Stores the signing secret (whsec_...) of the Stripe webhook endpoint you pointed at this program, so its sales are verified. Send an empty string to clear it.

Brand key, owner role or higherMCP tool: set_stripe_webhook_secretsetStripeWebhookSecret
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
secretbodystringYesThe Stripe webhook signing secret, whsec_..., or "" to clear it. up to 250 chars
Request
curl -X PUT https://viralpayouts.com/api/v1/programs/prg_9f2c1a/webhooks/stripe \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"secret":"..."}'
Answer (200)
{
  "data": {
    "set": true,
    "last4": "a1B2"
  }
}

PUT/api/v1/programs/{programId}/webhooks/shopify

Set the Shopify signing secret

Stores the Shopify webhook signing secret for this program's order webhooks. Send an empty string to clear it.

Brand key, owner role or higherMCP tool: set_shopify_webhook_secretsetShopifyWebhookSecret
FieldInTypeRequiredDescription
programIdpathstringYesThe program id (prg_...), from GET /programs.
secretbodystringYesThe Shopify webhook signing secret, or "" to clear it. up to 250 chars
Request
curl -X PUT https://viralpayouts.com/api/v1/programs/prg_9f2c1a/webhooks/shopify \
  -H "Authorization: Bearer vpk_..." \
  -H "Content-Type: application/json" \
  -d '{"secret":"..."}'
Answer (200)
{
  "data": {
    "set": true,
    "last4": "x9Yz"
  }
}

Partner

The partner API, for partner keys (vpp_...): your programs, links, stats, earnings, recruits, postbacks and the marketplace.

GET/api/v1/partner/me

Your account

Your account (the person across every program), your public page and the key making the call.

Partner key, read scope or writeMCP tool: get_my_accountgetMyAccount
Request
curl https://viralpayouts.com/api/v1/partner/me \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "account": {
      "id": "acc_7d10e2",
      "handle": "sam",
      "name": "Sam Lee",
      "email": "sam@example.com",
      "country": "GB",
      "emailVerified": true,
      "profileUrl": "https://viralpayouts.com/r/sam"
    },
    "key": {
      "id": "pkey_1",
      "name": "My script",
      "scope": "read"
    }
  }
}

GET/api/v1/partner/programs

Programs you are in

Every program you have joined or applied to, with your membership status and the program's.

Partner key, read scope or writeMCP tool: list_my_programslistMyPrograms
Request
curl https://viralpayouts.com/api/v1/partner/programs \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": [
    {
      "partnerId": "prt_51ab0c",
      "programId": "prg_9f2c1a",
      "programSlug": "acme-pro",
      "programName": "Acme Pro",
      "currency": "USD",
      "status": "approved",
      "programStatus": "live",
      "joinedAt": "2026-09-01T10:00:00.000Z"
    }
  ]
}

GET/api/v1/partner/programs/{program}

One of your programs

Your membership of one program: its terms, your links and coupon, your balance (cents) and why it is not paid yet if it is not.

Partner key, read scope or writeMCP tool: get_my_programgetMyProgram
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
Request
curl https://viralpayouts.com/api/v1/partner/programs/acme-pro \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "partner": {
      "id": "prt_51ab0c",
      "status": "approved"
    },
    "program": {
      "id": "prg_9f2c1a",
      "name": "Acme Pro",
      "directText": "25% of each sale"
    },
    "links": {
      "customer": "https://viralpayouts.com/r/sam/acme-pro"
    },
    "balance": {
      "pending": 2500,
      "inHold": 7500,
      "payable": 12000,
      "paid": 30000
    }
  }
}

GET/api/v1/partner/programs/{program}/stats

Your results by sub ID

Your clicks, sales, sales value, commission earned and EPC in one program, grouped by one sub ID, with totals. Cents.

Partner key, read scope or writeMCP tool: get_my_statsgetMyStats
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
byquerystring: sub1, sub2, sub3, sub4, sub5NoWhich sub ID to group by. Default sub1.
daysqueryinteger: 7, 30, 90, 365, 0NoPeriod in days: 7, 30, 90 or 365, or 0 for all time. Default 30.
Request
curl https://viralpayouts.com/api/v1/partner/programs/acme-pro/stats \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "by": "sub1",
    "days": 30,
    "totals": {
      "clicks": 400,
      "sales": 9,
      "salesValueCents": 90000,
      "earnedCents": 22500
    },
    "rows": []
  }
}

GET/api/v1/partner/programs/{program}/breakdown

Your results by country or device

Your clicks, sales and commission in one program grouped by the visitor's country or device.

Partner key, read scope or writeMCP tool: get_my_breakdowngetMyBreakdown
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
byquerystring: country, deviceNoGroup by country (default) or device.
daysqueryinteger: 7, 30, 90, 365, 0NoPeriod in days: 7, 30, 90 or 365, or 0 for all time. Default 30.
Request
curl https://viralpayouts.com/api/v1/partner/programs/acme-pro/breakdown \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "by": "device",
    "days": 30,
    "totalClicks": 400,
    "rows": [
      {
        "value": "mobile",
        "clicks": 300,
        "sales": 6
      }
    ]
  }
}

GET/api/v1/partner/programs/{program}/conversions

Your conversions

Your sales in one program and your recruits' sales (where you earn the override), newest first, with what you earned on each. On a recruit's sale you see the outcome and your earning, never the customer.

Partner key, read scope or writeMCP tool: list_my_conversionsPaginatedlistMyConversions
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/partner/programs/acme-pro/conversions \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": [
    {
      "id": "cnv_0c88aa",
      "mine": true,
      "customer": "a***@example.com",
      "amountCents": 10000,
      "status": "approved",
      "subId": "youtube",
      "occurredAt": "2026-10-11T10:00:00.000Z",
      "holdUntil": "2026-11-10T10:00:00.000Z",
      "myCents": 2500
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET/api/v1/partner/programs/{program}/payouts

Your payouts

Payouts to you from one program, newest first.

Partner key, read scope or writeMCP tool: list_my_payoutsPaginatedlistMyPayouts
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/partner/programs/acme-pro/payouts \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": [
    {
      "id": "pay_77e1f0",
      "amountCents": 12000,
      "currency": "USD",
      "method": "whop",
      "status": "paid",
      "createdAt": "2026-10-01T09:00:00.000Z",
      "settledAt": "2026-10-01T09:02:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET/api/v1/partner/earnings

Your earnings across programs

Your balance in every program (pending, in hold, payable, being paid, paid; cents), totals per currency, and what you earned this year across all programs.

Partner key, read scope or writeMCP tool: get_my_earningsgetMyEarnings
Request
curl https://viralpayouts.com/api/v1/partner/earnings \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "programs": [
      {
        "partnerId": "prt_51ab0c",
        "programName": "Acme Pro",
        "currency": "USD",
        "balance": {
          "pending": 2500,
          "inHold": 7500,
          "payable": 12000,
          "paid": 30000,
          "inFlight": 0,
          "earnedYtd": 42000
        }
      }
    ],
    "totalsByCurrency": {
      "USD": {
        "pending": 2500,
        "inHold": 7500,
        "payable": 12000,
        "paid": 30000,
        "inFlight": 0
      }
    },
    "earnedYtdCents": 42000
  }
}

GET/api/v1/partner/programs/{program}/recruits

Your recruits and overrides

The partners you recruited into one program, whether each is active, and the override you earned on each (cents), with the total.

Partner key, read scope or writeMCP tool: list_my_recruitslistMyRecruits
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
Request
curl https://viralpayouts.com/api/v1/partner/programs/acme-pro/recruits \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "overrideText": "10% of the commission of partners they recruit",
    "totalOverrideCents": 750,
    "recruits": [
      {
        "id": "prt_8812aa",
        "name": "Jo Park",
        "status": "approved",
        "active": true,
        "approvedCustomers": 3,
        "myOverrideCents": 750
      }
    ]
  }
}

GET/api/v1/partner/marketplace

Search programs to join

The public directory: live programs of brands ViralPayouts has activated. Matches q against the name, description and brand. Shows whether you are already in each.

Partner key, read scope or writeMCP tool: search_marketplacePaginatedsearchMarketplace
FieldInTypeRequiredDescription
qquerystringNoWords to look for. up to 100 chars
limitqueryintegerNoHow many items to return, 1 to 100. Default 25. min 1, max 100
cursorquerystringNoThe next_cursor of the previous page. Leave out for the first page. up to 400 chars
Request
curl https://viralpayouts.com/api/v1/partner/marketplace \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": [
    {
      "id": "prg_9f2c1a",
      "slug": "acme-pro",
      "name": "Acme Pro",
      "brandName": "Acme",
      "commission": "25% of each sale",
      "recruiterOverride": "10% of the commission of partners they recruit",
      "currency": "USD",
      "openToAll": false,
      "holdDays": 30,
      "joined": false,
      "page": "https://viralpayouts.com/programs/acme-pro"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

POST/api/v1/partner/marketplace/{program}/join

Join a program

Applies to a program as you. An open program approves you at once (unless a risk check holds the application for review); otherwise the brand reviews it. You must accept the program terms (read them at the terms URL first).

Partner key, write scopeMCP tool: join_programjoinProgram
FieldInTypeRequiredDescription
programpathstringYesThe program to join: its id or slug, from GET /partner/marketplace.
acceptTermsbodyboolean: trueYestrue: you accept the program terms.
channelbodystringNoWhere you will promote it. up to 80 chars
reachbodystringNoYour audience size, roughly. up to 40 chars
proofUrlbodystringNoA link to your channel or site. up to 300 chars
notebodystringNoAnything the brand should know. up to 1000 chars
promoTextbodystringNoHow you will promote it. Saved to your profile; leave out to use your profile. up to 1000 chars
primaryMethodbodystringNoYour main method (a method key). Leave out to use your profile. up to 40 chars
methodsbodyarray of stringNoOther methods (method keys).
paidSearchbodystring: none, non_brand, brandNoWhether you buy search ads, and on brand names.
acceptDisclosurebodybooleanNotrue: you promise to label posts with your link as ads.
answersbodyobjectNoAnswers to the program's own questions, by question id.
Idempotency-KeyheaderstringNoA unique value per request; a retry with the same value is answered once.
Request
curl -X POST https://viralpayouts.com/api/v1/partner/marketplace/acme-pro/join \
  -H "Authorization: Bearer vpp_..." \
  -H "Content-Type: application/json" \
  -d '{"acceptTerms":true}'
Answer (201)
{
  "data": {
    "partnerId": "prt_99aa01",
    "programSlug": "acme-pro",
    "status": "pending"
  }
}

GET/api/v1/partner/programs/{program}/postback

Your postback URL

The URL ViralPayouts calls when one of your sales is recorded, approved or reversed in this program, the macros it can carry, and the latest deliveries.

Partner key, read scope or writeMCP tool: get_postbackgetMyPostback
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
Request
curl https://viralpayouts.com/api/v1/partner/programs/acme-pro/postback \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "url": "https://tracker.example/pb?cid={click_id}&amt={payout}",
    "macros": [
      "sub_id",
      "sub2",
      "sub3",
      "sub4",
      "sub5",
      "click_id",
      "conversion_id",
      "payout",
      "currency",
      "status",
      "event",
      "program"
    ],
    "deliveries": []
  }
}

PUT/api/v1/partner/programs/{program}/postback

Set your postback URL

Sets or clears (null) the postback URL for one program. Use macros such as {click_id}, {payout} and {status}.

Partner key, write scopeMCP tool: set_postbacksetMyPostback
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
urlbodystring or nullYesAn https URL with macros, or null to clear it. up to 1000 chars
Request
curl -X PUT https://viralpayouts.com/api/v1/partner/programs/acme-pro/postback \
  -H "Authorization: Bearer vpp_..." \
  -H "Content-Type: application/json" \
  -d '{"url":"..."}'
Answer (200)
{
  "data": {
    "ok": true,
    "url": "https://tracker.example/pb?cid={click_id}&amt={payout}"
  }
}

POST/api/v1/partner/programs/{program}/postback/test

Send a test postback

Calls your postback URL once with sample values and returns what happened. Up to 10 an hour per program.

Partner key, write scopeMCP tool: test_postbacktestMyPostback
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
Request
curl -X POST https://viralpayouts.com/api/v1/partner/programs/acme-pro/postback/test \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "ok": true,
    "delivery": {
      "status": "sent",
      "lastStatusCode": 200
    }
  }
}

GET/api/v1/partner/programs/{program}/messages

Your messages with the brand

The latest messages between you and the brand of one program, oldest first. Reading marks the brand's messages read.

Partner key, read scope or writeMCP tool: list_my_messageslistMyMessages
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
Request
curl https://viralpayouts.com/api/v1/partner/programs/acme-pro/messages \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "messages": [
      {
        "id": "msg_2",
        "sender": "brand",
        "senderName": "Acme",
        "body": "Yes, branded search is fine.",
        "createdAt": "2026-10-11T11:00:00.000Z",
        "readAt": null
      }
    ]
  }
}

POST/api/v1/partner/programs/{program}/messages

Message the brand

Sends a message to the brand of one of your programs. Up to 20 an hour per program.

Partner key, write scopeMCP tool: send_my_messagesendMyMessage
FieldInTypeRequiredDescription
programpathstringYesOne of your programs: its id (prg_...), its slug, or your membership id (prt_...).
bodybodystringYesThe message, up to 4,000 characters. up to 4000 chars
Idempotency-KeyheaderstringNoA unique value per request; a retry with the same value is answered once.
Request
curl -X POST https://viralpayouts.com/api/v1/partner/programs/acme-pro/messages \
  -H "Authorization: Bearer vpp_..." \
  -H "Content-Type: application/json" \
  -d '{"body":"Hello!"}'
Answer (201)
{
  "data": {
    "sent": 1,
    "messageId": "msg_4"
  }
}

GET/api/v1/partner/profile

Your partner profile

How you promote (your words, main and other methods, paid-search answer, the promise to label posts as ads) and the places you promote from, each with whether ViralPayouts verified it, how, and the follower count when one was fetched. Brands see this when you apply.

Partner key, read scope or writeMCP tool: get_my_profilegetMyProfile
Request
curl https://viralpayouts.com/api/v1/partner/profile \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "bio": "I write about bookkeeping tools for UK accountants.",
    "promoText": "A comparison post on my blog plus two newsletter mentions a month to 3,000 accountants.",
    "primaryMethod": "content_review",
    "methods": [
      "content_review",
      "newsletter_email"
    ],
    "paidSearch": "none",
    "disclosureAttested": true,
    "properties": [
      {
        "id": "ppt_1a2b",
        "type": "website",
        "identifier": "example-books.co.uk",
        "verified": true,
        "method": "dns_txt",
        "followers": null
      }
    ]
  }
}

PUT/api/v1/partner/profile

Update your partner profile

Saves how you promote. promoText (at least 40 characters) and primaryMethod are required; methods are keys from the list in the docs (content_review, social_organic, newsletter_email, paid_search_brand ...). Fields you leave out keep their value.

Partner key, write scopeMCP tool: update_my_profileupdateMyProfile
FieldInTypeRequiredDescription
promoTextbodystringYesHow you will promote, in your own words: who sees it, where, how often. up to 1000 chars
primaryMethodbodystringYesYour main promotional method (a method key). up to 40 chars
methodsbodyarray of stringNoOther methods (method keys).
biobodystringNoAbout you and your audience. Shown on your public page. up to 600 chars
paidSearchbodystring: none, non_brand, brandNoWhether you buy search ads, and on brand names.
acceptDisclosurebodybooleanNotrue: you promise to label every post with your link as an ad (#ad or "Paid link").
profilePublicbodybooleanNoShow your bio and verified places on your public page.
Request
curl -X PUT https://viralpayouts.com/api/v1/partner/profile \
  -H "Authorization: Bearer vpp_..." \
  -H "Content-Type: application/json" \
  -d '{"promoText":"...","primaryMethod":"..."}'
Answer (200)
{
  "data": {
    "promoText": "A comparison post on my blog plus two newsletter mentions a month to 3,000 accountants.",
    "primaryMethod": "content_review",
    "methods": [
      "content_review"
    ],
    "properties": []
  }
}

GET/api/v1/partner/properties

The places you promote from

Your websites, newsletters and social accounts, with verification status, method, last check and follower counts as fetched (null when not fetched). To verify one, use the partner portal.

Partner key, read scope or writeMCP tool: list_my_propertieslistMyProperties
Request
curl https://viralpayouts.com/api/v1/partner/properties \
  -H "Authorization: Bearer vpp_..."
Answer (200)
{
  "data": {
    "properties": [
      {
        "id": "ppt_1a2b",
        "type": "youtube",
        "identifier": "examplechannel",
        "url": "https://www.youtube.com/@examplechannel",
        "verified": true,
        "method": "bio_code",
        "followers": 12400,
        "statsSource": "scraper:anyapi",
        "lastCheckedAt": "2026-10-13T09:00:00.000Z"
      }
    ]
  }
}