This reference lists every /api/v1 endpoint in the AffiliateBase REST API with its parameters, request fields, and response contracts. It is written for developers building an integration. Authentication and permissions are covered in API Keys and REST API.
Global contract
- Auth (one of):
Authorization: Bearer <API_KEY>,Authorization: Basic <base64(API_KEY:)>,X-Api-Key: <API_KEY>. OAuth access tokens issued to AI connectors are refused with403 FORBIDDEN. - Account context (required):
?account_id=<ACCOUNT_ID>orX-Account-Id: <ACCOUNT_ID> - Pagination defaults:
page=1,limit=25, maxlimit=100 - Array params:
state[]=due&state[]=pendingorstate=due,pending - Expand params:
expand[]=campaign&expand[]=saleorexpand=campaign,sale - Timestamp filters (
updated_since,updated_until) must be ISO-8601 where supported. An invalid value returns422 VALIDATION_ERROR. - Permissions: read requests (
GET) need thereadaction and all other methods needwrite, for examplev1.referrals.write.
Common error envelope:
{
"error": "Account ID is required",
"code": "MISSING_ACCOUNT_ID"
}Common error codes: MISSING_ACCOUNT_ID, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, INVALID_PARAMETER (400), TOO_MANY_REQUESTS (429; see Rate limits)
Payout endpoints return operation-specific 422 codes: PAYOUT_EXECUTION_FAILED, PAYOUT_RUN_CREATE_FAILED, PAYOUT_RUN_EXECUTION_FAILED, PAYOUT_RUN_REMEDIATION_FAILED, PAYOUT_EXPORT_CREATE_FAILED, PAYOUT_EXPORT_DOWNLOAD_FAILED, PAYOUT_EXPORT_DELETE_FAILED, and PAYOUT_EXPORT_SETTLEMENT_FAILED.
API keys
GET /api/v1/api_keys- Query:
page,limit - Role (session auth):
owneroradmin. API key auth needs a full-access key or exactlyv1.api_keys.manage.
- Query:
POST /api/v1/api_keys- Body:
name(string, required)permissions(object, required, non-empty)expires_at(ISO datetime, optional)
- Notes:
permissionsmust be an object.- A request that omits
permissionsor sends an empty object returns422 VALIDATION_ERROR. For full access, send{ "all": true }. - A key authenticated by API key can only create keys whose permissions it already holds.
- Role (session auth):
owneroradmin
- Body:
DELETE /api/v1/api_keys/:id- Revokes key (does not hard-delete row)
- Returns success message JSON
- Role (session auth):
owneroradmin
Affiliates
GET /api/v1/affiliates- Query:
page,limitcampaign_idemailstateorstate[](pending|active|disabled|suspicious|rejected|banned)stripe_customer_idupdated_since,updated_until(ISO-8601)expand[](campaign,links,coupon,commission_stats)
- Query:
POST /api/v1/affiliates- Body:
email(required)campaign_id(recommended; if omitted, an arbitrary non-archived campaign is used (422 if none). Always sendcampaign_id)first_name,last_name(optional)state(optional, defaults toactive)paypal_email,wise_email(optional)
- If the email already belongs to an AffiliateBase user, the request succeeds only when that user is already an affiliate or team member on this account. Otherwise it returns
422; usePOST /api/v1/invitationswithkind=affiliateso the person can accept. - Users created through this endpoint start with an unconfirmed email address.
- Body:
GET /api/v1/affiliates/:id- Query:
expand[](campaign,links,coupon,commission_stats)
- Query:
PATCH /api/v1/affiliates/:id- Body:
statecampaign_idpaypal_emailwise_email
statecannot be set tobanned, or changed while the affiliate is banned. Both return422 VALIDATION_ERROR; use the ban and unban endpoints below.
- Body:
DELETE /api/v1/affiliates/:id- Archives by default
- Returns
204 No Content
POST /api/v1/affiliates/:id/ban- Body:
reason(required:fraud,fake_traffic,self_referral,tos_violation,brand_abuse,spam,other)note(optional, up to 500 characters; kept for your audit trail, never returned by the API or sent in webhooks)dry_run(boolean, optional; previews the impact without changing anything)idempotency_key(optional; or send theIdempotency-Keyheader)requested_by(optional; who asked for the ban, recorded in the status history)
- Bans every affiliate record of the same person on the account, including archived ones. It voids unpaid commissions (voids are final), removes or reduces payouts that have not been submitted, archives links, and stops promo codes. Payouts already submitted or exported are left as they are and listed in
impact.untouched_payouts. - Returns a slim affiliate view (
id,state,campaign_id,archived,banned_at,ban_reason, without contact or payout details) plusimpact,dry_run, andnoop. Banning someone who is already banned returns200withnoop: trueand an emptyimpact. Retrying with the same idempotency key returns the originalimpactwithout banning again. - Errors:
422 VALIDATION_ERRORfor a missing or unknown reason or a note over 500 characters;404 NOT_FOUNDfor an unknown affiliate. - Permission: a full-access key, or a key with exactly
v1.affiliates.ban. Session auth:owneroradmin.
- Body:
POST /api/v1/affiliates/:id/unban- Body:
note(required, up to 500 characters)dry_run,idempotency_key,requested_by(optional, as for ban)
- Moves every banned record of the person to
disabled, never straight toactive. Reactivate separately withPATCH /api/v1/affiliates/:id. Voided commissions stay voided, and archived links and stopped promo codes stay stopped. - Returns the same slim affiliate view plus
impact,dry_run, andnoop. Unbanning an affiliate that is not banned returns422 VALIDATION_ERROR. - Permission: same as ban.
- Body:
- Affiliate objects include
banned_atandban_reason. Both arenullunlessstateisbanned.
Affiliate coupons
GET /api/v1/affiliates/:affiliate_id/couponPOST /api/v1/affiliates/:affiliate_id/couponPATCH /api/v1/affiliates/:affiliate_id/couponDELETE /api/v1/affiliates/:affiliate_id/coupon- Returns
204 No Content
- Returns
POST /api/v1/affiliates/:affiliate_id/coupon/sync- Body:
force(boolean, optional)
- Body:
Campaigns
GET /api/v1/campaigns- Query:
page,limit
- Query:
POST /api/v1/campaignsPATCH /api/v1/campaigns/:id- Body fields (partial updates supported):
name,slug,urlattribution_model(last_click|first_click; defaults tolast_click)private,private_tokensreward_type(percent|fixed)commission_percentcommission_amount_cents,commission_amount_currencyminimum_payout_cents,minimum_payout_currencymax_commission_period_months,max_commissionsintroductory_commission_percent,introductory_commission_months(percent campaigns; both required together, or both blank to clear)days_before_referrals_expire,days_until_commissions_are_dueaffiliate_dashboard_text,custom_reward_description,welcome_textcustomers_visible_to_affiliates,sale_description_visible_to_affiliatesparameter_typestripe_coupon_idpromo_code_provisioning(automatic|individual; defaults toautomatic)default
- Body fields (partial updates supported):
GET /api/v1/campaigns/:idDELETE /api/v1/campaigns/:id- Archives by default
- Returns
204 No Content
promo_code_provisioning=individual disables future automatic code generation for the campaign. Create or import each affiliate’s chosen code through the promo-code endpoints. Switching modes does not rename, stop, or replace existing codes. Explicit calls to the singular affiliate coupon creation endpoint still create a code.
Links
GET /api/v1/links- Query:
affiliate_idcampaign_idpage,limit
- Query:
POST /api/v1/links- Body:
affiliate_id(required)campaign_id(required)url(required)token(optional; auto-generated if missing)
- Body:
GET /api/v1/links/:idPATCH /api/v1/links/:id- Body:
affiliate_id,campaign_id,url,token
- Body:
DELETE /api/v1/links/:id- Archives by default
- Returns
204 No Content
Referrals
GET /api/v1/referrals- Query:
page,limitaffiliate_idconversion_stateorconversionStatestripe_customer_idemail(customer email)updated_since,updated_until(ISO-8601)expand[](link,customer,affiliate)
- Query:
POST /api/v1/referrals- Body:
affiliate_id(required)campaign_id(required whenlink_idis omitted)link_id(optional)conversion_state(visitor|lead|conversion, optional)expires_at(ISO datetime, optional)
- Body:
GET /api/v1/referrals/:id- Query:
expand[](link,customer,affiliate)
- Query:
PATCH /api/v1/referrals/:id- Body:
conversion_state(visitor|lead|conversion)deactivated_at(ISO datetime; deactivates the referral. Sendingnullfor a deactivated referral returns422, because reactivation is not supported)customer_email(optional; binds/creates customer)
- Body:
DELETE /api/v1/referrals/:id- Soft-deactivates referral
- Returns
204 No Content
Commissions
GET /api/v1/commissions- Query:
page,limitstateorstate[]affiliate_idcampaign_idupdated_since,updated_until(ISO-8601)expand[](campaign,sale)
- Query:
GET /api/v1/commissions/:id- Returns the commission object directly, not wrapped in
data. - Includes expanded
campaignandsaledata. - The expanded sale includes referral, customer, and affiliate details when present.
- Common enrichment fields:
sale.referral.customer.emailsale.referral.customer.namesale.referral.stripe_customer_idsale.affiliate.idsale.affiliate.emailsale.affiliate.first_namesale.affiliate.last_name
- Returns the commission object directly, not wrapped in
PATCH /api/v1/commissions/:id- Body:
due_at(ISO datetime, optional)
- Notes:
- Changing
due_atrecalculatespending|due, unless the commission ispaid,processing, orvoided. paid_atis rejected with422 VALIDATION_ERROR. Commissions become paid through payout settlement:POST /api/v1/payouts/:id/payor a payout export payment.- A commission held for fraud review cannot have
due_atchanged (422); resolve it withPOST /api/v1/commissions/:id/review.
- Changing
- Body:
DELETE /api/v1/commissions/:id- Soft-voids commission
- Body:
void_reason(optional)
- Returns
object,id,deleted: false,state,adjustment_commission_id, andpost_submission_adjustment. - If the commission’s payout was already exported, paid, or sent for payment, the commission is left as is and a negative adjustment commission (
adjustment_commission_id,post_submission_adjustment: true) is created to net against the affiliate’s next payout.
POST /api/v1/commissions/:id/review- Body:
review_action(approve|suspicious|deactivate)
approvereleases the hold and returns the commission topendingordue.suspiciouskeeps it held and suspends the affiliate.deactivatevoids the commission and suspends the affiliate.
- Body:
Payouts
GET /api/v1/payouts- Query:
page,limitaffiliate_idstateorstate[]eligible=true(returns eligible payout candidates; non-paginated)updated_since,updated_until(ISO-8601)expand[](affiliate,commissions)
- Query:
GET /api/v1/payouts/:idPOST /api/v1/payouts/:id/pay- Body:
payment_reference(optional)dry_run(boolean, optional; previews without paying)idempotency_keyandrequested_by(optional; recorded in the audit trail. TheIdempotency-Keyheader is also accepted)
- If the payout’s method is Stripe, this sends real money through Stripe. PayPal and Wise payouts are recorded as paid; you send the money yourself.
- A Stripe payout that belongs to a payout export is refused with
422. Settle the export instead, or delete it withDELETE /api/v1/payout_exports/:id(Void export before download in the dashboard) if it has not been downloaded.
- Body:
Payout exports
Payout export endpoints require the owner or admin role for session auth.
GET /api/v1/payout_exports- Query:
limit
- Query:
POST /api/v1/payout_exports- Body:
payout_ids(array, optional)export_format(csv|paypal|wise, optional)notes(optional)
- Body:
GET /api/v1/payout_exports/:id- Query:
export_format(csv|paypal|wise, optional)
- Notes:
- Returns metadata plus
filenameandcontent
- Returns metadata plus
- Query:
DELETE /api/v1/payout_exports/:idPOST /api/v1/payout_exports/:payout_export_id/payment- Body:
payment_reference(optional)
- Body:
Payout runs
Payout run endpoints require the owner or admin role for session auth.
POST /api/v1/payout_runs- Body:
idempotency_key(optional; generated if omitted)execute_now(boolean, optional, defaultfalse)affiliate_ids(array, optional)payment_reference(optional; used whenexecute_now=true)
- Body:
PUT /api/v1/payout_runs/:id/execute- Body:
async(boolean, optional, defaulttrue)payment_reference(optional)dry_run(boolean, optional; previews without executing)idempotency_key(optional; or send theIdempotency-KeyorX-Idempotency-Keyheader. A key repeated within 24 hours returnsreplayed: truewithout executing again)
- Body:
PUT /api/v1/payout_runs/:id/remediate- Body:
remediation_action(required)issue_code(optional)payout_ids(array, optional)dry_run,idempotency_key(optional, as for execute)
- Body:
GET /api/v1/payout_runs/reconcile- Query:
lookback_days(optional, default30)
- Query:
Invitations
GET /api/v1/invitations- Query:
kind(member|affiliate, optional)status(pending|accepted|revoked, optional)page,limit
- Query:
POST /api/v1/invitations- Body:
kind(member|affiliate, required)email(required)role(formember, optional, defaults tomember)campaign_idorcampaignId(foraffiliate, required)
- Body:
GET /api/v1/invitations/:id- Query:
kind(optional but recommended)
- Query:
PATCH /api/v1/invitations/:id- Body:
kind(optional but recommended)invitation_actionorstatus_action(accept|revoke)status=revoked(alias for revoke)role(member-invitation role update path)
- Accepting (
accept) requires a signed-in user whose email matches the invitation, so it works from the dashboard session and not with an API key. An API-key acceptance request returns422, anduser_idis ignored. - Only an account owner can grant the
ownerrole. A full-access API key acts as the owner who created it. A scoped key can never grantowner.
- Body:
DELETE /api/v1/invitations/:id- Query/body:
kind(optional but recommended)
- Returns
204 No Content
- Query/body:
POST /api/v1/invitations/:id/resend- Query/body:
kind(optional but recommended)
- Query/body:
LLM and SDK integration tip
Use the machine-readable contract when generating clients:
- OpenAPI:
/openapi/v1.yaml - Docs page: OpenAPI Spec
Example request
curl "https://app.affiliatebase.io/api/v1/commissions?account_id=<ACCOUNT_ID>&state=due&expand=campaign,sale&page=1&limit=25" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json"Individually assigned promo codes
Use these endpoints to manage individually assigned Stripe promo codes. Stripe operations require a connected Stripe account and an active affiliate in an active campaign. They use the existing v1.affiliate_coupons.read and v1.affiliate_coupons.write API permissions.
| Endpoint | Behavior |
|---|---|
GET /api/v1/affiliates/:affiliate_id/promo_codes | List assignments; use q for exact Stripe code search or discounts=true for valid discounts. |
POST /api/v1/affiliates/:affiliate_id/promo_codes | Preview/import an exact stripe_promotion_code_id, or use mode=create, code and stripe_coupon_id. |
PATCH /api/v1/affiliates/:affiliate_id/promo_codes/:id | Preview or persist the current provider observation. |
DELETE /api/v1/affiliates/:affiliate_id/promo_codes/:id | Preview or stop tracking locally, keeping Stripe redemption and historical records intact. |
Writes default to dry_run=true. Execute using dry_run=false; import and creation also require a stable Idempotency-Key. After an uncertain response, retry with the same key and payload. On OPERATION_PENDING, check the original result. On RECONCILIATION_REQUIRED, review the original operation before another creation attempt. An assignment response does not prove paid attribution.
Display code to customers. tracking_token is an opaque compatibility identifier. Ownership uses the exact provider ID, account and live/test mode. Codes cannot be transferred to another affiliate or reactivated after tracking stops. The existing singular /coupon endpoint remains the legacy campaign default.
The affiliate_promo_codes MCP tool exposes the same operations and defaults to preview. A connector token needs explicit mcp.affiliate_coupons.read / mcp.affiliate_coupons.write permissions. To grant them, select the optional Allow promo-code management checkbox when you create a token. Existing tokens keep their current permissions.
See Manage Promo Codes with MCP for copyable tool calls, permission setup and retry guidance, or Assign Stripe Promo Codes to Affiliates for the merchant workflow.