> For the complete documentation index, see [llms.txt](https://docs.growsurf.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.growsurf.com/developer-tools/rest-api/api-reference.md).

# API Reference

This reference documents the GrowSurf REST API, including all available public methods and examples of each.

{% hint style="info" %}

* **Using AI?** Follow [Build with AI](/build-with-ai.md). It gives you prompts to copy and keeps each approval point under your control. Your assistant chooses the supported connection method.
* **Using TypeScript, Python, PHP, Ruby, or Java?** Use an official GrowSurf API library. [Learn more](https://docs.growsurf.com/developer-tools/rest-api/api-libraries).
* **Building a native iOS or Android app?** Use the [iOS SDK](/developer-tools/ios-sdk.md) or [Android SDK](/developer-tools/android-sdk.md) for mobile attribution, participant creation, sharing, and referral portal data. Use this REST API from your backend for secure server-side actions.
  {% endhint %}

{% hint style="warning" %}
Do not embed your REST API key in native mobile apps. The iOS and Android SDKs use a separate Mobile SDK API and public Mobile SDK key. This REST API remains the right place for backend-only operations, including server-verified purchase or subscription referral qualification.
{% endhint %}

## INDEX ↓

| Section                                                                                                         | Endpoints                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Accounts](https://docs.growsurf.com/developer-tools/rest-api/api-reference#accounts)                           | [Create an account without an API key](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-accounts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| [Teams](https://docs.growsurf.com/developer-tools/rest-api/api-reference#teams)                                 | [Retrieve team](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-team) · [Update team](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-team) · [Request team verification](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-team-verification-request) · [Resend team owner verification email](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-team-owner-verification-email) · [Rotate API key](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-api-key-rotate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| [Campaigns](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaigns)                         | [List campaigns](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaigns) · [Retrieve a campaign](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id) · [Create a campaign](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaigns) · [Update a campaign](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-campaign-id) · [Clone a campaign](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-clone)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| [Campaign Resources](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-resources)       | [List program resources](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-resources) · [Create a program resource](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-resources) · [Update a program resource](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-campaign-id-resources-resourceid) · [Delete a program resource](https://docs.growsurf.com/developer-tools/rest-api/api-reference#delete-campaign-id-resources-resourceid) · [Create a program resource upload ticket](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-resource-upload-tickets)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| [Campaign Rewards](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-rewards)           | [List campaign rewards](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-reward-configs) · [Create a campaign reward](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-reward-configs) · [Update a campaign reward](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-campaign-id-reward-configs-campaignrewardid) · [Delete a campaign reward](https://docs.growsurf.com/developer-tools/rest-api/api-reference#delete-campaign-id-reward-configs-campaignrewardid)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| [Campaign Design](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-design)             | [Retrieve campaign design](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-design) · [Update campaign design](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-campaign-id-design)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| [Campaign Emails](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-emails)             | [Retrieve campaign emails](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-emails) · [Update campaign emails](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-campaign-id-emails)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| [Campaign Options](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-options)           | [Retrieve campaign options](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-options) · [Update campaign options](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-campaign-id-options)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| [Campaign Installation](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-installation) | [Retrieve campaign installation](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-installation) · [Update campaign installation](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-campaign-id-installation)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| [Campaign Integrations](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-integrations) | [List integrations](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-integrations)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| [Campaign Webhooks](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-webhooks)         | [List webhooks](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-webhooks) · [Create webhook](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-webhooks) · [Update webhook](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-campaign-id-webhooks-webhookid) · [Delete webhook](https://docs.growsurf.com/developer-tools/rest-api/api-reference#delete-campaign-id-webhooks-webhookid) · [Test webhook](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-webhooks-webhookid-test)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| [Participants](https://docs.growsurf.com/developer-tools/rest-api/api-reference#participants)                   | [List participants](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participants) · [List leaderboard participants](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-leaderboard) · [Add participant](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-participant) · [Retrieve participant](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participant-participantidoremail) · [Update participant](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-participant-participantidoremail) · [Delete participant](https://docs.growsurf.com/developer-tools/rest-api/api-reference#delete-campaign-id-participant-participantidoremail) · [Bulk delete participants](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-participants-bulk-delete) · [Trigger referral](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-participant-participantidoremail-ref) · [Cancel delayed referral trigger](https://docs.growsurf.com/developer-tools/rest-api/api-reference#delete-campaign-id-participant-participantidoremail-ref) · [Create mobile participant token](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-mobile-participant-token) · [Email participant](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-participant-participantidoremail-email) · [Retrieve participant analytics](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participant-participantidoremail-analytics) · [List participant activity logs](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participant-participantidoremail-activity-logs) · [Get a participant's payout destination status](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participant-participantidoremail-payout-destination) · [Request a payout destination confirmation](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-participant-participantidoremail-payout-destination-request-confirmation) |
| [Participant Rewards](https://docs.growsurf.com/developer-tools/rest-api/api-reference#participant-rewards)     | [List participant rewards](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participant-participantidoremail-rewards) · [Approve participant reward](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-reward-rewardid-approve) · [Fulfill participant reward](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-reward-rewardid-fulfill) · [Delete participant reward](https://docs.growsurf.com/developer-tools/rest-api/api-reference#delete-campaign-id-reward-rewardid)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| [Referrals and Invites](https://docs.growsurf.com/developer-tools/rest-api/api-reference#referrals-and-invites) | [List referrals and invites](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-referrals) · [List participant referrals and invites](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participant-participantidoremail-referrals) · [Send participant invites](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-participant-participantidoremail-invites)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| [Analytics](https://docs.growsurf.com/developer-tools/rest-api/api-reference#analytics)                         | [Retrieve campaign analytics](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-analytics) · [Retrieve campaign activation analytics](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-analytics-activation)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| [Affiliate Programs](https://docs.growsurf.com/developer-tools/rest-api/api-reference#affiliate-programs)       | [List affiliate applications](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-affiliate-applications) · [Get affiliate application](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-affiliate-applications-applicationid) · [Review affiliate application](https://docs.growsurf.com/developer-tools/rest-api/api-reference#patch-campaign-id-affiliate-applications-applicationid) · [List affiliate invites](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-affiliate-invites) · [Create affiliate invite](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-affiliate-invites) · [Revoke affiliate invite](https://docs.growsurf.com/developer-tools/rest-api/api-reference#delete-campaign-id-affiliate-invites-inviteid) · [Resend affiliate invite](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-affiliate-invites-inviteid-resend) · [Record affiliate transaction](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-participant-participantidoremail-transaction) · [Refund or amend affiliate transaction](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-participant-participantidoremail-transaction-refund) · [List participant commissions](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-commissions) · [List commissions for a participant](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participant-participantidoremail-commissions) · [Approve participant commission](https://docs.growsurf.com/developer-tools/rest-api/api-reference#post-campaign-id-commission-commissionid-approve) · [Delete participant commission](https://docs.growsurf.com/developer-tools/rest-api/api-reference#delete-campaign-id-commission-commissionid) · [List participant payouts](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-payouts) · [List payouts for a participant](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participant-participantidoremail-payouts)                                 |

***

## ACCOUNTS ↓

{% hint style="info" %}
Create a GrowSurf account and receive its first API key. This operation needs no authentication. If an AI assistant is creating the account for you, start with the prompts in [Build with AI](/build-with-ai.md).
{% endhint %}

## Create an account without an API key

> Creates a new GrowSurf account. This is the only endpoint that does not require an API key. Before calling it, an authorized account owner must review and approve GrowSurf's \[Terms of Service]\(<https://growsurf.com/terms>) and \[Privacy Policy]\(<https://growsurf.com/privacy>). The account starts a 14-day Business trial without a credit card. The response includes an API key for the new account, shown once in the response. The key is a secret: store it in a secret manager and do not put it in logs, screenshots, URLs, model context, analytics, or generated output. A lost key cannot be recovered through this API, so do not create an account here unless you can store the key somewhere that outlives the current conversation. If you cannot, ask the account owner to connect GrowSurf's hosted MCP server at \`<https://mcp.growsurf.com\\`> instead, which keeps the credential with your tool rather than in chat. The key is locked until the team owner's email address is verified: authenticated program and resource endpoints return a \`403\` with error code \`EMAIL\_NOT\_VERIFIED\_ERROR\` until then (resend the email via \`POST /team/owner/verification-email\`, then retry). Verification unlocks this same key — keep it and retry rather than requesting a replacement. A welcome email is sent to the address with the verification link and a set-password link for dashboard access. Accounts whose email is never verified are deleted automatically after 7 days. Separately, for security, the API key is replaced the first time the account owner signs in to the GrowSurf dashboard; email verification does not trigger that, and the previous key then returns a \`403\` with error code \`NOT\_AUTHORIZED\_ERROR\`. Some actions (such as emailing participants) additionally require GrowSurf to verify the team first. Calling this endpoint accepts those policies on the account holder's behalf.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Accounts","description":"Create a GrowSurf account and its initial API key."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[],"paths":{"/accounts":{"post":{"tags":["Accounts"],"operationId":"createAccount","summary":"Create an account without an API key","description":"Creates a new GrowSurf account. This is the only endpoint that does not require an API key. Before calling it, an authorized account owner must review and approve GrowSurf's [Terms of Service](https://growsurf.com/terms) and [Privacy Policy](https://growsurf.com/privacy). The account starts a 14-day Business trial without a credit card. The response includes an API key for the new account, shown once in the response. The key is a secret: store it in a secret manager and do not put it in logs, screenshots, URLs, model context, analytics, or generated output. A lost key cannot be recovered through this API, so do not create an account here unless you can store the key somewhere that outlives the current conversation. If you cannot, ask the account owner to connect GrowSurf's hosted MCP server at `https://mcp.growsurf.com` instead, which keeps the credential with your tool rather than in chat. The key is locked until the team owner's email address is verified: authenticated program and resource endpoints return a `403` with error code `EMAIL_NOT_VERIFIED_ERROR` until then (resend the email via `POST /team/owner/verification-email`, then retry). Verification unlocks this same key — keep it and retry rather than requesting a replacement. A welcome email is sent to the address with the verification link and a set-password link for dashboard access. Accounts whose email is never verified are deleted automatically after 7 days. Separately, for security, the API key is replaced the first time the account owner signs in to the GrowSurf dashboard; email verification does not trigger that, and the previous key then returns a `403` with error code `NOT_AUTHORIZED_ERROR`. Some actions (such as emailing participants) additionally require GrowSurf to verify the team first. Calling this endpoint accepts those policies on the account holder's behalf.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccountRequest"}}}},"responses":{"200":{"description":"Account created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccountResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"409":{"description":"The account could not be created with the supplied details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"schemas":{"CreateAccountRequest":{"type":"object","required":["email"],"additionalProperties":false,"properties":{"email":{"type":"string","format":"email","description":"The email address for the new GrowSurf account. Personal emails and disposable email addresses are not accepted."},"firstName":{"description":"First name for the new account owner.","type":"string","maxLength":255},"lastName":{"description":"Last name for the new account owner.","type":"string","maxLength":255},"company":{"description":"Company name for the new account.","type":"string","maxLength":255}}},"CreateAccountResponse":{"type":"object","required":["email","apiKey","verificationStatus"],"properties":{"email":{"description":"Email address for the new account.","type":"string","format":"email"},"apiKey":{"type":"string","description":"An API key for the new account. Use it as the `Bearer` token on subsequent requests. It is shown once and locked (`403` `EMAIL_NOT_VERIFIED_ERROR`) until the account's email is verified; verification unlocks this same key, so keep it and retry. It is replaced only when the account owner first signs in to the GrowSurf dashboard."},"verificationStatus":{"type":"string","enum":["NOT_REQUESTED","REQUESTED","VERIFIED"],"description":"GrowSurf team verification state. `VERIFIED` is required before a program can send participant emails."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

***

## TEAMS ↓

{% hint style="info" %}
Read or update the team bound to your API key or OAuth connection, request GrowSurf verification, resend the team owner's verification email, or rotate the API key making a direct API request. New API keys and OAuth connections act on one team. API key rotation is not available through MCP.
{% endhint %}

## Retrieve team

> Retrieves the team bound to the API key or OAuth connection. \`verificationStatus\` is \`VERIFIED\` once GrowSurf has verified the team, which is required before a program can send participant emails.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Teams","description":"The team bound to an API key or OAuth connection."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"schemas":{"Team":{"type":"object","description":"The team bound to the API key or OAuth connection.","required":["name","verificationStatus","verificationRequestedAt"],"properties":{"name":{"description":"The team's display name.","type":"string"},"verificationStatus":{"type":"string","enum":["NOT_REQUESTED","REQUESTED","VERIFIED"],"readOnly":true,"description":"GrowSurf team verification state. `VERIFIED` is required before a program can send participant emails."},"verificationRequestedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"When team verification was last requested, as a Unix timestamp in milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/team":{"get":{"tags":["Teams"],"operationId":"getTeam","summary":"Retrieve team","description":"Retrieves the team bound to the API key or OAuth connection. `verificationStatus` is `VERIFIED` once GrowSurf has verified the team, which is required before a program can send participant emails.","responses":{"200":{"description":"Team returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update team

> Updates the name of the team bound to the API key or OAuth connection. Any other property is rejected with a \`400\`. Personal profiles, billing, and team ownership are not editable here.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Teams","description":"The team bound to an API key or OAuth connection."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"schemas":{"UpdateTeamRequest":{"type":"object","description":"The Team fields you can update. Any property not listed here is rejected with a `400`.","additionalProperties":false,"required":["name"],"properties":{"name":{"description":"The team's display name.","type":"string","minLength":1,"maxLength":255}}},"Team":{"type":"object","description":"The team bound to the API key or OAuth connection.","required":["name","verificationStatus","verificationRequestedAt"],"properties":{"name":{"description":"The team's display name.","type":"string"},"verificationStatus":{"type":"string","enum":["NOT_REQUESTED","REQUESTED","VERIFIED"],"readOnly":true,"description":"GrowSurf team verification state. `VERIFIED` is required before a program can send participant emails."},"verificationRequestedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"When team verification was last requested, as a Unix timestamp in milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/team":{"patch":{"tags":["Teams"],"operationId":"updateTeam","summary":"Update team","description":"Updates the name of the team bound to the API key or OAuth connection. Any other property is rejected with a `400`. Personal profiles, billing, and team ownership are not editable here.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTeamRequest"}}}},"responses":{"200":{"description":"Updated team returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Request team verification

> Requests GrowSurf to verify the bound team (required before a program can email its participants). Idempotent — calling it again while a request is pending does not create a duplicate. Returns the team with its updated \`verificationStatus\`.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Teams","description":"The team bound to an API key or OAuth connection."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"schemas":{"Team":{"type":"object","description":"The team bound to the API key or OAuth connection.","required":["name","verificationStatus","verificationRequestedAt"],"properties":{"name":{"description":"The team's display name.","type":"string"},"verificationStatus":{"type":"string","enum":["NOT_REQUESTED","REQUESTED","VERIFIED"],"readOnly":true,"description":"GrowSurf team verification state. `VERIFIED` is required before a program can send participant emails."},"verificationRequestedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"When team verification was last requested, as a Unix timestamp in milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/team/verification-request":{"post":{"tags":["Teams"],"operationId":"requestTeamVerification","summary":"Request team verification","description":"Requests GrowSurf to verify the bound team (required before a program can email its participants). Idempotent — calling it again while a request is pending does not create a duplicate. Returns the team with its updated `verificationStatus`.","responses":{"200":{"description":"Verification request recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Resend team owner verification email

> Resends the email-verification message to the owner of the account the API key belongs to. This is the recovery path for a \`403\` with error code \`EMAIL\_NOT\_VERIFIED\_ERROR\`, so it stays callable with any GrowSurf API key while the rest of the API is locked. The response never reveals the owner's email address. A \`200\` with \`status: SENT\` is returned only when an email was actually dispatched. Returns \`400\` if the email is already verified, and \`429\` if a verification email was sent too recently — wait a moment, then retry.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Teams","description":"The team bound to an API key or OAuth connection."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"schemas":{"VerificationEmailResponse":{"type":"object","required":["success","status"],"properties":{"success":{"description":"Whether the verification email request was accepted.","type":"boolean"},"status":{"description":"Status of the verification email request.","type":"string","enum":["SENT"]}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/team/owner/verification-email":{"post":{"tags":["Teams"],"operationId":"resendTeamOwnerVerificationEmail","summary":"Resend team owner verification email","description":"Resends the email-verification message to the owner of the account the API key belongs to. This is the recovery path for a `403` with error code `EMAIL_NOT_VERIFIED_ERROR`, so it stays callable with any GrowSurf API key while the rest of the API is locked. The response never reveals the owner's email address. A `200` with `status: SENT` is returned only when an email was actually dispatched. Returns `400` if the email is already verified, and `429` if a verification email was sent too recently — wait a moment, then retry.","responses":{"200":{"description":"Verification email sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationEmailResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Rotate API key

> Generates a new API key and makes the key used on this request stop working when rotation succeeds. Send a unique, random \`Idempotency-Key\`. If the response is interrupted, immediately retry with the original API key and the same \`Idempotency-Key\` to receive the same new key. Update every integration that used the old key. The team owner is notified by email whenever the key is rotated. GrowSurf SDKs generate the idempotency key automatically. This endpoint accepts an API key with \`api\_key:rotate\`. If this scope is unavailable, rotate the key in the authenticated dashboard instead. This operation is available only through the REST API or a GrowSurf API SDK, not through MCP.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Teams","description":"The team bound to an API key or OAuth connection."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"schemas":{"RotateApiKeyResponse":{"type":"object","required":["apiKey"],"properties":{"apiKey":{"type":"string","description":"The new API key. Store it now; the key used for rotation stops working immediately."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api-key/rotate":{"post":{"tags":["Teams"],"operationId":"rotateApiKey","summary":"Rotate API key","description":"Generates a new API key and makes the key used on this request stop working when rotation succeeds. Send a unique, random `Idempotency-Key`. If the response is interrupted, immediately retry with the original API key and the same `Idempotency-Key` to receive the same new key. Update every integration that used the old key. The team owner is notified by email whenever the key is rotated. GrowSurf SDKs generate the idempotency key automatically. This endpoint accepts an API key with `api_key:rotate`. If this scope is unavailable, rotate the key in the authenticated dashboard instead. This operation is available only through the REST API or a GrowSurf API SDK, not through MCP.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"A unique, random value for this rotation request. Reuse this value only when retrying the same request with the original API key.","schema":{"type":"string","minLength":8,"maxLength":255,"pattern":"^[A-Za-z0-9._:-]+$"}}],"responses":{"200":{"description":"The new API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateApiKeyResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## CAMPAIGNS ↓

{% hint style="info" %}
Create and update a program's identity and lifecycle here (`name`, `companyName`, `companyLogoImageUrl`, `status`, and — on create — `type`, `currencyISO`, and inline `rewards`). A program's currency (`currencyISO`) is chosen when the program is created (defaults to `USD`) and can't be changed afterward — sending it to the update endpoint returns a `400`. A program's design, emails, options, and installation are **not** accepted on create or update and will return a `400`. Edit those — and the program's rewards — through the [Campaign Rewards](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-rewards), [Campaign Design](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-design), [Campaign Emails](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-emails), [Campaign Options](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-options), and [Campaign Installation](https://docs.growsurf.com/developer-tools/rest-api/api-reference#campaign-installation) API endpoints.
{% endhint %}

## List campaigns

> Retrieves a list of your programs. Deleted programs are not returned.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"schemas":{"CampaignListResponse":{"type":"object","required":["campaigns"],"properties":{"campaigns":{"description":"Programs returned for the API key's bound team.","type":"array","items":{"$ref":"#/components/schemas/Campaign"}}}},"Campaign":{"type":"object","description":"Detailed information about a GrowSurf program.","required":["id","name","type","referralCount","participantCount","impressionCount","inviteCount","winnerCount","status","rewards"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the program."},"name":{"type":"string","description":"The program name (internal only, never exposed to participants)."},"type":{"$ref":"#/components/schemas/CampaignType","description":"The program type."},"referralCount":{"type":"integer","readOnly":true,"description":"The total referral count."},"participantCount":{"type":"integer","readOnly":true,"description":"The total participant count."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions — the collective number of times participants' unique referral links have been viewed."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites sent by participants."},"winnerCount":{"type":"integer","readOnly":true,"description":"The total number of winners — all participants with at least one approved reward (includes referrers and referred friends)."},"currencyISO":{"type":["string","null"],"minLength":3,"maxLength":3,"description":"The program currency as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) code (e.g., `USD`, `GBP`)."},"status":{"$ref":"#/components/schemas/CampaignStatus","description":"The program status."},"rewards":{"type":"array","items":{"$ref":"#/components/schemas/Reward"},"description":"The list of rewards associated with the program."}}},"CampaignType":{"type":"string","enum":["REFERRAL","AFFILIATE"]},"CampaignStatus":{"type":"string","description":"Current program lifecycle status.","enum":["DRAFT","PENDING","IN_PROGRESS","COMPLETE","CANCELLED","DELETED"]},"Reward":{"type":"object","description":"A single campaign reward (also known as a `CampaignReward`). This is different from a `ParticipantReward`, which is a reward earned by a participant.","required":["id","type","isUnlimited","metadata"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the campaign reward. You can find this ID from *Program Editor > 1. Rewards* and clicking the reward."},"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns this referral reward. Present for SINGLE_SIDED, DOUBLE_SIDED, and MILESTONE rewards. Legacy rewards return CONVERSION."},"title":{"type":["string","null"],"description":"The reward title (internal only, never exposed to participants)."},"isVisible":{"type":"boolean","description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"description":{"type":["string","null"],"description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (only applicable for double-sided reward types)."},"referredRewardUpfront":{"type":"boolean","description":"Only applies to double-sided rewards. When `true`, the referred friend's reward is delivered upfront as a discount and no `ParticipantReward` is created for them when the referral triggers."},"isUnlimited":{"type":"boolean","description":"`true` if this reward can be earned by a single participant an unlimited number of times."},"limit":{"type":["integer","null"],"description":"The number of times a participant can earn this reward (overridden when `isUnlimited` is `true`). `-1` represents an unlimited reward in REST responses."},"conversionsRequired":{"type":["integer","null"],"description":"The number of referrals a participant must make to earn this reward."},"numberOfWinners":{"type":["integer","null"],"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total."},"limitDuration":{"type":["string","null"],"enum":["IN_TOTAL","PER_MONTH","PER_YEAR",null],"description":"Whether the reward can be earned in total, on a monthly basis, or on a yearly basis."},"imageUrl":{"type":["string","null"],"description":"The reward image URL."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"order":{"type":["integer","null"],"description":"If there are multiple rewards, the order in which the reward should be displayed. `null` by default until set within the Design step of the program editor."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text displayed in front of a participant's referral count for UI purposes (e.g., \"You are only\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text displayed after a participant's referral count for UI purposes (e.g., \"referrals away from receiving a nice reward!\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"The reward metadata."},"commissionStructure":{"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The reward commission structure. Present only for affiliate programs."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). `null` when no valuation is set. For commission rewards, both fields are `null`; GrowSurf classifies the actual commission amount as `NONEMPLOYEE_SERVICES`."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. `null` when no valuation is set. Commission rewards have no referred-friend side, so both fields are `null`."}}},"RewardType":{"type":"string","enum":["SINGLE_SIDED","DOUBLE_SIDED","MILESTONE","LEADERBOARD","AFFILIATE"]},"RewardEvent":{"type":"string","enum":["LEAD","CONVERSION"],"description":"The referral event that earns the reward."},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"RewardTaxValuation":{"type":"object","additionalProperties":false,"description":"Tax valuation settings for a reward. Only relevant when the program collects tax documentation.","properties":{"fairMarketValueUSD":{"type":["number","null"],"minimum":0,"maximum":90071992547409.9,"description":"Manual fair-market value in USD (major units) used as the fallback when the reward value cannot be resolved automatically. `null` = no manual value."},"taxCharacter":{"type":["string","null"],"enum":["NONEMPLOYEE_SERVICES","PRIZE_OR_AWARD","PURCHASE_REBATE","OTHER_INCOME","REVIEW_REQUIRED",null],"description":"The reason the recipient earns this reward. `null` inherits the program's confirmed tax treatment for configurable non-commission rewards. Commission rewards always use `NONEMPLOYEE_SERVICES`."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaigns":{"get":{"tags":["Campaigns"],"operationId":"listCampaigns","summary":"List campaigns","description":"Retrieves a list of your programs. Deleted programs are not returned.","responses":{"200":{"description":"Campaigns returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignListResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Retrieve a campaign

> Retrieves a program for the given program ID.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"Campaign":{"type":"object","description":"Detailed information about a GrowSurf program.","required":["id","name","type","referralCount","participantCount","impressionCount","inviteCount","winnerCount","status","rewards"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the program."},"name":{"type":"string","description":"The program name (internal only, never exposed to participants)."},"type":{"$ref":"#/components/schemas/CampaignType","description":"The program type."},"referralCount":{"type":"integer","readOnly":true,"description":"The total referral count."},"participantCount":{"type":"integer","readOnly":true,"description":"The total participant count."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions — the collective number of times participants' unique referral links have been viewed."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites sent by participants."},"winnerCount":{"type":"integer","readOnly":true,"description":"The total number of winners — all participants with at least one approved reward (includes referrers and referred friends)."},"currencyISO":{"type":["string","null"],"minLength":3,"maxLength":3,"description":"The program currency as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) code (e.g., `USD`, `GBP`)."},"status":{"$ref":"#/components/schemas/CampaignStatus","description":"The program status."},"rewards":{"type":"array","items":{"$ref":"#/components/schemas/Reward"},"description":"The list of rewards associated with the program."}}},"CampaignType":{"type":"string","enum":["REFERRAL","AFFILIATE"]},"CampaignStatus":{"type":"string","description":"Current program lifecycle status.","enum":["DRAFT","PENDING","IN_PROGRESS","COMPLETE","CANCELLED","DELETED"]},"Reward":{"type":"object","description":"A single campaign reward (also known as a `CampaignReward`). This is different from a `ParticipantReward`, which is a reward earned by a participant.","required":["id","type","isUnlimited","metadata"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the campaign reward. You can find this ID from *Program Editor > 1. Rewards* and clicking the reward."},"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns this referral reward. Present for SINGLE_SIDED, DOUBLE_SIDED, and MILESTONE rewards. Legacy rewards return CONVERSION."},"title":{"type":["string","null"],"description":"The reward title (internal only, never exposed to participants)."},"isVisible":{"type":"boolean","description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"description":{"type":["string","null"],"description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (only applicable for double-sided reward types)."},"referredRewardUpfront":{"type":"boolean","description":"Only applies to double-sided rewards. When `true`, the referred friend's reward is delivered upfront as a discount and no `ParticipantReward` is created for them when the referral triggers."},"isUnlimited":{"type":"boolean","description":"`true` if this reward can be earned by a single participant an unlimited number of times."},"limit":{"type":["integer","null"],"description":"The number of times a participant can earn this reward (overridden when `isUnlimited` is `true`). `-1` represents an unlimited reward in REST responses."},"conversionsRequired":{"type":["integer","null"],"description":"The number of referrals a participant must make to earn this reward."},"numberOfWinners":{"type":["integer","null"],"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total."},"limitDuration":{"type":["string","null"],"enum":["IN_TOTAL","PER_MONTH","PER_YEAR",null],"description":"Whether the reward can be earned in total, on a monthly basis, or on a yearly basis."},"imageUrl":{"type":["string","null"],"description":"The reward image URL."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"order":{"type":["integer","null"],"description":"If there are multiple rewards, the order in which the reward should be displayed. `null` by default until set within the Design step of the program editor."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text displayed in front of a participant's referral count for UI purposes (e.g., \"You are only\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text displayed after a participant's referral count for UI purposes (e.g., \"referrals away from receiving a nice reward!\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"The reward metadata."},"commissionStructure":{"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The reward commission structure. Present only for affiliate programs."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). `null` when no valuation is set. For commission rewards, both fields are `null`; GrowSurf classifies the actual commission amount as `NONEMPLOYEE_SERVICES`."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. `null` when no valuation is set. Commission rewards have no referred-friend side, so both fields are `null`."}}},"RewardType":{"type":"string","enum":["SINGLE_SIDED","DOUBLE_SIDED","MILESTONE","LEADERBOARD","AFFILIATE"]},"RewardEvent":{"type":"string","enum":["LEAD","CONVERSION"],"description":"The referral event that earns the reward."},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"RewardTaxValuation":{"type":"object","additionalProperties":false,"description":"Tax valuation settings for a reward. Only relevant when the program collects tax documentation.","properties":{"fairMarketValueUSD":{"type":["number","null"],"minimum":0,"maximum":90071992547409.9,"description":"Manual fair-market value in USD (major units) used as the fallback when the reward value cannot be resolved automatically. `null` = no manual value."},"taxCharacter":{"type":["string","null"],"enum":["NONEMPLOYEE_SERVICES","PRIZE_OR_AWARD","PURCHASE_REBATE","OTHER_INCOME","REVIEW_REQUIRED",null],"description":"The reason the recipient earns this reward. `null` inherits the program's confirmed tax treatment for configurable non-commission rewards. Commission rewards always use `NONEMPLOYEE_SERVICES`."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}":{"get":{"tags":["Campaigns"],"operationId":"retrieveCampaign","summary":"Retrieve a campaign","description":"Retrieves a program for the given program ID.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Campaign returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Create a campaign

> Creates a new program, plus any optional campaign rewards. The new program is created in \`DRAFT\` status and owned by the API key's bound team.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"schemas":{"CampaignCreateRequest":{"type":"object","description":"Request body for creating a program. Only `type` is required; everything else is server-defaulted. Editor-tab configuration (design, emails, options, installation) is NOT accepted here — configure it via the config sub-resources (e.g. `PATCH /campaign/{id}/options`) after the program is created.","required":["type"],"properties":{"type":{"type":"string","enum":["REFERRAL","AFFILIATE"],"description":"The program type. Immutable after creation."},"name":{"type":"string","description":"The program name (internal only, never exposed to participants). Defaults to a generated name (a friendly label plus the creation date) that you can rename at any time."},"companyName":{"type":"string","description":"The company name shown to participants across the program's emails and referral portal. When omitted, it defaults to the `company` name set when the account was created (see `POST /accounts`), with common legal suffixes removed."},"companyLogoImageUrl":{"type":"string","description":"URL of the company logo shown to participants. When omitted, it is auto-populated from your company's domain (and the program theme is seeded with your brand colors). Pass a value to override the logo; the returned campaign reflects a GrowSurf-hosted URL that may differ from the one you supplied."},"currencyISO":{"type":"string","default":"USD","description":"ISO 4217 currency code. Defaults to `USD`. Chosen when the program is created and immutable afterward — it cannot be changed on update."},"goal":{"type":"string","enum":["CUSTOMERS","USERS","SUBSCRIBERS","WAITLIST","B2B_SAAS_SELF_SERVICE","B2B_SAAS_ENTERPRISE","B2C_SUBSCRIPTIONS","FINANCIAL_SERVICES","ONLINE_EDUCATION","ONLINE_INSURANCE"],"description":"What the program is for, which seeds share settings that suit that audience. Set it when you create the program; it is not accepted on update.\n\nPrograms that sell to businesses (`CUSTOMERS`, `USERS`, `B2B_SAAS_SELF_SERVICE`, `B2B_SAAS_ENTERPRISE`) start with the LinkedIn share button visible. Consumer, financial, education, insurance, newsletter, and waitlist programs (`B2C_SUBSCRIPTIONS`, `FINANCIAL_SERVICES`, `ONLINE_EDUCATION`, `ONLINE_INSURANCE`, `SUBSCRIBERS`, `WAITLIST`) start with it hidden. Omit `goal` and every share button keeps its standard default. Change any of it afterward with `PATCH /campaign/{id}/design`."},"rewards":{"type":"array","items":{"$ref":"#/components/schemas/RewardCreateRequest"},"description":"Optional inline rewards to create with the program."}}},"RewardCreateRequest":{"type":"object","description":"Request body for creating a campaign reward. `type` is required and must be compatible with the program type. AFFILIATE rewards additionally require `commissionStructure` (with `amount` for a `FIXED` commission or `percent` for a `PERCENT` commission).","required":["type"],"allOf":[{"$ref":"#/components/schemas/RewardWritableFields"},{"type":"object","properties":{"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type. Immutable after creation."}}},{"if":{"required":["commissionStructure"],"properties":{"commissionStructure":{"required":["event"],"properties":{"event":{"enum":["CLICK","LEAD"]}}}}},"then":{"properties":{"commissionStructure":{"required":["amount"],"properties":{"amount":{"type":"integer","minimum":1},"type":{"enum":["FIXED",null]}}}}}}]},"RewardWritableFields":{"type":"object","description":"The writable fields shared by the create and update campaign reward requests.","properties":{"title":{"type":"string","description":"The reward title (internal only, never exposed to participants)."},"description":{"type":"string","description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (double-sided rewards only)."},"imageUrl":{"type":["string","null"],"description":"An image URL for the reward."},"isVisible":{"type":"boolean","default":true,"description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"isUnlimited":{"type":"boolean","default":true,"description":"Whether the reward can be earned an unlimited number of times. Defaults to `true`, except `MILESTONE` rewards, which can only be earned once."},"referredRewardUpfront":{"type":"boolean","default":false,"description":"For double-sided rewards, deliver the referred friend's reward upfront as a discount."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns a SINGLE_SIDED, DOUBLE_SIDED, or MILESTONE referral reward. Defaults to CONVERSION. LEAD requires the program's referralTrigger to be CUSTOM."},"limit":{"type":"integer","minimum":0,"default":1,"description":"The number of times a participant can earn the reward (overridden by `isUnlimited`)."},"conversionsRequired":{"type":"integer","minimum":1,"default":1,"description":"The number of referrals required to earn the reward."},"numberOfWinners":{"type":"integer","minimum":0,"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total. A `LEADERBOARD` reward that omits it defaults to `3`."},"order":{"type":"integer","description":"The display order of the reward."},"limitDuration":{"type":"string","enum":["IN_TOTAL","PER_MONTH","PER_YEAR"],"default":"IN_TOTAL","description":"The window over which `limit` applies."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text shown before a participant's referral count in milestone progress copy (e.g. `You are only`). Applies to `MILESTONE` rewards."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text shown after a participant's referral count in milestone progress copy (e.g. `referrals away from your next reward!`). Applies to `MILESTONE` rewards."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook when this reward is earned. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same behavior and caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"Custom key/value metadata (single-level; keys are camelCased on save; values are stored as strings). Sending `metadata` REPLACES the stored object. Campaign copy can reference a key via `{{campaignReward['<rewardId>']['<key>']}}` tokens: renaming a key (same value, new key) automatically rewrites those references; removing a key that campaign copy still references returns a `409` listing the referencing fields."},"commissionStructure":{"$ref":"#/components/schemas/CommissionStructure","description":"The affiliate commission structure (AFFILIATE rewards only). REQUIRED when creating an AFFILIATE reward — include `amount` (and optionally `amountISO`) for a `FIXED` commission, or `percent` for a `PERCENT` commission."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). Used by tax documentation / 1099 reporting. `null` when no valuation is set. Commission rewards always use their actual cash amount and the `NONEMPLOYEE_SERVICES` tax character, so GrowSurf clears these settings for that reward type."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. Set its treatment independently when the two sides are earned for different reasons. `null` when no valuation is set. Commission rewards have no referred-friend side, so GrowSurf clears these settings for that reward type."}}},"RewardEvent":{"type":"string","enum":["LEAD","CONVERSION"],"description":"The referral event that earns the reward."},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"RewardTaxValuation":{"type":"object","additionalProperties":false,"description":"Tax valuation settings for a reward. Only relevant when the program collects tax documentation.","properties":{"fairMarketValueUSD":{"type":["number","null"],"minimum":0,"maximum":90071992547409.9,"description":"Manual fair-market value in USD (major units) used as the fallback when the reward value cannot be resolved automatically. `null` = no manual value."},"taxCharacter":{"type":["string","null"],"enum":["NONEMPLOYEE_SERVICES","PRIZE_OR_AWARD","PURCHASE_REBATE","OTHER_INCOME","REVIEW_REQUIRED",null],"description":"The reason the recipient earns this reward. `null` inherits the program's confirmed tax treatment for configurable non-commission rewards. Commission rewards always use `NONEMPLOYEE_SERVICES`."}}},"RewardType":{"type":"string","enum":["SINGLE_SIDED","DOUBLE_SIDED","MILESTONE","LEADERBOARD","AFFILIATE"]},"Campaign":{"type":"object","description":"Detailed information about a GrowSurf program.","required":["id","name","type","referralCount","participantCount","impressionCount","inviteCount","winnerCount","status","rewards"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the program."},"name":{"type":"string","description":"The program name (internal only, never exposed to participants)."},"type":{"$ref":"#/components/schemas/CampaignType","description":"The program type."},"referralCount":{"type":"integer","readOnly":true,"description":"The total referral count."},"participantCount":{"type":"integer","readOnly":true,"description":"The total participant count."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions — the collective number of times participants' unique referral links have been viewed."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites sent by participants."},"winnerCount":{"type":"integer","readOnly":true,"description":"The total number of winners — all participants with at least one approved reward (includes referrers and referred friends)."},"currencyISO":{"type":["string","null"],"minLength":3,"maxLength":3,"description":"The program currency as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) code (e.g., `USD`, `GBP`)."},"status":{"$ref":"#/components/schemas/CampaignStatus","description":"The program status."},"rewards":{"type":"array","items":{"$ref":"#/components/schemas/Reward"},"description":"The list of rewards associated with the program."}}},"CampaignType":{"type":"string","enum":["REFERRAL","AFFILIATE"]},"CampaignStatus":{"type":"string","description":"Current program lifecycle status.","enum":["DRAFT","PENDING","IN_PROGRESS","COMPLETE","CANCELLED","DELETED"]},"Reward":{"type":"object","description":"A single campaign reward (also known as a `CampaignReward`). This is different from a `ParticipantReward`, which is a reward earned by a participant.","required":["id","type","isUnlimited","metadata"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the campaign reward. You can find this ID from *Program Editor > 1. Rewards* and clicking the reward."},"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns this referral reward. Present for SINGLE_SIDED, DOUBLE_SIDED, and MILESTONE rewards. Legacy rewards return CONVERSION."},"title":{"type":["string","null"],"description":"The reward title (internal only, never exposed to participants)."},"isVisible":{"type":"boolean","description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"description":{"type":["string","null"],"description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (only applicable for double-sided reward types)."},"referredRewardUpfront":{"type":"boolean","description":"Only applies to double-sided rewards. When `true`, the referred friend's reward is delivered upfront as a discount and no `ParticipantReward` is created for them when the referral triggers."},"isUnlimited":{"type":"boolean","description":"`true` if this reward can be earned by a single participant an unlimited number of times."},"limit":{"type":["integer","null"],"description":"The number of times a participant can earn this reward (overridden when `isUnlimited` is `true`). `-1` represents an unlimited reward in REST responses."},"conversionsRequired":{"type":["integer","null"],"description":"The number of referrals a participant must make to earn this reward."},"numberOfWinners":{"type":["integer","null"],"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total."},"limitDuration":{"type":["string","null"],"enum":["IN_TOTAL","PER_MONTH","PER_YEAR",null],"description":"Whether the reward can be earned in total, on a monthly basis, or on a yearly basis."},"imageUrl":{"type":["string","null"],"description":"The reward image URL."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"order":{"type":["integer","null"],"description":"If there are multiple rewards, the order in which the reward should be displayed. `null` by default until set within the Design step of the program editor."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text displayed in front of a participant's referral count for UI purposes (e.g., \"You are only\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text displayed after a participant's referral count for UI purposes (e.g., \"referrals away from receiving a nice reward!\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"The reward metadata."},"commissionStructure":{"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The reward commission structure. Present only for affiliate programs."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). `null` when no valuation is set. For commission rewards, both fields are `null`; GrowSurf classifies the actual commission amount as `NONEMPLOYEE_SERVICES`."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. `null` when no valuation is set. Commission rewards have no referred-friend side, so both fields are `null`."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaigns":{"post":{"tags":["Campaigns"],"operationId":"createCampaign","summary":"Create a campaign","description":"Creates a new program, plus any optional campaign rewards. The new program is created in `DRAFT` status and owned by the API key's bound team.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreateRequest"}}}},"responses":{"200":{"description":"Campaign created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"description":"This action isn't available for your account yet (it needs an active subscription or a payment method on file), or the program limit was reached."},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update a campaign

> Updates a program's identity and lifecycle. Only the fields you send are changed. \`type\`, \`urlId\`, and \`currencyISO\` are immutable. Editor-tab configuration (design, emails, options, installation) is edited via the dedicated config sub-resources, not here. The program cannot be deleted via this endpoint.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignUpdateRequest":{"type":"object","description":"Request body for updating a program's identity and lifecycle. Only the fields you send are changed. `type`, `urlId`, and `currencyISO` are immutable — currency is chosen once when the program is created and cannot be changed here (sending it returns a `400`). Editor-tab configuration (design, emails, options, installation) is edited via the dedicated config sub-resources (e.g. `PATCH /campaign/{id}/emails`), not here.","properties":{"name":{"type":"string","description":"The program name (internal only, never exposed to participants)."},"companyName":{"type":"string","description":"The company name shown to participants across the program's emails and referral portal."},"companyLogoImageUrl":{"type":"string","description":"URL of the company logo shown to participants. The returned campaign reflects a GrowSurf-hosted URL that may differ from the one you supplied."},"status":{"type":"string","enum":["IN_PROGRESS","COMPLETE"],"description":"The requested program status. Only the dashboard's lifecycle moves are accepted: `IN_PROGRESS` publishes a `DRAFT` program or resumes a `COMPLETE`/`CANCELLED` one; `COMPLETE` ends an `IN_PROGRESS` program. Any other target (including `DRAFT`, `PENDING`, `CANCELLED`, and `DELETED`) returns a `400`."}}},"Campaign":{"type":"object","description":"Detailed information about a GrowSurf program.","required":["id","name","type","referralCount","participantCount","impressionCount","inviteCount","winnerCount","status","rewards"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the program."},"name":{"type":"string","description":"The program name (internal only, never exposed to participants)."},"type":{"$ref":"#/components/schemas/CampaignType","description":"The program type."},"referralCount":{"type":"integer","readOnly":true,"description":"The total referral count."},"participantCount":{"type":"integer","readOnly":true,"description":"The total participant count."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions — the collective number of times participants' unique referral links have been viewed."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites sent by participants."},"winnerCount":{"type":"integer","readOnly":true,"description":"The total number of winners — all participants with at least one approved reward (includes referrers and referred friends)."},"currencyISO":{"type":["string","null"],"minLength":3,"maxLength":3,"description":"The program currency as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) code (e.g., `USD`, `GBP`)."},"status":{"$ref":"#/components/schemas/CampaignStatus","description":"The program status."},"rewards":{"type":"array","items":{"$ref":"#/components/schemas/Reward"},"description":"The list of rewards associated with the program."}}},"CampaignType":{"type":"string","enum":["REFERRAL","AFFILIATE"]},"CampaignStatus":{"type":"string","description":"Current program lifecycle status.","enum":["DRAFT","PENDING","IN_PROGRESS","COMPLETE","CANCELLED","DELETED"]},"Reward":{"type":"object","description":"A single campaign reward (also known as a `CampaignReward`). This is different from a `ParticipantReward`, which is a reward earned by a participant.","required":["id","type","isUnlimited","metadata"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the campaign reward. You can find this ID from *Program Editor > 1. Rewards* and clicking the reward."},"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns this referral reward. Present for SINGLE_SIDED, DOUBLE_SIDED, and MILESTONE rewards. Legacy rewards return CONVERSION."},"title":{"type":["string","null"],"description":"The reward title (internal only, never exposed to participants)."},"isVisible":{"type":"boolean","description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"description":{"type":["string","null"],"description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (only applicable for double-sided reward types)."},"referredRewardUpfront":{"type":"boolean","description":"Only applies to double-sided rewards. When `true`, the referred friend's reward is delivered upfront as a discount and no `ParticipantReward` is created for them when the referral triggers."},"isUnlimited":{"type":"boolean","description":"`true` if this reward can be earned by a single participant an unlimited number of times."},"limit":{"type":["integer","null"],"description":"The number of times a participant can earn this reward (overridden when `isUnlimited` is `true`). `-1` represents an unlimited reward in REST responses."},"conversionsRequired":{"type":["integer","null"],"description":"The number of referrals a participant must make to earn this reward."},"numberOfWinners":{"type":["integer","null"],"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total."},"limitDuration":{"type":["string","null"],"enum":["IN_TOTAL","PER_MONTH","PER_YEAR",null],"description":"Whether the reward can be earned in total, on a monthly basis, or on a yearly basis."},"imageUrl":{"type":["string","null"],"description":"The reward image URL."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"order":{"type":["integer","null"],"description":"If there are multiple rewards, the order in which the reward should be displayed. `null` by default until set within the Design step of the program editor."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text displayed in front of a participant's referral count for UI purposes (e.g., \"You are only\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text displayed after a participant's referral count for UI purposes (e.g., \"referrals away from receiving a nice reward!\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"The reward metadata."},"commissionStructure":{"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The reward commission structure. Present only for affiliate programs."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). `null` when no valuation is set. For commission rewards, both fields are `null`; GrowSurf classifies the actual commission amount as `NONEMPLOYEE_SERVICES`."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. `null` when no valuation is set. Commission rewards have no referred-friend side, so both fields are `null`."}}},"RewardType":{"type":"string","enum":["SINGLE_SIDED","DOUBLE_SIDED","MILESTONE","LEADERBOARD","AFFILIATE"]},"RewardEvent":{"type":"string","enum":["LEAD","CONVERSION"],"description":"The referral event that earns the reward."},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"RewardTaxValuation":{"type":"object","additionalProperties":false,"description":"Tax valuation settings for a reward. Only relevant when the program collects tax documentation.","properties":{"fairMarketValueUSD":{"type":["number","null"],"minimum":0,"maximum":90071992547409.9,"description":"Manual fair-market value in USD (major units) used as the fallback when the reward value cannot be resolved automatically. `null` = no manual value."},"taxCharacter":{"type":["string","null"],"enum":["NONEMPLOYEE_SERVICES","PRIZE_OR_AWARD","PURCHASE_REBATE","OTHER_INCOME","REVIEW_REQUIRED",null],"description":"The reason the recipient earns this reward. `null` inherits the program's confirmed tax treatment for configurable non-commission rewards. Commission rewards always use `NONEMPLOYEE_SERVICES`."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}":{"patch":{"tags":["Campaigns"],"operationId":"updateCampaign","summary":"Update a campaign","description":"Updates a program's identity and lifecycle. Only the fields you send are changed. `type`, `urlId`, and `currencyISO` are immutable. Editor-tab configuration (design, emails, options, installation) is edited via the dedicated config sub-resources, not here. The program cannot be deleted via this endpoint.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignUpdateRequest"}}}},"responses":{"200":{"description":"Campaign updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Clone a campaign

> Clones an existing program into a new \`DRAFT\` program. Integrations and credentials are not copied; active rewards are cloned.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"Campaign":{"type":"object","description":"Detailed information about a GrowSurf program.","required":["id","name","type","referralCount","participantCount","impressionCount","inviteCount","winnerCount","status","rewards"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the program."},"name":{"type":"string","description":"The program name (internal only, never exposed to participants)."},"type":{"$ref":"#/components/schemas/CampaignType","description":"The program type."},"referralCount":{"type":"integer","readOnly":true,"description":"The total referral count."},"participantCount":{"type":"integer","readOnly":true,"description":"The total participant count."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions — the collective number of times participants' unique referral links have been viewed."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites sent by participants."},"winnerCount":{"type":"integer","readOnly":true,"description":"The total number of winners — all participants with at least one approved reward (includes referrers and referred friends)."},"currencyISO":{"type":["string","null"],"minLength":3,"maxLength":3,"description":"The program currency as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) code (e.g., `USD`, `GBP`)."},"status":{"$ref":"#/components/schemas/CampaignStatus","description":"The program status."},"rewards":{"type":"array","items":{"$ref":"#/components/schemas/Reward"},"description":"The list of rewards associated with the program."}}},"CampaignType":{"type":"string","enum":["REFERRAL","AFFILIATE"]},"CampaignStatus":{"type":"string","description":"Current program lifecycle status.","enum":["DRAFT","PENDING","IN_PROGRESS","COMPLETE","CANCELLED","DELETED"]},"Reward":{"type":"object","description":"A single campaign reward (also known as a `CampaignReward`). This is different from a `ParticipantReward`, which is a reward earned by a participant.","required":["id","type","isUnlimited","metadata"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the campaign reward. You can find this ID from *Program Editor > 1. Rewards* and clicking the reward."},"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns this referral reward. Present for SINGLE_SIDED, DOUBLE_SIDED, and MILESTONE rewards. Legacy rewards return CONVERSION."},"title":{"type":["string","null"],"description":"The reward title (internal only, never exposed to participants)."},"isVisible":{"type":"boolean","description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"description":{"type":["string","null"],"description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (only applicable for double-sided reward types)."},"referredRewardUpfront":{"type":"boolean","description":"Only applies to double-sided rewards. When `true`, the referred friend's reward is delivered upfront as a discount and no `ParticipantReward` is created for them when the referral triggers."},"isUnlimited":{"type":"boolean","description":"`true` if this reward can be earned by a single participant an unlimited number of times."},"limit":{"type":["integer","null"],"description":"The number of times a participant can earn this reward (overridden when `isUnlimited` is `true`). `-1` represents an unlimited reward in REST responses."},"conversionsRequired":{"type":["integer","null"],"description":"The number of referrals a participant must make to earn this reward."},"numberOfWinners":{"type":["integer","null"],"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total."},"limitDuration":{"type":["string","null"],"enum":["IN_TOTAL","PER_MONTH","PER_YEAR",null],"description":"Whether the reward can be earned in total, on a monthly basis, or on a yearly basis."},"imageUrl":{"type":["string","null"],"description":"The reward image URL."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"order":{"type":["integer","null"],"description":"If there are multiple rewards, the order in which the reward should be displayed. `null` by default until set within the Design step of the program editor."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text displayed in front of a participant's referral count for UI purposes (e.g., \"You are only\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text displayed after a participant's referral count for UI purposes (e.g., \"referrals away from receiving a nice reward!\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"The reward metadata."},"commissionStructure":{"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The reward commission structure. Present only for affiliate programs."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). `null` when no valuation is set. For commission rewards, both fields are `null`; GrowSurf classifies the actual commission amount as `NONEMPLOYEE_SERVICES`."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. `null` when no valuation is set. Commission rewards have no referred-friend side, so both fields are `null`."}}},"RewardType":{"type":"string","enum":["SINGLE_SIDED","DOUBLE_SIDED","MILESTONE","LEADERBOARD","AFFILIATE"]},"RewardEvent":{"type":"string","enum":["LEAD","CONVERSION"],"description":"The referral event that earns the reward."},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"RewardTaxValuation":{"type":"object","additionalProperties":false,"description":"Tax valuation settings for a reward. Only relevant when the program collects tax documentation.","properties":{"fairMarketValueUSD":{"type":["number","null"],"minimum":0,"maximum":90071992547409.9,"description":"Manual fair-market value in USD (major units) used as the fallback when the reward value cannot be resolved automatically. `null` = no manual value."},"taxCharacter":{"type":["string","null"],"enum":["NONEMPLOYEE_SERVICES","PRIZE_OR_AWARD","PURCHASE_REBATE","OTHER_INCOME","REVIEW_REQUIRED",null],"description":"The reason the recipient earns this reward. `null` inherits the program's confirmed tax treatment for configurable non-commission rewards. Commission rewards always use `NONEMPLOYEE_SERVICES`."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/clone":{"post":{"tags":["Campaigns"],"operationId":"cloneCampaign","summary":"Clone a campaign","description":"Clones an existing program into a new `DRAFT` program. Integrations and credentials are not copied; active rewards are cloned.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Campaign cloned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## CAMPAIGN RESOURCES ↓

{% hint style="info" %}
Manage the files, links, and text that participants see in the program's **Resources** section. New items are drafts by default. FILE resources must be 10 MB or smaller, and file names can contain no more than 120 characters. A FILE can be published and downloaded while its security review is pending; a rejected review blocks it everywhere, including a file that was already published. To upload a new private file through REST, request a short-lived upload ticket, upload the exact bytes to the returned URL, then create the FILE resource with the upload result and one-time ticket. Deleting a resource does not delete a file that is also in Media Center.
{% endhint %}

## List program resources

> Returns all draft and published FILE, LINK, and TEXT resources in their participant display order. Private file delivery coordinates are never returned.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"ProgramResourceListResponse":{"type":"object","additionalProperties":false,"required":["resources"],"properties":{"resources":{"type":"array","maxItems":100,"description":"Ordered resources visible to the caller.","items":{"$ref":"#/components/schemas/ProgramResource"}}}},"ProgramResource":{"type":"object","additionalProperties":false,"description":"One ordered program Resource. File delivery coordinates and internal database identifiers are never returned.","required":["id","type","title","description","category","url","text","file","isPublished","position","createdAt","updatedAt"],"properties":{"id":{"type":"string","readOnly":true,"description":"Stable public resource ID."},"type":{"description":"Resource content type.","$ref":"#/components/schemas/ProgramResourceType"},"title":{"type":"string","maxLength":120,"description":"Participant-visible resource title."},"description":{"type":["string","null"],"maxLength":500,"description":"Optional participant-visible supporting text."},"category":{"type":["string","null"],"maxLength":60,"description":"Optional category heading. Resources keep their explicit order across headings."},"url":{"type":["string","null"],"format":"uri","maxLength":2048,"description":"HTTPS destination for a LINK; otherwise `null`."},"text":{"type":["string","null"],"maxLength":5000,"description":"Plain copyable content for TEXT; otherwise `null`."},"file":{"oneOf":[{"$ref":"#/components/schemas/ProgramResourceFile"},{"type":"null"}],"description":"Safe FILE metadata; otherwise `null`."},"isPublished":{"type":"boolean","description":"Whether eligible participants can receive the resource."},"position":{"type":"integer","minimum":0,"description":"Zero-based dense participant display position."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"Unix time in milliseconds when the resource was created."},"updatedAt":{"type":"integer","format":"int64","readOnly":true,"description":"Unix time in milliseconds when the resource was last updated."}}},"ProgramResourceType":{"type":"string","enum":["FILE","LINK","TEXT"],"description":"The resource content type."},"ProgramResourceFile":{"type":"object","additionalProperties":false,"required":["fileName","mimeType","bytes","format","moderationStatus"],"properties":{"fileName":{"type":"string","maxLength":120,"description":"Original file name shown to program managers and participants."},"mimeType":{"type":"string","description":"MIME type detected from the uploaded bytes."},"bytes":{"type":"integer","minimum":1,"maximum":10485760,"description":"Exact file size in bytes."},"format":{"type":"string","description":"Detected file format."},"moderationStatus":{"description":"Security review state for the access-controlled file.","$ref":"#/components/schemas/ProgramResourceModerationStatus"}}},"ProgramResourceModerationStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"The latest security review state for an access-controlled FILE. `PENDING` and `APPROVED` files can be published and downloaded; a `REJECTED` file is blocked everywhere, including one that was already published."},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/resources":{"get":{"tags":["Campaigns"],"operationId":"listProgramResources","summary":"List program resources","description":"Returns all draft and published FILE, LINK, and TEXT resources in their participant display order. Private file delivery coordinates are never returned.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Program resources returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramResourceListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Create a program resource

> Creates one resource at the end of the program's order. LINK URLs must use HTTPS. TEXT content is plain text. To create a new FILE, first request an upload ticket, upload the bytes directly to the returned URL, then send the upload result as \`uploadResult\` with the one-time \`uploadTicket\`. A FILE is available to participants while its security review is pending; a rejected review blocks it.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CreateProgramResourceRequest":{"type":"object","additionalProperties":false,"required":["type","title"],"oneOf":[{"title":"File resource","required":["uploadTicket","uploadResult"],"not":{"anyOf":[{"required":["url"]},{"required":["text"]}]},"properties":{"type":{"const":"FILE","description":"Selects an access-controlled uploaded file resource."}}},{"title":"Link resource","required":["url"],"not":{"anyOf":[{"required":["text"]},{"required":["uploadTicket"]},{"required":["uploadResult"]}]},"properties":{"type":{"const":"LINK","description":"Selects an HTTPS link resource."}}},{"title":"Text resource","required":["text"],"not":{"anyOf":[{"required":["url"]},{"required":["uploadTicket"]},{"required":["uploadResult"]}]},"properties":{"type":{"const":"TEXT","description":"Selects a plain copyable text resource."}}}],"properties":{"type":{"description":"Resource content type.","$ref":"#/components/schemas/ProgramResourceType"},"title":{"type":"string","minLength":1,"maxLength":120,"description":"Participant-visible resource title."},"description":{"type":["string","null"],"maxLength":500,"description":"Optional participant-visible supporting text."},"category":{"type":["string","null"],"maxLength":60,"description":"Optional category heading."},"isPublished":{"type":"boolean","default":false,"description":"Whether eligible participants can receive the resource."},"url":{"type":"string","format":"uri","pattern":"^https://","maxLength":2048,"description":"HTTPS destination required for a LINK resource."},"text":{"type":"string","minLength":1,"maxLength":5000,"description":"Plain copyable content required for a TEXT resource."},"uploadTicket":{"type":"string","description":"One-time ticket returned by `createProgramResourceUploadTicket`."},"uploadResult":{"description":"Signed upload result required for a FILE resource.","$ref":"#/components/schemas/ProgramResourceUploadResult"}}},"ProgramResourceType":{"type":"string","enum":["FILE","LINK","TEXT"],"description":"The resource content type."},"ProgramResourceUploadResult":{"type":"object","description":"The unmodified signed result returned after uploading with a GrowSurf ticket.","required":["public_id","version","signature","resource_type","type","bytes","secure_url"],"properties":{"asset_id":{"type":"string","description":"Asset identifier returned by the signed upload."},"public_id":{"type":"string","description":"Public upload identifier that must match the upload ticket."},"version":{"type":"integer","minimum":1,"description":"Asset version included in upload-response signature verification."},"signature":{"type":"string","description":"Signature used to verify the upload result."},"resource_type":{"type":"string","enum":["image","raw"],"description":"Upload resource type selected for the file format."},"type":{"type":"string","const":"authenticated","description":"Access-controlled delivery type required for resource files."},"bytes":{"type":"integer","minimum":1,"maximum":10485760,"description":"File size reported by the upload in bytes."},"secure_url":{"type":"string","format":"uri","description":"URL used only for server-side upload verification."},"format":{"type":"string","description":"File format reported by the upload."}},"additionalProperties":true},"ProgramResource":{"type":"object","additionalProperties":false,"description":"One ordered program Resource. File delivery coordinates and internal database identifiers are never returned.","required":["id","type","title","description","category","url","text","file","isPublished","position","createdAt","updatedAt"],"properties":{"id":{"type":"string","readOnly":true,"description":"Stable public resource ID."},"type":{"description":"Resource content type.","$ref":"#/components/schemas/ProgramResourceType"},"title":{"type":"string","maxLength":120,"description":"Participant-visible resource title."},"description":{"type":["string","null"],"maxLength":500,"description":"Optional participant-visible supporting text."},"category":{"type":["string","null"],"maxLength":60,"description":"Optional category heading. Resources keep their explicit order across headings."},"url":{"type":["string","null"],"format":"uri","maxLength":2048,"description":"HTTPS destination for a LINK; otherwise `null`."},"text":{"type":["string","null"],"maxLength":5000,"description":"Plain copyable content for TEXT; otherwise `null`."},"file":{"oneOf":[{"$ref":"#/components/schemas/ProgramResourceFile"},{"type":"null"}],"description":"Safe FILE metadata; otherwise `null`."},"isPublished":{"type":"boolean","description":"Whether eligible participants can receive the resource."},"position":{"type":"integer","minimum":0,"description":"Zero-based dense participant display position."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"Unix time in milliseconds when the resource was created."},"updatedAt":{"type":"integer","format":"int64","readOnly":true,"description":"Unix time in milliseconds when the resource was last updated."}}},"ProgramResourceFile":{"type":"object","additionalProperties":false,"required":["fileName","mimeType","bytes","format","moderationStatus"],"properties":{"fileName":{"type":"string","maxLength":120,"description":"Original file name shown to program managers and participants."},"mimeType":{"type":"string","description":"MIME type detected from the uploaded bytes."},"bytes":{"type":"integer","minimum":1,"maximum":10485760,"description":"Exact file size in bytes."},"format":{"type":"string","description":"Detected file format."},"moderationStatus":{"description":"Security review state for the access-controlled file.","$ref":"#/components/schemas/ProgramResourceModerationStatus"}}},"ProgramResourceModerationStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"The latest security review state for an access-controlled FILE. `PENDING` and `APPROVED` files can be published and downloaded; a `REJECTED` file is blocked everywhere, including one that was already published."},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/resources":{"post":{"tags":["Campaigns"],"operationId":"createProgramResource","summary":"Create a program resource","description":"Creates one resource at the end of the program's order. LINK URLs must use HTTPS. TEXT content is plain text. To create a new FILE, first request an upload ticket, upload the bytes directly to the returned URL, then send the upload result as `uploadResult` with the one-time `uploadTicket`. A FILE is available to participants while its security review is pending; a rejected review blocks it.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProgramResourceRequest"}}}},"responses":{"200":{"description":"Program resource created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramResource"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update a program resource

> Updates the supplied content or publication fields. Set \`position\` to move the resource; GrowSurf compacts the remaining positions. Changing \`type\` requires the complete fields for the new type. A FILE whose security review rejected it cannot be published or downloaded; a pending review does not block it.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ProgramResourceId":{"name":"resourceId","in":"path","required":true,"description":"Program resource ID.","schema":{"type":"string"}}},"schemas":{"UpdateProgramResourceRequest":{"type":"object","additionalProperties":false,"minProperties":1,"description":"Partial Resource update. If `type` changes, include the complete content fields required by the new type.","allOf":[{"if":{"required":["uploadTicket"]},"then":{"required":["uploadResult"]}},{"if":{"required":["uploadResult"]},"then":{"required":["uploadTicket"]}},{"not":{"anyOf":[{"required":["url","text"]},{"required":["url","uploadTicket"]},{"required":["url","uploadResult"]},{"required":["text","uploadTicket"]},{"required":["text","uploadResult"]}]}},{"if":{"required":["type"],"properties":{"type":{"const":"FILE"}}},"then":{"required":["uploadTicket","uploadResult"],"not":{"anyOf":[{"required":["url"]},{"required":["text"]}]}}},{"if":{"required":["type"],"properties":{"type":{"const":"LINK"}}},"then":{"required":["url"],"not":{"anyOf":[{"required":["text"]},{"required":["uploadTicket"]},{"required":["uploadResult"]}]}}},{"if":{"required":["type"],"properties":{"type":{"const":"TEXT"}}},"then":{"required":["text"],"not":{"anyOf":[{"required":["url"]},{"required":["uploadTicket"]},{"required":["uploadResult"]}]}}}],"properties":{"type":{"description":"New resource content type when changing the type.","$ref":"#/components/schemas/ProgramResourceType"},"title":{"type":"string","minLength":1,"maxLength":120,"description":"Participant-visible resource title."},"description":{"type":["string","null"],"maxLength":500,"description":"Optional participant-visible supporting text."},"category":{"type":["string","null"],"maxLength":60,"description":"Optional category heading."},"isPublished":{"type":"boolean","description":"Whether eligible participants can receive the resource."},"position":{"type":"integer","minimum":0,"maximum":99,"description":"New zero-based display position. It must be less than the current Resource count; the list remains dense."},"url":{"type":"string","format":"uri","pattern":"^https://","maxLength":2048,"description":"HTTPS destination for a LINK resource."},"text":{"type":"string","minLength":1,"maxLength":5000,"description":"Plain copyable content for a TEXT resource."},"uploadTicket":{"type":"string","description":"One-time ticket required when replacing FILE content."},"uploadResult":{"description":"Signed upload result required when replacing FILE content.","$ref":"#/components/schemas/ProgramResourceUploadResult"}}},"ProgramResourceType":{"type":"string","enum":["FILE","LINK","TEXT"],"description":"The resource content type."},"ProgramResourceUploadResult":{"type":"object","description":"The unmodified signed result returned after uploading with a GrowSurf ticket.","required":["public_id","version","signature","resource_type","type","bytes","secure_url"],"properties":{"asset_id":{"type":"string","description":"Asset identifier returned by the signed upload."},"public_id":{"type":"string","description":"Public upload identifier that must match the upload ticket."},"version":{"type":"integer","minimum":1,"description":"Asset version included in upload-response signature verification."},"signature":{"type":"string","description":"Signature used to verify the upload result."},"resource_type":{"type":"string","enum":["image","raw"],"description":"Upload resource type selected for the file format."},"type":{"type":"string","const":"authenticated","description":"Access-controlled delivery type required for resource files."},"bytes":{"type":"integer","minimum":1,"maximum":10485760,"description":"File size reported by the upload in bytes."},"secure_url":{"type":"string","format":"uri","description":"URL used only for server-side upload verification."},"format":{"type":"string","description":"File format reported by the upload."}},"additionalProperties":true},"ProgramResource":{"type":"object","additionalProperties":false,"description":"One ordered program Resource. File delivery coordinates and internal database identifiers are never returned.","required":["id","type","title","description","category","url","text","file","isPublished","position","createdAt","updatedAt"],"properties":{"id":{"type":"string","readOnly":true,"description":"Stable public resource ID."},"type":{"description":"Resource content type.","$ref":"#/components/schemas/ProgramResourceType"},"title":{"type":"string","maxLength":120,"description":"Participant-visible resource title."},"description":{"type":["string","null"],"maxLength":500,"description":"Optional participant-visible supporting text."},"category":{"type":["string","null"],"maxLength":60,"description":"Optional category heading. Resources keep their explicit order across headings."},"url":{"type":["string","null"],"format":"uri","maxLength":2048,"description":"HTTPS destination for a LINK; otherwise `null`."},"text":{"type":["string","null"],"maxLength":5000,"description":"Plain copyable content for TEXT; otherwise `null`."},"file":{"oneOf":[{"$ref":"#/components/schemas/ProgramResourceFile"},{"type":"null"}],"description":"Safe FILE metadata; otherwise `null`."},"isPublished":{"type":"boolean","description":"Whether eligible participants can receive the resource."},"position":{"type":"integer","minimum":0,"description":"Zero-based dense participant display position."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"Unix time in milliseconds when the resource was created."},"updatedAt":{"type":"integer","format":"int64","readOnly":true,"description":"Unix time in milliseconds when the resource was last updated."}}},"ProgramResourceFile":{"type":"object","additionalProperties":false,"required":["fileName","mimeType","bytes","format","moderationStatus"],"properties":{"fileName":{"type":"string","maxLength":120,"description":"Original file name shown to program managers and participants."},"mimeType":{"type":"string","description":"MIME type detected from the uploaded bytes."},"bytes":{"type":"integer","minimum":1,"maximum":10485760,"description":"Exact file size in bytes."},"format":{"type":"string","description":"Detected file format."},"moderationStatus":{"description":"Security review state for the access-controlled file.","$ref":"#/components/schemas/ProgramResourceModerationStatus"}}},"ProgramResourceModerationStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"],"description":"The latest security review state for an access-controlled FILE. `PENDING` and `APPROVED` files can be published and downloaded; a `REJECTED` file is blocked everywhere, including one that was already published."},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/resources/{resourceId}":{"patch":{"tags":["Campaigns"],"operationId":"updateProgramResource","summary":"Update a program resource","description":"Updates the supplied content or publication fields. Set `position` to move the resource; GrowSurf compacts the remaining positions. Changing `type` requires the complete fields for the new type. A FILE whose security review rejected it cannot be published or downloaded; a pending review does not block it.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ProgramResourceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProgramResourceRequest"}}}},"responses":{"200":{"description":"Program resource updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramResource"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Delete a program resource

> Deletes the program resource and compacts the remaining order. A referenced Media Center file is not deleted.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ProgramResourceId":{"name":"resourceId","in":"path","required":true,"description":"Program resource ID.","schema":{"type":"string"}}},"schemas":{"DeleteProgramResourceResponse":{"type":"object","additionalProperties":false,"required":["id","success"],"properties":{"id":{"type":"string","description":"Deleted public resource ID."},"success":{"type":"boolean","const":true,"description":"Confirms that the resource was deleted."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/resources/{resourceId}":{"delete":{"tags":["Campaigns"],"operationId":"deleteProgramResource","summary":"Delete a program resource","description":"Deletes the program resource and compacts the remaining order. A referenced Media Center file is not deleted.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ProgramResourceId"}],"responses":{"200":{"description":"Program resource deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteProgramResourceResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Create a program resource upload ticket

> Creates a short-lived, one-time ticket and signed fields for an access-controlled FILE upload. The ticket is bound to this team, program, file name, MIME type, and byte size. Upload the exact bytes to \`uploadUrl\` with \`uploadParameters\`, then create or update a FILE resource with the returned upload result. This operation returns \`503\` until secure file review is configured.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"ProgramResourceUploadTicketRequest":{"type":"object","additionalProperties":false,"required":["fileName","mimeType","bytes"],"properties":{"fileName":{"type":"string","minLength":1,"maxLength":120,"description":"File name with an allowed extension."},"mimeType":{"type":"string","minLength":1,"description":"Browser-declared MIME type. GrowSurf also verifies the file signature after upload."},"bytes":{"type":"integer","minimum":1,"maximum":10485760,"description":"Browser-reported file size in bytes."}}},"ProgramResourceUploadTicket":{"type":"object","additionalProperties":false,"required":["ticket","expiresIn","uploadUrl","uploadParameters"],"properties":{"ticket":{"type":"string","description":"One-time bearer-like ticket. Do not log or persist it."},"expiresIn":{"type":"integer","minimum":1,"description":"Ticket lifetime in seconds."},"uploadUrl":{"type":"string","format":"uri","description":"Endpoint that accepts the signed direct upload."},"uploadParameters":{"type":"object","additionalProperties":{"oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"description":"Opaque signed scalar fields that must be sent unchanged with the file upload."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServiceUnavailable":{"description":"A required upstream service or account configuration is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/resource-upload-tickets":{"post":{"tags":["Campaigns"],"operationId":"createProgramResourceUploadTicket","summary":"Create a program resource upload ticket","description":"Creates a short-lived, one-time ticket and signed fields for an access-controlled FILE upload. The ticket is bound to this team, program, file name, MIME type, and byte size. Upload the exact bytes to `uploadUrl` with `uploadParameters`, then create or update a FILE resource with the returned upload result. This operation returns `503` until secure file review is configured.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramResourceUploadTicketRequest"}}}},"responses":{"200":{"description":"One-time upload ticket created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramResourceUploadTicket"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}}}}
```

***

## CAMPAIGN REWARDS ↓

{% hint style="info" %}
Read or update a program's **Rewards** tab. Available fields depend on the program type (referral or affiliate); `PATCH` is a partial merge (send only what you want to change).
{% endhint %}

## List campaign rewards

> Retrieves the list of a program's configured rewards (\`CampaignReward\`s) — the same set embedded in the \`rewards\` array of the campaign response. Delete a reward with \`DELETE /campaign/{id}/reward-configs/{campaignRewardId}\`.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignRewardListResponse":{"type":"object","description":"The list of a program's configured rewards.","required":["rewards"],"properties":{"rewards":{"type":"array","items":{"$ref":"#/components/schemas/Reward"},"description":"The program's active, visible, and enabled reward configs."}}},"Reward":{"type":"object","description":"A single campaign reward (also known as a `CampaignReward`). This is different from a `ParticipantReward`, which is a reward earned by a participant.","required":["id","type","isUnlimited","metadata"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the campaign reward. You can find this ID from *Program Editor > 1. Rewards* and clicking the reward."},"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns this referral reward. Present for SINGLE_SIDED, DOUBLE_SIDED, and MILESTONE rewards. Legacy rewards return CONVERSION."},"title":{"type":["string","null"],"description":"The reward title (internal only, never exposed to participants)."},"isVisible":{"type":"boolean","description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"description":{"type":["string","null"],"description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (only applicable for double-sided reward types)."},"referredRewardUpfront":{"type":"boolean","description":"Only applies to double-sided rewards. When `true`, the referred friend's reward is delivered upfront as a discount and no `ParticipantReward` is created for them when the referral triggers."},"isUnlimited":{"type":"boolean","description":"`true` if this reward can be earned by a single participant an unlimited number of times."},"limit":{"type":["integer","null"],"description":"The number of times a participant can earn this reward (overridden when `isUnlimited` is `true`). `-1` represents an unlimited reward in REST responses."},"conversionsRequired":{"type":["integer","null"],"description":"The number of referrals a participant must make to earn this reward."},"numberOfWinners":{"type":["integer","null"],"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total."},"limitDuration":{"type":["string","null"],"enum":["IN_TOTAL","PER_MONTH","PER_YEAR",null],"description":"Whether the reward can be earned in total, on a monthly basis, or on a yearly basis."},"imageUrl":{"type":["string","null"],"description":"The reward image URL."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"order":{"type":["integer","null"],"description":"If there are multiple rewards, the order in which the reward should be displayed. `null` by default until set within the Design step of the program editor."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text displayed in front of a participant's referral count for UI purposes (e.g., \"You are only\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text displayed after a participant's referral count for UI purposes (e.g., \"referrals away from receiving a nice reward!\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"The reward metadata."},"commissionStructure":{"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The reward commission structure. Present only for affiliate programs."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). `null` when no valuation is set. For commission rewards, both fields are `null`; GrowSurf classifies the actual commission amount as `NONEMPLOYEE_SERVICES`."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. `null` when no valuation is set. Commission rewards have no referred-friend side, so both fields are `null`."}}},"RewardType":{"type":"string","enum":["SINGLE_SIDED","DOUBLE_SIDED","MILESTONE","LEADERBOARD","AFFILIATE"]},"RewardEvent":{"type":"string","enum":["LEAD","CONVERSION"],"description":"The referral event that earns the reward."},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"RewardTaxValuation":{"type":"object","additionalProperties":false,"description":"Tax valuation settings for a reward. Only relevant when the program collects tax documentation.","properties":{"fairMarketValueUSD":{"type":["number","null"],"minimum":0,"maximum":90071992547409.9,"description":"Manual fair-market value in USD (major units) used as the fallback when the reward value cannot be resolved automatically. `null` = no manual value."},"taxCharacter":{"type":["string","null"],"enum":["NONEMPLOYEE_SERVICES","PRIZE_OR_AWARD","PURCHASE_REBATE","OTHER_INCOME","REVIEW_REQUIRED",null],"description":"The reason the recipient earns this reward. `null` inherits the program's confirmed tax treatment for configurable non-commission rewards. Commission rewards always use `NONEMPLOYEE_SERVICES`."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/reward-configs":{"get":{"tags":["Campaigns"],"operationId":"listCampaignRewards","summary":"List campaign rewards","description":"Retrieves the list of a program's configured rewards (`CampaignReward`s) — the same set embedded in the `rewards` array of the campaign response. Delete a reward with `DELETE /campaign/{id}/reward-configs/{campaignRewardId}`.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Campaign rewards returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignRewardListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Create a campaign reward

> Creates a new campaign reward (\`CampaignReward\`) with a GrowSurf-assigned ID. The reward type must be compatible with the program type (affiliate programs support only \`AFFILIATE\` rewards; referral programs support all other types). Enabling an active reward of a type automatically enables that reward type on the program.\
> For a referral reward, \`event: LEAD\` earns the reward on a referred signup and requires the program's \`referralTrigger\` to be \`CUSTOM\`; \`event: CONVERSION\` earns it on the later qualifying action. For an affiliate reward, \`commissionStructure.event: LEAD\` requires a \`FIXED\` commission with a positive \`amount\`; \`SALE\` can use \`FIXED\` or \`PERCENT\`.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"RewardCreateRequest":{"type":"object","description":"Request body for creating a campaign reward. `type` is required and must be compatible with the program type. AFFILIATE rewards additionally require `commissionStructure` (with `amount` for a `FIXED` commission or `percent` for a `PERCENT` commission).","required":["type"],"allOf":[{"$ref":"#/components/schemas/RewardWritableFields"},{"type":"object","properties":{"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type. Immutable after creation."}}},{"if":{"required":["commissionStructure"],"properties":{"commissionStructure":{"required":["event"],"properties":{"event":{"enum":["CLICK","LEAD"]}}}}},"then":{"properties":{"commissionStructure":{"required":["amount"],"properties":{"amount":{"type":"integer","minimum":1},"type":{"enum":["FIXED",null]}}}}}}]},"RewardWritableFields":{"type":"object","description":"The writable fields shared by the create and update campaign reward requests.","properties":{"title":{"type":"string","description":"The reward title (internal only, never exposed to participants)."},"description":{"type":"string","description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (double-sided rewards only)."},"imageUrl":{"type":["string","null"],"description":"An image URL for the reward."},"isVisible":{"type":"boolean","default":true,"description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"isUnlimited":{"type":"boolean","default":true,"description":"Whether the reward can be earned an unlimited number of times. Defaults to `true`, except `MILESTONE` rewards, which can only be earned once."},"referredRewardUpfront":{"type":"boolean","default":false,"description":"For double-sided rewards, deliver the referred friend's reward upfront as a discount."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns a SINGLE_SIDED, DOUBLE_SIDED, or MILESTONE referral reward. Defaults to CONVERSION. LEAD requires the program's referralTrigger to be CUSTOM."},"limit":{"type":"integer","minimum":0,"default":1,"description":"The number of times a participant can earn the reward (overridden by `isUnlimited`)."},"conversionsRequired":{"type":"integer","minimum":1,"default":1,"description":"The number of referrals required to earn the reward."},"numberOfWinners":{"type":"integer","minimum":0,"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total. A `LEADERBOARD` reward that omits it defaults to `3`."},"order":{"type":"integer","description":"The display order of the reward."},"limitDuration":{"type":"string","enum":["IN_TOTAL","PER_MONTH","PER_YEAR"],"default":"IN_TOTAL","description":"The window over which `limit` applies."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text shown before a participant's referral count in milestone progress copy (e.g. `You are only`). Applies to `MILESTONE` rewards."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text shown after a participant's referral count in milestone progress copy (e.g. `referrals away from your next reward!`). Applies to `MILESTONE` rewards."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook when this reward is earned. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same behavior and caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"Custom key/value metadata (single-level; keys are camelCased on save; values are stored as strings). Sending `metadata` REPLACES the stored object. Campaign copy can reference a key via `{{campaignReward['<rewardId>']['<key>']}}` tokens: renaming a key (same value, new key) automatically rewrites those references; removing a key that campaign copy still references returns a `409` listing the referencing fields."},"commissionStructure":{"$ref":"#/components/schemas/CommissionStructure","description":"The affiliate commission structure (AFFILIATE rewards only). REQUIRED when creating an AFFILIATE reward — include `amount` (and optionally `amountISO`) for a `FIXED` commission, or `percent` for a `PERCENT` commission."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). Used by tax documentation / 1099 reporting. `null` when no valuation is set. Commission rewards always use their actual cash amount and the `NONEMPLOYEE_SERVICES` tax character, so GrowSurf clears these settings for that reward type."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. Set its treatment independently when the two sides are earned for different reasons. `null` when no valuation is set. Commission rewards have no referred-friend side, so GrowSurf clears these settings for that reward type."}}},"RewardEvent":{"type":"string","enum":["LEAD","CONVERSION"],"description":"The referral event that earns the reward."},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"RewardTaxValuation":{"type":"object","additionalProperties":false,"description":"Tax valuation settings for a reward. Only relevant when the program collects tax documentation.","properties":{"fairMarketValueUSD":{"type":["number","null"],"minimum":0,"maximum":90071992547409.9,"description":"Manual fair-market value in USD (major units) used as the fallback when the reward value cannot be resolved automatically. `null` = no manual value."},"taxCharacter":{"type":["string","null"],"enum":["NONEMPLOYEE_SERVICES","PRIZE_OR_AWARD","PURCHASE_REBATE","OTHER_INCOME","REVIEW_REQUIRED",null],"description":"The reason the recipient earns this reward. `null` inherits the program's confirmed tax treatment for configurable non-commission rewards. Commission rewards always use `NONEMPLOYEE_SERVICES`."}}},"RewardType":{"type":"string","enum":["SINGLE_SIDED","DOUBLE_SIDED","MILESTONE","LEADERBOARD","AFFILIATE"]},"Reward":{"type":"object","description":"A single campaign reward (also known as a `CampaignReward`). This is different from a `ParticipantReward`, which is a reward earned by a participant.","required":["id","type","isUnlimited","metadata"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the campaign reward. You can find this ID from *Program Editor > 1. Rewards* and clicking the reward."},"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns this referral reward. Present for SINGLE_SIDED, DOUBLE_SIDED, and MILESTONE rewards. Legacy rewards return CONVERSION."},"title":{"type":["string","null"],"description":"The reward title (internal only, never exposed to participants)."},"isVisible":{"type":"boolean","description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"description":{"type":["string","null"],"description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (only applicable for double-sided reward types)."},"referredRewardUpfront":{"type":"boolean","description":"Only applies to double-sided rewards. When `true`, the referred friend's reward is delivered upfront as a discount and no `ParticipantReward` is created for them when the referral triggers."},"isUnlimited":{"type":"boolean","description":"`true` if this reward can be earned by a single participant an unlimited number of times."},"limit":{"type":["integer","null"],"description":"The number of times a participant can earn this reward (overridden when `isUnlimited` is `true`). `-1` represents an unlimited reward in REST responses."},"conversionsRequired":{"type":["integer","null"],"description":"The number of referrals a participant must make to earn this reward."},"numberOfWinners":{"type":["integer","null"],"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total."},"limitDuration":{"type":["string","null"],"enum":["IN_TOTAL","PER_MONTH","PER_YEAR",null],"description":"Whether the reward can be earned in total, on a monthly basis, or on a yearly basis."},"imageUrl":{"type":["string","null"],"description":"The reward image URL."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"order":{"type":["integer","null"],"description":"If there are multiple rewards, the order in which the reward should be displayed. `null` by default until set within the Design step of the program editor."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text displayed in front of a participant's referral count for UI purposes (e.g., \"You are only\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text displayed after a participant's referral count for UI purposes (e.g., \"referrals away from receiving a nice reward!\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"The reward metadata."},"commissionStructure":{"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The reward commission structure. Present only for affiliate programs."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). `null` when no valuation is set. For commission rewards, both fields are `null`; GrowSurf classifies the actual commission amount as `NONEMPLOYEE_SERVICES`."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. `null` when no valuation is set. Commission rewards have no referred-friend side, so both fields are `null`."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/reward-configs":{"post":{"tags":["Campaigns"],"operationId":"createCampaignReward","summary":"Create a campaign reward","description":"Creates a new campaign reward (`CampaignReward`) with a GrowSurf-assigned ID. The reward type must be compatible with the program type (affiliate programs support only `AFFILIATE` rewards; referral programs support all other types). Enabling an active reward of a type automatically enables that reward type on the program.\nFor a referral reward, `event: LEAD` earns the reward on a referred signup and requires the program's `referralTrigger` to be `CUSTOM`; `event: CONVERSION` earns it on the later qualifying action. For an affiliate reward, `commissionStructure.event: LEAD` requires a `FIXED` commission with a positive `amount`; `SALE` can use `FIXED` or `PERCENT`.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardCreateRequest"}}}},"responses":{"200":{"description":"Campaign reward created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reward"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update a campaign reward

> Updates an existing campaign reward (\`CampaignReward\`). The reward \`type\` is immutable and cannot be changed. When the update replaces \`metadata\`, renamed keys automatically rewrite any \`{{campaignReward\[…]}}\` references in campaign copy; removing a key that campaign copy still references returns a \`409\` listing the referencing fields.\
> For a referral reward, \`event: LEAD\` earns the reward on a referred signup and requires the program's \`referralTrigger\` to be \`CUSTOM\`; \`event: CONVERSION\` earns it on the later qualifying action. For an affiliate reward, \`commissionStructure.event: LEAD\` requires a \`FIXED\` commission with a positive \`amount\`; \`SALE\` can use \`FIXED\` or \`PERCENT\`.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"CampaignRewardId":{"name":"campaignRewardId","in":"path","required":true,"description":"Campaign reward (`CampaignReward`) ID.","schema":{"type":"string"}}},"schemas":{"RewardUpdateRequest":{"allOf":[{"$ref":"#/components/schemas/RewardWritableFields"}],"description":"Request body for updating a campaign reward. All fields are optional; `type` is immutable and must not be supplied."},"RewardWritableFields":{"type":"object","description":"The writable fields shared by the create and update campaign reward requests.","properties":{"title":{"type":"string","description":"The reward title (internal only, never exposed to participants)."},"description":{"type":"string","description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (double-sided rewards only)."},"imageUrl":{"type":["string","null"],"description":"An image URL for the reward."},"isVisible":{"type":"boolean","default":true,"description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"isUnlimited":{"type":"boolean","default":true,"description":"Whether the reward can be earned an unlimited number of times. Defaults to `true`, except `MILESTONE` rewards, which can only be earned once."},"referredRewardUpfront":{"type":"boolean","default":false,"description":"For double-sided rewards, deliver the referred friend's reward upfront as a discount."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns a SINGLE_SIDED, DOUBLE_SIDED, or MILESTONE referral reward. Defaults to CONVERSION. LEAD requires the program's referralTrigger to be CUSTOM."},"limit":{"type":"integer","minimum":0,"default":1,"description":"The number of times a participant can earn the reward (overridden by `isUnlimited`)."},"conversionsRequired":{"type":"integer","minimum":1,"default":1,"description":"The number of referrals required to earn the reward."},"numberOfWinners":{"type":"integer","minimum":0,"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total. A `LEADERBOARD` reward that omits it defaults to `3`."},"order":{"type":"integer","description":"The display order of the reward."},"limitDuration":{"type":"string","enum":["IN_TOTAL","PER_MONTH","PER_YEAR"],"default":"IN_TOTAL","description":"The window over which `limit` applies."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text shown before a participant's referral count in milestone progress copy (e.g. `You are only`). Applies to `MILESTONE` rewards."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text shown after a participant's referral count in milestone progress copy (e.g. `referrals away from your next reward!`). Applies to `MILESTONE` rewards."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook when this reward is earned. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same behavior and caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"Custom key/value metadata (single-level; keys are camelCased on save; values are stored as strings). Sending `metadata` REPLACES the stored object. Campaign copy can reference a key via `{{campaignReward['<rewardId>']['<key>']}}` tokens: renaming a key (same value, new key) automatically rewrites those references; removing a key that campaign copy still references returns a `409` listing the referencing fields."},"commissionStructure":{"$ref":"#/components/schemas/CommissionStructure","description":"The affiliate commission structure (AFFILIATE rewards only). REQUIRED when creating an AFFILIATE reward — include `amount` (and optionally `amountISO`) for a `FIXED` commission, or `percent` for a `PERCENT` commission."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). Used by tax documentation / 1099 reporting. `null` when no valuation is set. Commission rewards always use their actual cash amount and the `NONEMPLOYEE_SERVICES` tax character, so GrowSurf clears these settings for that reward type."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. Set its treatment independently when the two sides are earned for different reasons. `null` when no valuation is set. Commission rewards have no referred-friend side, so GrowSurf clears these settings for that reward type."}}},"RewardEvent":{"type":"string","enum":["LEAD","CONVERSION"],"description":"The referral event that earns the reward."},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"RewardTaxValuation":{"type":"object","additionalProperties":false,"description":"Tax valuation settings for a reward. Only relevant when the program collects tax documentation.","properties":{"fairMarketValueUSD":{"type":["number","null"],"minimum":0,"maximum":90071992547409.9,"description":"Manual fair-market value in USD (major units) used as the fallback when the reward value cannot be resolved automatically. `null` = no manual value."},"taxCharacter":{"type":["string","null"],"enum":["NONEMPLOYEE_SERVICES","PRIZE_OR_AWARD","PURCHASE_REBATE","OTHER_INCOME","REVIEW_REQUIRED",null],"description":"The reason the recipient earns this reward. `null` inherits the program's confirmed tax treatment for configurable non-commission rewards. Commission rewards always use `NONEMPLOYEE_SERVICES`."}}},"Reward":{"type":"object","description":"A single campaign reward (also known as a `CampaignReward`). This is different from a `ParticipantReward`, which is a reward earned by a participant.","required":["id","type","isUnlimited","metadata"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the campaign reward. You can find this ID from *Program Editor > 1. Rewards* and clicking the reward."},"type":{"$ref":"#/components/schemas/RewardType","description":"The reward type."},"event":{"$ref":"#/components/schemas/RewardEvent","description":"The event that earns this referral reward. Present for SINGLE_SIDED, DOUBLE_SIDED, and MILESTONE rewards. Legacy rewards return CONVERSION."},"title":{"type":["string","null"],"description":"The reward title (internal only, never exposed to participants)."},"isVisible":{"type":"boolean","description":"Whether the reward is enabled. When `false`, the reward is disabled: it is no longer awarded, and it is hidden from participants, including those who already earned it. Set `true` for the reward to be visible and awardable."},"description":{"type":["string","null"],"description":"The reward description shown to the referrer."},"referralDescription":{"type":["string","null"],"description":"The reward description shown to the referred friend (only applicable for double-sided reward types)."},"referredRewardUpfront":{"type":"boolean","description":"Only applies to double-sided rewards. When `true`, the referred friend's reward is delivered upfront as a discount and no `ParticipantReward` is created for them when the referral triggers."},"isUnlimited":{"type":"boolean","description":"`true` if this reward can be earned by a single participant an unlimited number of times."},"limit":{"type":["integer","null"],"description":"The number of times a participant can earn this reward (overridden when `isUnlimited` is `true`). `-1` represents an unlimited reward in REST responses."},"conversionsRequired":{"type":["integer","null"],"description":"The number of referrals a participant must make to earn this reward."},"numberOfWinners":{"type":["integer","null"],"description":"The maximum number of winners. Only applies to `LEADERBOARD` rewards. When `limitDuration` is `PER_MONTH`, this many top referrers win each month; otherwise this many win in total."},"limitDuration":{"type":["string","null"],"enum":["IN_TOTAL","PER_MONTH","PER_YEAR",null],"description":"Whether the reward can be earned in total, on a monthly basis, or on a yearly basis."},"imageUrl":{"type":["string","null"],"description":"The reward image URL."},"couponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referrer in the reward-won email and webhook. Display text only; GrowSurf does not create or validate it in any billing system. If the program has a connected billing integration (Stripe, Chargebee, or Recurly) that issues a coupon for the referral, that issued code is shown instead."},"order":{"type":["integer","null"],"description":"If there are multiple rewards, the order in which the reward should be displayed. `null` by default until set within the Design step of the program editor."},"nextMilestonePrefix":{"type":["string","null"],"description":"Text displayed in front of a participant's referral count for UI purposes (e.g., \"You are only\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"nextMilestoneSuffix":{"type":["string","null"],"description":"Text displayed after a participant's referral count for UI purposes (e.g., \"referrals away from receiving a nice reward!\"). Applicable for milestone rewards (when `type` is `MILESTONE`)."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"The reward metadata."},"commissionStructure":{"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The reward commission structure. Present only for affiliate programs."},"referralCouponCode":{"type":["string","null"],"description":"A legacy static coupon code shown to the referred friend in the reward-won email and webhook (double-sided rewards). Same caveats as `couponCode`: display text only, not created or validated in any billing system, and superseded by a connected billing integration's issued coupon when one exists."},"value":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the reward (the referrer's side of a double-sided reward). `null` when no valuation is set. For commission rewards, both fields are `null`; GrowSurf classifies the actual commission amount as `NONEMPLOYEE_SERVICES`."},"referredValue":{"oneOf":[{"$ref":"#/components/schemas/RewardTaxValuation"},{"type":"null"}],"description":"Tax valuation for the referred friend's side of a double-sided reward. `null` when no valuation is set. Commission rewards have no referred-friend side, so both fields are `null`."}}},"RewardType":{"type":"string","enum":["SINGLE_SIDED","DOUBLE_SIDED","MILESTONE","LEADERBOARD","AFFILIATE"]},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"Conflicting duplicate request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/reward-configs/{campaignRewardId}":{"patch":{"tags":["Campaigns"],"operationId":"updateCampaignReward","summary":"Update a campaign reward","description":"Updates an existing campaign reward (`CampaignReward`). The reward `type` is immutable and cannot be changed. When the update replaces `metadata`, renamed keys automatically rewrite any `{{campaignReward[…]}}` references in campaign copy; removing a key that campaign copy still references returns a `409` listing the referencing fields.\nFor a referral reward, `event: LEAD` earns the reward on a referred signup and requires the program's `referralTrigger` to be `CUSTOM`; `event: CONVERSION` earns it on the later qualifying action. For an affiliate reward, `commissionStructure.event: LEAD` requires a `FIXED` commission with a positive `amount`; `SALE` can use `FIXED` or `PERCENT`.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/CampaignRewardId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewardUpdateRequest"}}}},"responses":{"200":{"description":"Campaign reward updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reward"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Delete a campaign reward

> Deletes a campaign reward (\`CampaignReward\`). The reward is deactivated, removed from the program's reward set, and any connected upfront-discount coupons are cleaned up. If campaign copy still references any of the reward's metadata keys via \`{{campaignReward\[…]}}\` tokens, the delete returns a \`409\` listing the referencing fields — update those fields first.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"CampaignRewardId":{"name":"campaignRewardId","in":"path","required":true,"description":"Campaign reward (`CampaignReward`) ID.","schema":{"type":"string"}}},"schemas":{"DeleteRewardResponse":{"type":"object","required":["id","success"],"properties":{"id":{"type":"string","description":"The deleted reward ID."},"success":{"description":"Whether the campaign reward was deleted.","type":"boolean"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"Conflicting duplicate request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/reward-configs/{campaignRewardId}":{"delete":{"tags":["Campaigns"],"operationId":"deleteCampaignReward","summary":"Delete a campaign reward","description":"Deletes a campaign reward (`CampaignReward`). The reward is deactivated, removed from the program's reward set, and any connected upfront-discount coupons are cleaned up. If campaign copy still references any of the reward's metadata keys via `{{campaignReward[…]}}` tokens, the delete returns a `409` listing the referencing fields — update those fields first.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/CampaignRewardId"}],"responses":{"200":{"description":"Campaign reward deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRewardResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## CAMPAIGN DESIGN ↓

{% hint style="info" %}
Read or update a program's design configuration: the **Design** tab plus payout-destination confirmation page copy under `payoutDestinationConfirmation` (configured from payout integration cards in the dashboard). This includes the GrowSurf window layout, header, share channels, returning-participant sign-in text, signup form, Claim Offer Popup, portal/landing pages, theme styling, and participant-facing country-name overrides under `countryLabels`. The available fields depend on the program type; `PATCH` is a partial merge, so send only what you want to change. Portal and landing-page custom code or JavaScript is not editable through this API.
{% endhint %}

## Retrieve campaign design

> Retrieves a program's configured design fields: the dashboard Program Editor's \*\*Design\*\* tab plus the payout-destination confirmation page copy configured from payout integration cards. This includes the GrowSurf window layout, header, share channels and invites, signup form, portal and landing pages, theme styling, and referral or affiliate summary and status sections. The available fields depend on the program type. \`payoutDestinationConfirmation\` is omitted when no confirmation fields are stored. Stored \`null\` fields are returned as \`null\`; omitted and \`null\` fields use localized defaults.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignDesign":{"type":"object","description":"A program's design configuration. It includes the dashboard Program Editor's Design tab and the payout-destination confirmation page copy configured from payout integration cards. The exact fields available depend on the program type (for example, `referralSummary` is referral-only, while `affiliateSummary`, `commissions`, and `payouts` are affiliate-only). `participantSettings` is available to both program types; its manual payout and Wise fields are affiliate-only. `GET` returns the fields configured for the program; `payoutDestinationConfirmation` is omitted when no confirmation fields are stored. Stored `null` fields are returned as `null`; omitted and `null` fields use localized defaults. `PATCH` back only the sections or fields you want to change (arrays such as `signup.fields` replace wholesale).","properties":{"participantAvatarStyle":{"type":"string","enum":["CHARACTERS","INITIALS","ANIMALS","GRADIENT"],"default":"CHARACTERS","description":"The participant avatar style. New programs start with `CHARACTERS`. Programs with a missing or unknown stored value return `INITIALS` for backward compatibility."},"window":{"type":"object","description":"How the sections inside the GrowSurf window are laid out. `navigationMode` chooses between a tabbed layout (`TABS`) and a single scrolling list (`LIST`)."},"header":{"type":"object","description":"Header content for participants (`postText`) and non-participants (`preText`)."},"stats":{"type":"object","description":"The stats panel a participant sees at the top of the GrowSurf window, summarizing their own referral progress. Only its heading text (`title`) is editable here."},"share":{"type":"object","description":"Share channels (`type.<channel>.{isVisible,buttonText,message,order}`), invite settings, and share-button styling."},"signup":{"type":"object","description":"Signup form (`fields[]`, GDPR consent, button and login text)."},"login":{"description":"Customizable text shown during participant sign-in.","$ref":"#/components/schemas/ParticipantLoginDesign"},"payoutDestinationConfirmation":{"description":"Customizable text for the participant-facing payout-destination confirmation page.","$ref":"#/components/schemas/PayoutDestinationConfirmationDesign"},"countryLabels":{"type":"object","description":"Participant-facing country-name overrides, keyed by ISO 3166-1 alpha-2 code (for example `GB`). Each label replaces the default country name wherever participants pick a country, such as payout and tax forms. Overrides merge per code, so send only the codes you want to change; send `null` (or the default name) for a code to restore its default. Unknown codes are rejected.","additionalProperties":{"type":["string","null"],"maxLength":500}},"referralStatus":{"type":"object","description":"The Referral Status section, which lists the people a participant has invited and each one's progress (invite sent, pending, awarded, or expired). Controls its title, icon, column and status labels, empty-state text, and whether invited emails are masked."},"leaderboard":{"type":"object","description":"The Leaderboard section that ranks participants against each other. Controls its title, icon, column labels, which time-period and ranking selectors appear, empty-state text, and how other participants' names are masked."},"referredExperience":{"type":"object","description":"The banner, headline, and Claim Offer Popup shown to someone who arrives on your site through a referral link. Controls their copy and placement, whether the popup is enabled, and how the referrer's name is formatted. The popup is available for both referral and affiliate programs.","properties":{"isOfferPopupEnabled":{"type":"boolean","description":"Whether referred visitors see the Claim Offer Popup. Affiliate programs start with it disabled until you opt in."},"offerPopupTitle":{"type":["string","null"],"maxLength":255,"description":"Popup heading. Supports the same dynamic-text variables as the dashboard editor."},"offerPopupDescription":{"type":["string","null"],"maxLength":255,"description":"Text shown below the popup heading."},"offerPopupButtonText":{"type":["string","null"],"maxLength":100,"description":"Text on the button that saves the visitor's offer."},"offerPopupImageUrl":{"type":["string","null"],"maxLength":500,"description":"Optional image shown in the popup."},"isOfferPopupReferrerImageShown":{"type":"boolean","description":"Whether the popup shows the referrer's profile image when one is available."},"offerPopupPlacement":{"type":"string","enum":["CENTER","BOTTOM","BOTTOM_RIGHT","BOTTOM_LEFT","TOP"],"description":"Where the popup appears. Small screens may use a bottom sheet."},"offerPopupDelaySeconds":{"type":"integer","enum":[0,3,5,10],"description":"Seconds to wait before showing the popup."},"offerPopupThankYouText":{"type":["string","null"],"maxLength":255,"description":"Message shown after the visitor saves the offer."},"offerPopupThankYouButtonText":{"type":["string","null"],"maxLength":100,"description":"Text on the post-claim button that opens the configured signup URL."},"isOfferPopupConfettiEnabled":{"type":"boolean","description":"Whether to show confetti after the visitor saves the offer."},"isOfferPopupShownOnAllPages":{"type":"boolean","description":"Whether the popup can appear on every page where GrowSurf is installed instead of only the Share URL."},"offerPopupSecondaryLinkText":{"type":["string","null"],"maxLength":100,"description":"Optional secondary-link text shown after the offer is saved."},"offerPopupSecondaryLinkUrl":{"type":["string","null"],"maxLength":255,"description":"Destination for the optional secondary link. Must start with `http://` or `https://`. Send `null` or an empty string to clear it."},"isOfferPopupOverlayDimmed":{"type":"boolean","description":"Whether the centered popup dims the page behind it."},"offerPopupEmailPlaceholder":{"type":["string","null"],"maxLength":100,"description":"Hint text inside the popup's email field. Leave it empty to reuse the email field label from your signup form."},"offerPopupPromoCodeCopyLabel":{"type":["string","null"],"maxLength":100,"description":"Text on the copy action beside the promo code, shown after the offer is claimed when an upfront-discount coupon is enabled."},"offerPopupSubmitError":{"type":["string","null"],"maxLength":255,"description":"Message shown inside the popup when the offer cannot be saved, such as during a connection problem."},"isBannerEnabled":{"type":"boolean","description":"Whether referred visitors see the welcome banner, a sticky banner that follows them as they scroll your Share URL."},"bannerText":{"type":["string","null"],"maxLength":150000,"description":"Welcome banner copy. Accepts HTML and the same dynamic-text variables as the dashboard editor."},"bannerPlacement":{"type":"string","enum":["TOP","BOTTOM"],"description":"Where the welcome banner sits on the page."},"isBannerClickableToSignupUrl":{"type":"boolean","description":"Whether clicking the welcome banner opens the program's signup URL. It has no effect until a signup URL is configured."},"isHeadingEnabled":{"type":"boolean","description":"Whether referred visitors see the inline welcome message added to the main heading on your Share URL."},"headingText":{"type":["string","null"],"maxLength":150000,"description":"Inline welcome message copy. Accepts HTML and the same dynamic-text variables as the dashboard editor."},"headingTarget":{"type":"string","enum":["H1","H2","H3","H4","H5"],"description":"Which heading element on the page the inline welcome message attaches to. GrowSurf uses the first match."},"headingPlacement":{"type":"string","enum":["PREPEND","APPEND","REPLACE"],"description":"Whether the inline welcome message goes before the target heading, after it, or replaces it."},"isHeadingStyled":{"type":"boolean","description":"Whether the inline welcome message keeps its own styling. When `false` it renders as plain text."},"isHeadingClickableToSignupUrl":{"type":"boolean","description":"Whether clicking the inline welcome message opens the program's signup URL. It has no effect until a signup URL is configured."},"pageTitleReplacement":{"type":["string","null"],"maxLength":150000,"description":"Browser tab title shown to referred visitors on your Share URL, in place of the page's own title."},"referrerNameFormat":{"type":"string","enum":["FIRST","FIRST_LAST_INITIAL","FIRST_LAST"],"description":"How the referrer's name appears in referred-visitor copy — `FIRST` (`Gavin`), `FIRST_LAST_INITIAL` (`Gavin B`), or `FIRST_LAST` (`Gavin Belson`)."},"referrerNameFallback":{"type":["string","null"],"maxLength":100,"description":"Name used in referred-visitor copy when the referrer has no first or last name on file."}}},"referralSummary":{"type":"object","description":"Referral programs only. The row of summary tiles a participant sees (clicks, leads, referrals, invites sent, rewards earned, and more). Controls each tile's label, tooltip, and visibility, plus the section's title and icon."},"affiliateSummary":{"type":"object","description":"Affiliate programs only. The row of summary tiles an affiliate sees (clicks, leads, referrals, referral revenue, upcoming payout, and total paid out). Controls each tile's label, tooltip, and visibility, plus the section's title and icon."},"commissions":{"type":"object","description":"Affiliate programs only. The Commissions section of the participant portal, which lists an affiliate's earned commissions and their status (pending, approved, paid, or reversed). Controls its title, icon, column labels, status labels and tooltips, and empty-state text."},"payouts":{"type":"object","description":"Affiliate programs only. The Payouts section of the participant portal, which lists an affiliate's payouts and their status (upcoming, queued, issued, failed, or reversed). Controls its title, icon, column labels, status labels and tooltips, and empty-state text."},"rewards":{"type":"object","description":"The heading, icon, and empty-state text of the rewards panel shown to participants. Which rewards appear, and the order they appear in, are managed through the campaign rewards endpoints, not here."},"resources":{"type":"object","additionalProperties":false,"description":"Participant presentation settings for Resources. Resource content and publication order are managed through the program Resources endpoints.","properties":{"isPublicDisplayed":{"type":"boolean","description":"Whether the Resources destination is enabled. It remains hidden until at least one valid resource is published."},"title":{"type":"string","maxLength":100,"description":"Participant-visible Resources section title."},"viewResourcesLinkText":{"type":"string","maxLength":100,"description":"LIST-mode row text."},"backLinkText":{"type":"string","maxLength":100,"description":"LIST-mode detail Back text."},"copyButtonText":{"type":"string","maxLength":100,"description":"TEXT resource copy action."},"copiedText":{"type":"string","maxLength":100,"description":"TEXT resource copy confirmation."},"emptyState":{"type":"string","maxLength":500,"description":"Text shown in place of the list when no resources are published."},"icon":{"type":"object","additionalProperties":false,"description":"Icon configuration for the Resources destination.","properties":{"type":{"type":"string","enum":["DEFAULT","IMAGE","NONE"],"description":"Whether to use the default icon, a configured image, or no icon."},"imageUrl":{"type":"string","maxLength":500,"description":"LIST-mode icon image URL when `type` is `IMAGE`."}}}}},"participantSettings":{"type":"object","description":"The participant's account settings area in the portal. Controls the labels and messages for logging out, managing PayPal and Wise payout emails (including confirmation, connected, expired, and repair states), and completing tax details (country of residency, VAT, and tax-form status). Wise payout copy applies to affiliate programs; the other fields also apply to eligible referral programs."},"landingPages":{"type":"object","description":"Portal and landing pages — company info, `content` (FAQ/terms/labels), `styles` (fonts), third-party script IDs (GA/GTM/Facebook/HubSpot), and SEO meta tags. Custom code, CSS, and JS are not editable via the API."},"theme":{"type":"object","description":"Visual theme styling (colors, shadows, and similar).","properties":{"referredExperienceOfferPopup":{"type":"object","description":"Paid-plan color settings for the Claim Offer Popup.","properties":{"color":{"type":["string","null"],"maxLength":255,"description":"Popup text color."},"backgroundColor":{"type":["string","null"],"maxLength":255,"description":"Popup background color."}}}}}}},"ParticipantLoginDesign":{"type":"object","description":"Customizable sign-in text shown when an existing participant asks GrowSurf for a secure sign-in link.","properties":{"heading":{"type":"string","maxLength":100,"description":"The heading above the participant sign-in form."},"description":{"type":"string","maxLength":500,"description":"The supporting text that explains how participant sign-in works."},"fieldLabel":{"type":"string","maxLength":100,"description":"The label for the participant email field."},"fieldPlaceholder":{"type":"string","maxLength":100,"description":"The placeholder shown inside the participant email field."},"buttonText":{"type":"string","maxLength":100,"description":"The text on the button that requests a secure sign-in link."},"successHeading":{"type":"string","maxLength":100,"description":"The heading shown after a sign-in link request is accepted."},"successBody":{"type":"string","maxLength":500,"description":"The privacy-safe message shown after a sign-in link request is accepted."},"resendPrompt":{"type":"string","maxLength":100,"description":"The prompt shown before the resend action."},"resend":{"type":"string","maxLength":100,"description":"The text on the action that requests another sign-in link."},"resent":{"type":"string","maxLength":500,"description":"The confirmation shown after another sign-in link is requested."},"invalidEmail":{"type":"string","maxLength":500,"description":"The validation message shown for an invalid email address."},"cooldown":{"type":"string","maxLength":500,"description":"The message shown when sign-in link requests are temporarily rate limited."},"serverError":{"type":"string","maxLength":500,"description":"The message shown when GrowSurf cannot request a sign-in link."},"invalidLink":{"type":"string","maxLength":500,"description":"The message shown when a sign-in link is invalid or expired."}}},"PayoutDestinationConfirmationDesign":{"type":"object","additionalProperties":false,"description":"Customizable text for the participant-facing payout-destination confirmation page. One shared set applies to every payout provider enabled for the program. Provider-aware text may use `{{payoutProvider}}`; send `null` or an empty string to restore that field's default text. Use provider-neutral text for `errorMessages.generic` because an unknown link may not identify a payout provider.","properties":{"headline":{"type":["string","null"],"maxLength":255,"description":"The page heading shown above the confirmation form."},"description":{"type":["string","null"],"maxLength":500,"description":"The instructions shown below the page heading."},"emailLabel":{"type":["string","null"],"maxLength":255,"description":"The label for the participant's payout email field."},"emailPlaceholder":{"type":["string","null"],"maxLength":255,"description":"The placeholder shown inside the payout email field."},"emailAgainLabel":{"type":["string","null"],"maxLength":255,"description":"The label for the field where the participant confirms their payout email."},"emailAgainPlaceholder":{"type":["string","null"],"maxLength":255,"description":"The placeholder shown inside the payout-email confirmation field."},"legalNameLabel":{"type":["string","null"],"maxLength":255,"description":"The label for the payout recipient's legal name."},"legalNamePlaceholder":{"type":["string","null"],"maxLength":255,"description":"The placeholder shown inside the legal-name field."},"legalTypeLabel":{"type":["string","null"],"maxLength":255,"description":"The label for choosing whether the payout recipient is an individual or business."},"legalTypeIndividual":{"type":["string","null"],"maxLength":255,"description":"The option text for an individual payout recipient."},"legalTypeBusiness":{"type":["string","null"],"maxLength":255,"description":"The option text for a business payout recipient."},"button":{"type":["string","null"],"maxLength":255,"description":"The text on the button that submits the confirmation form."},"success":{"type":["string","null"],"maxLength":500,"description":"The message shown after payout details are confirmed."},"claimPending":{"type":["string","null"],"maxLength":500,"description":"The message shown while GrowSurf finishes confirming the payout destination."},"errorMessages":{"description":"Validation and link-status messages shown on the payout-destination confirmation page.","$ref":"#/components/schemas/PayoutDestinationConfirmationErrorMessages"}}},"PayoutDestinationConfirmationErrorMessages":{"type":"object","additionalProperties":false,"description":"Customizable validation and link-status messages on the payout-destination confirmation page. Provider-aware states may use `{{payoutProvider}}`; send `null` or an empty string to restore that message's default text.","properties":{"invalidEmail":{"type":["string","null"],"maxLength":255,"description":"The validation message shown when the participant enters an invalid email address."},"emailMismatch":{"type":["string","null"],"maxLength":255,"description":"The validation message shown when the two email entries do not match."},"tokenExpired":{"type":["string","null"],"maxLength":255,"description":"The message shown when the confirmation link has expired."},"tokenUsed":{"type":["string","null"],"maxLength":255,"description":"The message shown when the confirmation link has already been used."},"alreadyConfirmed":{"type":["string","null"],"maxLength":255,"description":"The message shown when the payout destination is no longer waiting for confirmation."},"generic":{"type":["string","null"],"maxLength":255,"description":"The fallback message shown when the confirmation link cannot be used for another reason. Use provider-neutral text because an unknown link may not identify a payout provider."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/design":{"get":{"tags":["Campaigns"],"operationId":"retrieveCampaignDesign","summary":"Retrieve campaign design","description":"Retrieves a program's configured design fields: the dashboard Program Editor's **Design** tab plus the payout-destination confirmation page copy configured from payout integration cards. This includes the GrowSurf window layout, header, share channels and invites, signup form, portal and landing pages, theme styling, and referral or affiliate summary and status sections. The available fields depend on the program type. `payoutDestinationConfirmation` is omitted when no confirmation fields are stored. Stored `null` fields are returned as `null`; omitted and `null` fields use localized defaults.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Campaign design returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDesign"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update campaign design

> Updates a program's design configuration, including the payout-destination confirmation page copy configured from payout integration cards. Only the fields you send are changed; anything you leave out is untouched (arrays such as \`signup.fields\` replace wholesale). Unknown fields, fields not available for the program type, and invalid values return a \`400\`. Landing-page custom code and JavaScript are not editable via the API.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignDesign":{"type":"object","description":"A program's design configuration. It includes the dashboard Program Editor's Design tab and the payout-destination confirmation page copy configured from payout integration cards. The exact fields available depend on the program type (for example, `referralSummary` is referral-only, while `affiliateSummary`, `commissions`, and `payouts` are affiliate-only). `participantSettings` is available to both program types; its manual payout and Wise fields are affiliate-only. `GET` returns the fields configured for the program; `payoutDestinationConfirmation` is omitted when no confirmation fields are stored. Stored `null` fields are returned as `null`; omitted and `null` fields use localized defaults. `PATCH` back only the sections or fields you want to change (arrays such as `signup.fields` replace wholesale).","properties":{"participantAvatarStyle":{"type":"string","enum":["CHARACTERS","INITIALS","ANIMALS","GRADIENT"],"default":"CHARACTERS","description":"The participant avatar style. New programs start with `CHARACTERS`. Programs with a missing or unknown stored value return `INITIALS` for backward compatibility."},"window":{"type":"object","description":"How the sections inside the GrowSurf window are laid out. `navigationMode` chooses between a tabbed layout (`TABS`) and a single scrolling list (`LIST`)."},"header":{"type":"object","description":"Header content for participants (`postText`) and non-participants (`preText`)."},"stats":{"type":"object","description":"The stats panel a participant sees at the top of the GrowSurf window, summarizing their own referral progress. Only its heading text (`title`) is editable here."},"share":{"type":"object","description":"Share channels (`type.<channel>.{isVisible,buttonText,message,order}`), invite settings, and share-button styling."},"signup":{"type":"object","description":"Signup form (`fields[]`, GDPR consent, button and login text)."},"login":{"description":"Customizable text shown during participant sign-in.","$ref":"#/components/schemas/ParticipantLoginDesign"},"payoutDestinationConfirmation":{"description":"Customizable text for the participant-facing payout-destination confirmation page.","$ref":"#/components/schemas/PayoutDestinationConfirmationDesign"},"countryLabels":{"type":"object","description":"Participant-facing country-name overrides, keyed by ISO 3166-1 alpha-2 code (for example `GB`). Each label replaces the default country name wherever participants pick a country, such as payout and tax forms. Overrides merge per code, so send only the codes you want to change; send `null` (or the default name) for a code to restore its default. Unknown codes are rejected.","additionalProperties":{"type":["string","null"],"maxLength":500}},"referralStatus":{"type":"object","description":"The Referral Status section, which lists the people a participant has invited and each one's progress (invite sent, pending, awarded, or expired). Controls its title, icon, column and status labels, empty-state text, and whether invited emails are masked."},"leaderboard":{"type":"object","description":"The Leaderboard section that ranks participants against each other. Controls its title, icon, column labels, which time-period and ranking selectors appear, empty-state text, and how other participants' names are masked."},"referredExperience":{"type":"object","description":"The banner, headline, and Claim Offer Popup shown to someone who arrives on your site through a referral link. Controls their copy and placement, whether the popup is enabled, and how the referrer's name is formatted. The popup is available for both referral and affiliate programs.","properties":{"isOfferPopupEnabled":{"type":"boolean","description":"Whether referred visitors see the Claim Offer Popup. Affiliate programs start with it disabled until you opt in."},"offerPopupTitle":{"type":["string","null"],"maxLength":255,"description":"Popup heading. Supports the same dynamic-text variables as the dashboard editor."},"offerPopupDescription":{"type":["string","null"],"maxLength":255,"description":"Text shown below the popup heading."},"offerPopupButtonText":{"type":["string","null"],"maxLength":100,"description":"Text on the button that saves the visitor's offer."},"offerPopupImageUrl":{"type":["string","null"],"maxLength":500,"description":"Optional image shown in the popup."},"isOfferPopupReferrerImageShown":{"type":"boolean","description":"Whether the popup shows the referrer's profile image when one is available."},"offerPopupPlacement":{"type":"string","enum":["CENTER","BOTTOM","BOTTOM_RIGHT","BOTTOM_LEFT","TOP"],"description":"Where the popup appears. Small screens may use a bottom sheet."},"offerPopupDelaySeconds":{"type":"integer","enum":[0,3,5,10],"description":"Seconds to wait before showing the popup."},"offerPopupThankYouText":{"type":["string","null"],"maxLength":255,"description":"Message shown after the visitor saves the offer."},"offerPopupThankYouButtonText":{"type":["string","null"],"maxLength":100,"description":"Text on the post-claim button that opens the configured signup URL."},"isOfferPopupConfettiEnabled":{"type":"boolean","description":"Whether to show confetti after the visitor saves the offer."},"isOfferPopupShownOnAllPages":{"type":"boolean","description":"Whether the popup can appear on every page where GrowSurf is installed instead of only the Share URL."},"offerPopupSecondaryLinkText":{"type":["string","null"],"maxLength":100,"description":"Optional secondary-link text shown after the offer is saved."},"offerPopupSecondaryLinkUrl":{"type":["string","null"],"maxLength":255,"description":"Destination for the optional secondary link. Must start with `http://` or `https://`. Send `null` or an empty string to clear it."},"isOfferPopupOverlayDimmed":{"type":"boolean","description":"Whether the centered popup dims the page behind it."},"offerPopupEmailPlaceholder":{"type":["string","null"],"maxLength":100,"description":"Hint text inside the popup's email field. Leave it empty to reuse the email field label from your signup form."},"offerPopupPromoCodeCopyLabel":{"type":["string","null"],"maxLength":100,"description":"Text on the copy action beside the promo code, shown after the offer is claimed when an upfront-discount coupon is enabled."},"offerPopupSubmitError":{"type":["string","null"],"maxLength":255,"description":"Message shown inside the popup when the offer cannot be saved, such as during a connection problem."},"isBannerEnabled":{"type":"boolean","description":"Whether referred visitors see the welcome banner, a sticky banner that follows them as they scroll your Share URL."},"bannerText":{"type":["string","null"],"maxLength":150000,"description":"Welcome banner copy. Accepts HTML and the same dynamic-text variables as the dashboard editor."},"bannerPlacement":{"type":"string","enum":["TOP","BOTTOM"],"description":"Where the welcome banner sits on the page."},"isBannerClickableToSignupUrl":{"type":"boolean","description":"Whether clicking the welcome banner opens the program's signup URL. It has no effect until a signup URL is configured."},"isHeadingEnabled":{"type":"boolean","description":"Whether referred visitors see the inline welcome message added to the main heading on your Share URL."},"headingText":{"type":["string","null"],"maxLength":150000,"description":"Inline welcome message copy. Accepts HTML and the same dynamic-text variables as the dashboard editor."},"headingTarget":{"type":"string","enum":["H1","H2","H3","H4","H5"],"description":"Which heading element on the page the inline welcome message attaches to. GrowSurf uses the first match."},"headingPlacement":{"type":"string","enum":["PREPEND","APPEND","REPLACE"],"description":"Whether the inline welcome message goes before the target heading, after it, or replaces it."},"isHeadingStyled":{"type":"boolean","description":"Whether the inline welcome message keeps its own styling. When `false` it renders as plain text."},"isHeadingClickableToSignupUrl":{"type":"boolean","description":"Whether clicking the inline welcome message opens the program's signup URL. It has no effect until a signup URL is configured."},"pageTitleReplacement":{"type":["string","null"],"maxLength":150000,"description":"Browser tab title shown to referred visitors on your Share URL, in place of the page's own title."},"referrerNameFormat":{"type":"string","enum":["FIRST","FIRST_LAST_INITIAL","FIRST_LAST"],"description":"How the referrer's name appears in referred-visitor copy — `FIRST` (`Gavin`), `FIRST_LAST_INITIAL` (`Gavin B`), or `FIRST_LAST` (`Gavin Belson`)."},"referrerNameFallback":{"type":["string","null"],"maxLength":100,"description":"Name used in referred-visitor copy when the referrer has no first or last name on file."}}},"referralSummary":{"type":"object","description":"Referral programs only. The row of summary tiles a participant sees (clicks, leads, referrals, invites sent, rewards earned, and more). Controls each tile's label, tooltip, and visibility, plus the section's title and icon."},"affiliateSummary":{"type":"object","description":"Affiliate programs only. The row of summary tiles an affiliate sees (clicks, leads, referrals, referral revenue, upcoming payout, and total paid out). Controls each tile's label, tooltip, and visibility, plus the section's title and icon."},"commissions":{"type":"object","description":"Affiliate programs only. The Commissions section of the participant portal, which lists an affiliate's earned commissions and their status (pending, approved, paid, or reversed). Controls its title, icon, column labels, status labels and tooltips, and empty-state text."},"payouts":{"type":"object","description":"Affiliate programs only. The Payouts section of the participant portal, which lists an affiliate's payouts and their status (upcoming, queued, issued, failed, or reversed). Controls its title, icon, column labels, status labels and tooltips, and empty-state text."},"rewards":{"type":"object","description":"The heading, icon, and empty-state text of the rewards panel shown to participants. Which rewards appear, and the order they appear in, are managed through the campaign rewards endpoints, not here."},"resources":{"type":"object","additionalProperties":false,"description":"Participant presentation settings for Resources. Resource content and publication order are managed through the program Resources endpoints.","properties":{"isPublicDisplayed":{"type":"boolean","description":"Whether the Resources destination is enabled. It remains hidden until at least one valid resource is published."},"title":{"type":"string","maxLength":100,"description":"Participant-visible Resources section title."},"viewResourcesLinkText":{"type":"string","maxLength":100,"description":"LIST-mode row text."},"backLinkText":{"type":"string","maxLength":100,"description":"LIST-mode detail Back text."},"copyButtonText":{"type":"string","maxLength":100,"description":"TEXT resource copy action."},"copiedText":{"type":"string","maxLength":100,"description":"TEXT resource copy confirmation."},"emptyState":{"type":"string","maxLength":500,"description":"Text shown in place of the list when no resources are published."},"icon":{"type":"object","additionalProperties":false,"description":"Icon configuration for the Resources destination.","properties":{"type":{"type":"string","enum":["DEFAULT","IMAGE","NONE"],"description":"Whether to use the default icon, a configured image, or no icon."},"imageUrl":{"type":"string","maxLength":500,"description":"LIST-mode icon image URL when `type` is `IMAGE`."}}}}},"participantSettings":{"type":"object","description":"The participant's account settings area in the portal. Controls the labels and messages for logging out, managing PayPal and Wise payout emails (including confirmation, connected, expired, and repair states), and completing tax details (country of residency, VAT, and tax-form status). Wise payout copy applies to affiliate programs; the other fields also apply to eligible referral programs."},"landingPages":{"type":"object","description":"Portal and landing pages — company info, `content` (FAQ/terms/labels), `styles` (fonts), third-party script IDs (GA/GTM/Facebook/HubSpot), and SEO meta tags. Custom code, CSS, and JS are not editable via the API."},"theme":{"type":"object","description":"Visual theme styling (colors, shadows, and similar).","properties":{"referredExperienceOfferPopup":{"type":"object","description":"Paid-plan color settings for the Claim Offer Popup.","properties":{"color":{"type":["string","null"],"maxLength":255,"description":"Popup text color."},"backgroundColor":{"type":["string","null"],"maxLength":255,"description":"Popup background color."}}}}}}},"ParticipantLoginDesign":{"type":"object","description":"Customizable sign-in text shown when an existing participant asks GrowSurf for a secure sign-in link.","properties":{"heading":{"type":"string","maxLength":100,"description":"The heading above the participant sign-in form."},"description":{"type":"string","maxLength":500,"description":"The supporting text that explains how participant sign-in works."},"fieldLabel":{"type":"string","maxLength":100,"description":"The label for the participant email field."},"fieldPlaceholder":{"type":"string","maxLength":100,"description":"The placeholder shown inside the participant email field."},"buttonText":{"type":"string","maxLength":100,"description":"The text on the button that requests a secure sign-in link."},"successHeading":{"type":"string","maxLength":100,"description":"The heading shown after a sign-in link request is accepted."},"successBody":{"type":"string","maxLength":500,"description":"The privacy-safe message shown after a sign-in link request is accepted."},"resendPrompt":{"type":"string","maxLength":100,"description":"The prompt shown before the resend action."},"resend":{"type":"string","maxLength":100,"description":"The text on the action that requests another sign-in link."},"resent":{"type":"string","maxLength":500,"description":"The confirmation shown after another sign-in link is requested."},"invalidEmail":{"type":"string","maxLength":500,"description":"The validation message shown for an invalid email address."},"cooldown":{"type":"string","maxLength":500,"description":"The message shown when sign-in link requests are temporarily rate limited."},"serverError":{"type":"string","maxLength":500,"description":"The message shown when GrowSurf cannot request a sign-in link."},"invalidLink":{"type":"string","maxLength":500,"description":"The message shown when a sign-in link is invalid or expired."}}},"PayoutDestinationConfirmationDesign":{"type":"object","additionalProperties":false,"description":"Customizable text for the participant-facing payout-destination confirmation page. One shared set applies to every payout provider enabled for the program. Provider-aware text may use `{{payoutProvider}}`; send `null` or an empty string to restore that field's default text. Use provider-neutral text for `errorMessages.generic` because an unknown link may not identify a payout provider.","properties":{"headline":{"type":["string","null"],"maxLength":255,"description":"The page heading shown above the confirmation form."},"description":{"type":["string","null"],"maxLength":500,"description":"The instructions shown below the page heading."},"emailLabel":{"type":["string","null"],"maxLength":255,"description":"The label for the participant's payout email field."},"emailPlaceholder":{"type":["string","null"],"maxLength":255,"description":"The placeholder shown inside the payout email field."},"emailAgainLabel":{"type":["string","null"],"maxLength":255,"description":"The label for the field where the participant confirms their payout email."},"emailAgainPlaceholder":{"type":["string","null"],"maxLength":255,"description":"The placeholder shown inside the payout-email confirmation field."},"legalNameLabel":{"type":["string","null"],"maxLength":255,"description":"The label for the payout recipient's legal name."},"legalNamePlaceholder":{"type":["string","null"],"maxLength":255,"description":"The placeholder shown inside the legal-name field."},"legalTypeLabel":{"type":["string","null"],"maxLength":255,"description":"The label for choosing whether the payout recipient is an individual or business."},"legalTypeIndividual":{"type":["string","null"],"maxLength":255,"description":"The option text for an individual payout recipient."},"legalTypeBusiness":{"type":["string","null"],"maxLength":255,"description":"The option text for a business payout recipient."},"button":{"type":["string","null"],"maxLength":255,"description":"The text on the button that submits the confirmation form."},"success":{"type":["string","null"],"maxLength":500,"description":"The message shown after payout details are confirmed."},"claimPending":{"type":["string","null"],"maxLength":500,"description":"The message shown while GrowSurf finishes confirming the payout destination."},"errorMessages":{"description":"Validation and link-status messages shown on the payout-destination confirmation page.","$ref":"#/components/schemas/PayoutDestinationConfirmationErrorMessages"}}},"PayoutDestinationConfirmationErrorMessages":{"type":"object","additionalProperties":false,"description":"Customizable validation and link-status messages on the payout-destination confirmation page. Provider-aware states may use `{{payoutProvider}}`; send `null` or an empty string to restore that message's default text.","properties":{"invalidEmail":{"type":["string","null"],"maxLength":255,"description":"The validation message shown when the participant enters an invalid email address."},"emailMismatch":{"type":["string","null"],"maxLength":255,"description":"The validation message shown when the two email entries do not match."},"tokenExpired":{"type":["string","null"],"maxLength":255,"description":"The message shown when the confirmation link has expired."},"tokenUsed":{"type":["string","null"],"maxLength":255,"description":"The message shown when the confirmation link has already been used."},"alreadyConfirmed":{"type":["string","null"],"maxLength":255,"description":"The message shown when the payout destination is no longer waiting for confirmation."},"generic":{"type":["string","null"],"maxLength":255,"description":"The fallback message shown when the confirmation link cannot be used for another reason. Use provider-neutral text because an unknown link may not identify a payout provider."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/design":{"patch":{"tags":["Campaigns"],"operationId":"updateCampaignDesign","summary":"Update campaign design","description":"Updates a program's design configuration, including the payout-destination confirmation page copy configured from payout integration cards. Only the fields you send are changed; anything you leave out is untouched (arrays such as `signup.fields` replace wholesale). Unknown fields, fields not available for the program type, and invalid values return a `400`. Landing-page custom code and JavaScript are not editable via the API.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDesign"}}}},"responses":{"200":{"description":"Campaign design updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDesign"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## CAMPAIGN EMAILS ↓

{% hint style="info" %}
Read or update a program's **Emails** tab — each editable email template (`subject`, `preheader`, `body`, `isEnabled`) plus the shared `settings` block (sender, contact, and design). The set of templates depends on the program type (referral or affiliate). `PATCH` is a partial merge (send only what you want to change); writing a template that is not available for the program type (referral or affiliate) returns a `400`, and the custom sender `fromEmail` is read-only (it requires dashboard domain verification).
{% endhint %}

## Retrieve campaign emails

> Retrieves a program's email configuration — the same surface as the dashboard Program Editor's \*\*Emails\*\* tab. Returns each editable email template (\`subject\`, \`preheader\`, \`body\`, \`isEnabled\`) plus the \`settings\` block (sender, contact, and design). The set of email templates returned depends on the program type (referral vs affiliate).

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignEmails":{"type":"object","description":"A program's email configuration (the dashboard Program Editor's Emails tab). Each property is an editable email template; the set of templates available depends on the program type (referral vs affiliate), and writing a template that is not available for the program type returns a `400`. The `settings` object holds sender, contact, and design settings. To see the full object with every template and its current values, `GET` this resource, then `PATCH` back only the fields you want to change.","properties":{"welcomeNonReferred":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Welcome email sent to a participant who joins on their own, without being referred. Available for both referral and affiliate programs."},"welcomeReferred":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Welcome email sent to someone who signs up through another participant's referral link (also covers referrals you add from the dashboard). Referral programs only."},"offerClaimed":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a referred visitor who saves an offer through the Claim Offer Popup. It sends only while the popup is enabled and replaces the welcome-referred email for that visitor, so they receive one email. Available for both referral and affiliate programs."},"referralLinkViewedFirstTime":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a participant the first time their referral link is viewed. It goes out only once. Available for both referral and affiliate programs."},"referralLinkUsed":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a referrer when they earn referral credit because someone they referred completed the qualifying action. Most useful when a reward needs more than one referral to unlock. Labeled \"Referral Credit Received\" in the Program Editor. Referral programs only."},"referredSignup":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a referrer each time someone signs up using their referral link. Labeled \"Referral Link Used\" in the Program Editor. Available for both referral and affiliate programs."},"goalAchieved":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a participant when they unlock a reward (and to the referred friend as well, for double-sided rewards). Labeled \"Reward Unlocked\" in the Program Editor. Referral programs only."},"campaignEndedWinners":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to every participant who unlocked at least one reward, once the program ends. Referral programs only."},"campaignEndedNonWinners":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to every participant who did not unlock a reward, once the program ends. Referral programs only."},"progressUpdateMonthly":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"A month-end summary emailed to participants recapping how they are doing in the program. Available for both referral and affiliate programs."},"commissionGenerated":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to an affiliate when they earn a new commission, for example when a customer they referred makes a purchase. Affiliate programs only."},"commissionAdjusted":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to an affiliate when one of their commissions is adjusted because of a full refund, partial refund, or chargeback. Affiliate programs only."},"payoutPending":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to an affiliate when a batch payment is started and they have a payout on the way. Affiliate programs only."},"payoutSentSuccess":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to an affiliate when their payout completes successfully. Labeled \"Payout Sent Successfully\" in the Program Editor. Affiliate programs only."},"invite":{"$ref":"#/components/schemas/CampaignInviteEmailTemplate","description":"The invitation email a participant sends to friends. Applies only when the program sends invites on the company's behalf (Design > Share > Invite, with your company as the sender). Its `useCompanyReplyTo` field sets whether replies go to your company or to the participant who sent the invite. Available for both referral and affiliate programs."},"loginLink":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"The one-time sign-in link emailed to returning participants. Applies when participant sign-in is required (in Options). Labeled \"One-Time Login Link\" in the Program Editor. This is a transactional email, so its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"payoutDestinationConfirmation":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Asks a participant to confirm the payout destination where they will receive payouts (for example a PayPal or Wise email address). The body may use `{{payoutProvider}}` for the provider name and must keep the `{{payoutDestinationConfirmationLink}}` confirmation link. This is a transactional email, so its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"payoutDestinationChanged":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells a participant that their payout destination changed. The body must keep `{{payoutDestinationMaskedEmail}}` so the notice identifies the new destination without showing the full address. This is a transactional email, so its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"taxInfoMissing":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Asks a participant to submit the tax information required before they can be paid. Sends only when the program collects tax information. Its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"taxInfoReceived":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Confirms to a participant that the tax information they submitted was received. Its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"taxInfoApproved":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells a participant that their tax form is complete and approved. Labeled \"Tax Form Complete\" in the Program Editor. Its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"taxInfoRejected":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells a participant that their tax information was rejected and needs to be resubmitted. Its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"affiliateApplicationReceived":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Confirms to an applicant that their affiliate application was received and is under review. Sends when the program reviews public signups (an `affiliateApplicationMode` of `MANUAL_REVIEW` or `AUTO_APPROVE`). This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"affiliateApplicationApproved":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells an applicant that their affiliate application was approved and welcomes them into the program. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"affiliateApplicationDenied":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells an applicant that their affiliate application was not approved. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"inviteAffiliate":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Invites a prospective affiliate to join the program. The body must keep `{{affiliateInviteLink}}`. This is a promotional email, so its on/off toggle can be changed. Affiliate programs only."},"affiliateApplicationStatusLink":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sends an applicant a secure link to view their affiliate application status. Applies when `affiliateApplicationMode` is `MANUAL_REVIEW` or `AUTO_APPROVE`. The body must keep `{{applicationStatusLink}}`. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"affiliateApplicationEmailCorrection":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Asks an applicant to confirm a corrected email address before their affiliate application is updated. The body must contain `{{identityVerificationLink}}`. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"affiliateEmailChangeVerification":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Asks an affiliate to confirm a new account email address before it is updated. The body must contain `{{identityVerificationLink}}`. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"settings":{"$ref":"#/components/schemas/CampaignEmailSettings","description":"Sender, contact, and design settings for the program's emails (the Emails > Settings sub-page)."}}},"CampaignEmailTemplate":{"type":"object","description":"A single editable email template. `subject` and `preheader` are capped at 255 characters; `body` is HTML capped at 150000 characters.","properties":{"subject":{"type":"string","maxLength":255,"description":"The email's subject line. Supports dynamic text (`{{...}}` tokens), the same as the body."},"preheader":{"type":"string","maxLength":255,"description":"The preview text shown after the subject line in most inboxes."},"body":{"type":"string","maxLength":150000,"description":"The email body as HTML. You can personalize it with dynamic text, inserting `{{...}}` tokens like `{{firstName}}` or `{{shareUrl}}` to tailor each email to its recipient. See [Guide to using dynamic text in GrowSurf emails](https://support.growsurf.com/article/213-guide-to-using-dynamic-text-in-growsurf-emails)."},"isEnabled":{"type":"boolean","description":"Whether this email is enabled. Read-only for transactional emails and the participant-to-friend `invite` email; promotional emails can be toggled."}}},"CampaignInviteEmailTemplate":{"description":"The participant-to-friend `invite` email. It is the only template with a reply-to choice; every other template uses your program's reply-to address.","allOf":[{"$ref":"#/components/schemas/CampaignEmailTemplate"},{"type":"object","properties":{"useCompanyReplyTo":{"type":"boolean","description":"When `true`, replies go to your company's reply-to address, which keeps the referring participant's email address private (it is never exposed to the people they invite). When `false`, replies go to the referring participant."}}}]},"CampaignEmailSettings":{"type":"object","description":"Email sender, contact, and design settings (the Emails → Settings sub-page).","properties":{"sender":{"description":"Sender name and reply-to settings for program emails.","$ref":"#/components/schemas/CampaignEmailSenderSettings"},"contact":{"description":"Physical mailing address shown in program email footers.","$ref":"#/components/schemas/CampaignEmailContactSettings"},"design":{"description":"Design settings shared across program emails.","$ref":"#/components/schemas/CampaignEmailDesignSettings"}}},"CampaignEmailSenderSettings":{"type":"object","properties":{"fromName":{"type":"string","maxLength":100,"description":"The email sender name. Required; cannot be an email address."},"replyToEmail":{"type":"string","maxLength":100,"description":"The reply-to email address. Required; must be a valid email address."},"fromEmail":{"type":"string","readOnly":true,"description":"The from email address. Read-only — setting a custom from-address requires domain verification in the dashboard."}}},"CampaignEmailContactSettings":{"type":"object","description":"The physical mailing address shown in your email footers (required by anti-spam laws such as the US CAN-SPAM Act).","properties":{"companyName":{"type":"string","maxLength":255,"description":"Required."},"addressLine1":{"type":"string","maxLength":255,"description":"Required."},"addressLine2":{"description":"Second address line shown in email footers.","type":["string","null"],"maxLength":255},"city":{"type":"string","maxLength":255,"description":"Required."},"state":{"description":"State, province, or region shown in email footers.","type":["string","null"],"maxLength":255},"postalCode":{"description":"Postal or ZIP code shown in email footers.","type":["string","null"],"maxLength":100},"country":{"type":["string","null"],"maxLength":100,"description":"Free-text country (not validated as an ISO code)."}}},"CampaignEmailDesignSettings":{"type":"object","properties":{"header":{"type":["string","null"],"maxLength":150000,"description":"Email header HTML."},"footer":{"type":["string","null"],"maxLength":150000,"description":"Email footer HTML."},"unsubscribePromotional":{"type":"string","maxLength":150000,"description":"Unsubscribe footer for promotional emails. Must contain the tokens `{{unsubscribeLink}}`, `{{companyName}}`, and `{{address}}`."},"unsubscribeInvite":{"type":"string","maxLength":150000,"description":"Unsubscribe footer for invite emails. Must contain the tokens `{{unsubscribeLink}}`, `{{companyName}}`, and `{{address}}`."},"unsubscribeAffiliateInvite":{"type":"string","maxLength":150000,"description":"Unsubscribe footer for direct affiliate invitation emails. Must contain the tokens `{{unsubscribeLink}}`, `{{companyName}}`, and `{{address}}`."},"unsubscribeTransactional":{"type":"string","maxLength":150000,"description":"Footer for transactional emails. Must contain the tokens `{{companyName}}` and `{{address}}`."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/emails":{"get":{"tags":["Campaigns"],"operationId":"retrieveCampaignEmails","summary":"Retrieve campaign emails","description":"Retrieves a program's email configuration — the same surface as the dashboard Program Editor's **Emails** tab. Returns each editable email template (`subject`, `preheader`, `body`, `isEnabled`) plus the `settings` block (sender, contact, and design). The set of email templates returned depends on the program type (referral vs affiliate).","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Campaign emails configuration returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignEmails"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update campaign emails

> Updates a program's email configuration. Only the fields you send are changed; omitted fields are left untouched. You may only write the email templates the dashboard exposes for the program type — writing a template that is not available for the program type returns a \`400\`. Some fields are read-only (\`settings.sender.fromEmail\`, whose custom value requires dashboard domain verification).

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignEmails":{"type":"object","description":"A program's email configuration (the dashboard Program Editor's Emails tab). Each property is an editable email template; the set of templates available depends on the program type (referral vs affiliate), and writing a template that is not available for the program type returns a `400`. The `settings` object holds sender, contact, and design settings. To see the full object with every template and its current values, `GET` this resource, then `PATCH` back only the fields you want to change.","properties":{"welcomeNonReferred":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Welcome email sent to a participant who joins on their own, without being referred. Available for both referral and affiliate programs."},"welcomeReferred":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Welcome email sent to someone who signs up through another participant's referral link (also covers referrals you add from the dashboard). Referral programs only."},"offerClaimed":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a referred visitor who saves an offer through the Claim Offer Popup. It sends only while the popup is enabled and replaces the welcome-referred email for that visitor, so they receive one email. Available for both referral and affiliate programs."},"referralLinkViewedFirstTime":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a participant the first time their referral link is viewed. It goes out only once. Available for both referral and affiliate programs."},"referralLinkUsed":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a referrer when they earn referral credit because someone they referred completed the qualifying action. Most useful when a reward needs more than one referral to unlock. Labeled \"Referral Credit Received\" in the Program Editor. Referral programs only."},"referredSignup":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a referrer each time someone signs up using their referral link. Labeled \"Referral Link Used\" in the Program Editor. Available for both referral and affiliate programs."},"goalAchieved":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to a participant when they unlock a reward (and to the referred friend as well, for double-sided rewards). Labeled \"Reward Unlocked\" in the Program Editor. Referral programs only."},"campaignEndedWinners":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to every participant who unlocked at least one reward, once the program ends. Referral programs only."},"campaignEndedNonWinners":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to every participant who did not unlock a reward, once the program ends. Referral programs only."},"progressUpdateMonthly":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"A month-end summary emailed to participants recapping how they are doing in the program. Available for both referral and affiliate programs."},"commissionGenerated":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to an affiliate when they earn a new commission, for example when a customer they referred makes a purchase. Affiliate programs only."},"commissionAdjusted":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to an affiliate when one of their commissions is adjusted because of a full refund, partial refund, or chargeback. Affiliate programs only."},"payoutPending":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to an affiliate when a batch payment is started and they have a payout on the way. Affiliate programs only."},"payoutSentSuccess":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sent to an affiliate when their payout completes successfully. Labeled \"Payout Sent Successfully\" in the Program Editor. Affiliate programs only."},"invite":{"$ref":"#/components/schemas/CampaignInviteEmailTemplate","description":"The invitation email a participant sends to friends. Applies only when the program sends invites on the company's behalf (Design > Share > Invite, with your company as the sender). Its `useCompanyReplyTo` field sets whether replies go to your company or to the participant who sent the invite. Available for both referral and affiliate programs."},"loginLink":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"The one-time sign-in link emailed to returning participants. Applies when participant sign-in is required (in Options). Labeled \"One-Time Login Link\" in the Program Editor. This is a transactional email, so its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"payoutDestinationConfirmation":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Asks a participant to confirm the payout destination where they will receive payouts (for example a PayPal or Wise email address). The body may use `{{payoutProvider}}` for the provider name and must keep the `{{payoutDestinationConfirmationLink}}` confirmation link. This is a transactional email, so its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"payoutDestinationChanged":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells a participant that their payout destination changed. The body must keep `{{payoutDestinationMaskedEmail}}` so the notice identifies the new destination without showing the full address. This is a transactional email, so its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"taxInfoMissing":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Asks a participant to submit the tax information required before they can be paid. Sends only when the program collects tax information. Its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"taxInfoReceived":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Confirms to a participant that the tax information they submitted was received. Its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"taxInfoApproved":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells a participant that their tax form is complete and approved. Labeled \"Tax Form Complete\" in the Program Editor. Its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"taxInfoRejected":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells a participant that their tax information was rejected and needs to be resubmitted. Its on/off toggle cannot be changed. Available for both referral and affiliate programs."},"affiliateApplicationReceived":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Confirms to an applicant that their affiliate application was received and is under review. Sends when the program reviews public signups (an `affiliateApplicationMode` of `MANUAL_REVIEW` or `AUTO_APPROVE`). This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"affiliateApplicationApproved":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells an applicant that their affiliate application was approved and welcomes them into the program. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"affiliateApplicationDenied":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Tells an applicant that their affiliate application was not approved. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"inviteAffiliate":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Invites a prospective affiliate to join the program. The body must keep `{{affiliateInviteLink}}`. This is a promotional email, so its on/off toggle can be changed. Affiliate programs only."},"affiliateApplicationStatusLink":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Sends an applicant a secure link to view their affiliate application status. Applies when `affiliateApplicationMode` is `MANUAL_REVIEW` or `AUTO_APPROVE`. The body must keep `{{applicationStatusLink}}`. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"affiliateApplicationEmailCorrection":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Asks an applicant to confirm a corrected email address before their affiliate application is updated. The body must contain `{{identityVerificationLink}}`. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"affiliateEmailChangeVerification":{"$ref":"#/components/schemas/CampaignEmailTemplate","description":"Asks an affiliate to confirm a new account email address before it is updated. The body must contain `{{identityVerificationLink}}`. This is a transactional email, so its on/off toggle cannot be changed. Affiliate programs only."},"settings":{"$ref":"#/components/schemas/CampaignEmailSettings","description":"Sender, contact, and design settings for the program's emails (the Emails > Settings sub-page)."}}},"CampaignEmailTemplate":{"type":"object","description":"A single editable email template. `subject` and `preheader` are capped at 255 characters; `body` is HTML capped at 150000 characters.","properties":{"subject":{"type":"string","maxLength":255,"description":"The email's subject line. Supports dynamic text (`{{...}}` tokens), the same as the body."},"preheader":{"type":"string","maxLength":255,"description":"The preview text shown after the subject line in most inboxes."},"body":{"type":"string","maxLength":150000,"description":"The email body as HTML. You can personalize it with dynamic text, inserting `{{...}}` tokens like `{{firstName}}` or `{{shareUrl}}` to tailor each email to its recipient. See [Guide to using dynamic text in GrowSurf emails](https://support.growsurf.com/article/213-guide-to-using-dynamic-text-in-growsurf-emails)."},"isEnabled":{"type":"boolean","description":"Whether this email is enabled. Read-only for transactional emails and the participant-to-friend `invite` email; promotional emails can be toggled."}}},"CampaignInviteEmailTemplate":{"description":"The participant-to-friend `invite` email. It is the only template with a reply-to choice; every other template uses your program's reply-to address.","allOf":[{"$ref":"#/components/schemas/CampaignEmailTemplate"},{"type":"object","properties":{"useCompanyReplyTo":{"type":"boolean","description":"When `true`, replies go to your company's reply-to address, which keeps the referring participant's email address private (it is never exposed to the people they invite). When `false`, replies go to the referring participant."}}}]},"CampaignEmailSettings":{"type":"object","description":"Email sender, contact, and design settings (the Emails → Settings sub-page).","properties":{"sender":{"description":"Sender name and reply-to settings for program emails.","$ref":"#/components/schemas/CampaignEmailSenderSettings"},"contact":{"description":"Physical mailing address shown in program email footers.","$ref":"#/components/schemas/CampaignEmailContactSettings"},"design":{"description":"Design settings shared across program emails.","$ref":"#/components/schemas/CampaignEmailDesignSettings"}}},"CampaignEmailSenderSettings":{"type":"object","properties":{"fromName":{"type":"string","maxLength":100,"description":"The email sender name. Required; cannot be an email address."},"replyToEmail":{"type":"string","maxLength":100,"description":"The reply-to email address. Required; must be a valid email address."},"fromEmail":{"type":"string","readOnly":true,"description":"The from email address. Read-only — setting a custom from-address requires domain verification in the dashboard."}}},"CampaignEmailContactSettings":{"type":"object","description":"The physical mailing address shown in your email footers (required by anti-spam laws such as the US CAN-SPAM Act).","properties":{"companyName":{"type":"string","maxLength":255,"description":"Required."},"addressLine1":{"type":"string","maxLength":255,"description":"Required."},"addressLine2":{"description":"Second address line shown in email footers.","type":["string","null"],"maxLength":255},"city":{"type":"string","maxLength":255,"description":"Required."},"state":{"description":"State, province, or region shown in email footers.","type":["string","null"],"maxLength":255},"postalCode":{"description":"Postal or ZIP code shown in email footers.","type":["string","null"],"maxLength":100},"country":{"type":["string","null"],"maxLength":100,"description":"Free-text country (not validated as an ISO code)."}}},"CampaignEmailDesignSettings":{"type":"object","properties":{"header":{"type":["string","null"],"maxLength":150000,"description":"Email header HTML."},"footer":{"type":["string","null"],"maxLength":150000,"description":"Email footer HTML."},"unsubscribePromotional":{"type":"string","maxLength":150000,"description":"Unsubscribe footer for promotional emails. Must contain the tokens `{{unsubscribeLink}}`, `{{companyName}}`, and `{{address}}`."},"unsubscribeInvite":{"type":"string","maxLength":150000,"description":"Unsubscribe footer for invite emails. Must contain the tokens `{{unsubscribeLink}}`, `{{companyName}}`, and `{{address}}`."},"unsubscribeAffiliateInvite":{"type":"string","maxLength":150000,"description":"Unsubscribe footer for direct affiliate invitation emails. Must contain the tokens `{{unsubscribeLink}}`, `{{companyName}}`, and `{{address}}`."},"unsubscribeTransactional":{"type":"string","maxLength":150000,"description":"Footer for transactional emails. Must contain the tokens `{{companyName}}` and `{{address}}`."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/emails":{"patch":{"tags":["Campaigns"],"operationId":"updateCampaignEmails","summary":"Update campaign emails","description":"Updates a program's email configuration. Only the fields you send are changed; omitted fields are left untouched. You may only write the email templates the dashboard exposes for the program type — writing a template that is not available for the program type returns a `400`. Some fields are read-only (`settings.sender.fromEmail`, whose custom value requires dashboard domain verification).","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignEmails"}}}},"responses":{"200":{"description":"Campaign emails configuration updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignEmails"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## CAMPAIGN OPTIONS ↓

{% hint style="info" %}
Read or update a program's **Options** tab — attribution, reward/fraud approval, anti-fraud lists and toggles, referral cookie/credit windows, reCAPTCHA, affiliate enrollment and application review, payout threshold + tax settings (affiliate programs only), and notification-email settings. `PATCH` is a partial merge (send only what you want to change). `attributionModel` is `LAST_CLICK` or `FIRST_CLICK`; a missing legacy value reads as `LAST_CLICK`. The existing `referralCookieWindowDays` value is also the attribution window. Changing `attributionModel` requires a paid plan. Some fields are program-type specific (`requireManualRewardApproval`/`autoFulfillRewards` are referral program only; `affiliateApplicationMode`/`affiliateReapplicationPolicy` and `payoutThreshold`/`taxDocumentation` are affiliate-only). `fraud.recaptcha.secretKey` is write-only (never returned), and `referralCreditWindowDays: null` means the credit never expires.
{% endhint %}

## Retrieve campaign options

> Retrieves a program's options — the same surface as the dashboard Program Editor's \*\*Options\*\* tab. Includes the attribution model, reward/fraud approval, anti-fraud lists + toggles, referral cookie/credit windows, reCAPTCHA, affiliate enrollment + application review, payout threshold + tax settings (affiliate only), and notification-email settings. \`fraud.recaptcha.secretKey\` is never returned.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignOptions":{"type":"object","description":"A program's options (the dashboard Program Editor's Options tab). To see the full object with every field and its current value, `GET` this resource, then `PATCH` back only the fields you want to change. Some fields are program-type specific (see the per-field notes).","properties":{"affiliateApplicationMode":{"type":"string","enum":["OPEN_ENROLLMENT","MANUAL_REVIEW","AUTO_APPROVE"],"description":"Affiliate programs only. How public signups join the program. `OPEN_ENROLLMENT` enrolls signups directly (the default for programs created before applications existed). `MANUAL_REVIEW` collects an application you approve or deny. `AUTO_APPROVE` collects the full application and approves it immediately. Switching to a reviewed mode requires a saved Program Page. Trusted enrollment through the API, CSV import, dashboard, or invites is never blocked by this setting."},"affiliateReapplicationPolicy":{"type":"string","enum":["AFTER_COOLDOWN","DISABLED"],"description":"Affiliate programs only. Whether a denied applicant may apply again. `AFTER_COOLDOWN` (the default) allows a new application once `affiliateReapplicationCooldownDays` has passed; `DISABLED` never allows one."},"affiliateReapplicationCooldownDays":{"type":"integer","minimum":1,"maximum":365,"description":"Affiliate programs only. How many days a denied applicant waits before they can apply again (1-365, default 30). Only used when `affiliateReapplicationPolicy` is `AFTER_COOLDOWN`."},"affiliateApplicationReviewEstimateBusinessDays":{"type":["integer","null"],"minimum":1,"maximum":60,"description":"Affiliate programs only. Optional review-time expectation (in business days, 1-60) shown to pending applicants. Send `null` to clear it; when unset, applicants see neutral \"we'll email you\" copy instead."},"requireManualRewardApproval":{"type":"boolean","description":"Referral programs only. When `true`, each reward a participant earns is held for you to manually approve before it unlocks (and before the reward-won email is sent); when `false`, earned rewards are approved automatically. Cannot be `true` together with `autoFulfillRewards`."},"autoFulfillRewards":{"type":"boolean","description":"Referral programs only. When `true`, rewards a participant earns are automatically marked as fulfilled (issued) instead of you marking each one by hand, usually paired with an automated reward-delivery integration. Cannot be `true` together with `requireManualRewardApproval`."},"requireManualFraudApproval":{"type":"boolean","description":"When `true`, no signup is automatically blocked for fraud. Every participant is allowed to join, and any suspected fraud is flagged in your dashboard for you to review. Cannot be `true` together with `autoBlockFraud`."},"autoBlockFraud":{"type":"boolean","description":"When `true`, signups flagged as high fraud risk are automatically blocked from joining; when `false`, they are allowed in and flagged in your dashboard for review. Cannot be `true` together with `requireManualFraudApproval`."},"requireParticipantAuth":{"type":"boolean","description":"Require returning participants to authenticate. Affiliate programs require this to be `true`."},"enforceGdprCompliance":{"type":"boolean","description":"When `true`, GrowSurf stores only the minimum participant data needed to help you meet GDPR/CCPA requirements. Some participant fields are omitted and fraud screening is limited while this is on."},"blockPaidAdsTraffic":{"type":"boolean","description":"When `true`, referrals from visitors who arrived through paid ads (for example Google or Facebook ads) are not attributed, so the referring participant is not credited for them. Recommended so participants don't compete with you for your own paid keywords."},"attributionModel":{"type":"string","enum":["LAST_CLICK","FIRST_CLICK"],"default":"LAST_CLICK","description":"Selects which server-verified visit receives conversion credit within `referralCookieWindowDays`. `LAST_CLICK` is used when this field is missing, including for programs created before attribution models were configurable."},"referralCookieWindowDays":{"type":"integer","enum":[1,3,7,14,30,60,90,180,365,400],"description":"How long, in days, a referral-link click is remembered in the visitor's browser. If the visitor signs up within this window, the referral is credited to the participant whose link they clicked."},"referralCreditWindowDays":{"type":["integer","null"],"enum":[1,3,7,14,30,60,90,180,365,null],"description":"How long, in days, a referred friend has to complete a qualifying action for their referrer to earn credit and unlock a reward. After this window the referral no longer counts toward rewards. `null` = never expires."},"payoutThreshold":{"type":["integer","null"],"minimum":0,"description":"Affiliate programs only. Minimum payout in the program's currency minor units (`0` or `null` = no minimum). Requires `currencyISO` to be set."},"fraud":{"$ref":"#/components/schemas/CampaignOptionsFraud","description":"Anti-fraud settings — block and allow lists for emails, IPs, and countries; toggles for blocking burner emails, data-center IPs, and referrals from high-risk referrers; per-IP signup rate limits; and reCAPTCHA."},"taxDocumentation":{"$ref":"#/components/schemas/CampaignOptionsTaxDocumentation","description":"Affiliate programs only. Your company's billing details (name, address, and VAT number) used on affiliate payout invoices and for VAT handling."},"notificationEmails":{"$ref":"#/components/schemas/CampaignOptionsNotificationEmails","description":"Owner notification-email settings — who receives program notifications and which events they are notified about."}}},"CampaignOptionsFraud":{"type":"object","description":"Anti-fraud settings.","properties":{"blockedEmails":{"type":"array","items":{"type":"string","maxLength":100},"description":"Blocked emails (wildcards allowed). Max 10000. Replaces the whole list."},"blockedIps":{"type":"array","items":{"type":"string","maxLength":100},"description":"Blocked IP addresses (wildcards allowed). Max 10000."},"blockedCountries":{"type":"array","items":{"type":"string","maxLength":100},"description":"Blocked countries (ISO 3166-1 alpha-2 codes)."},"allowedEmails":{"description":"Allowed email addresses or patterns. Replaces the whole list.","type":"array","items":{"type":"string","maxLength":100}},"allowedIps":{"description":"Allowed IP addresses or patterns. Replaces the whole list.","type":"array","items":{"type":"string","maxLength":100}},"allowedCountries":{"type":"array","items":{"type":"string","maxLength":100},"description":"Allowed countries (ISO 3166-1 alpha-2 codes)."},"blockBurnerEmails":{"description":"Whether signups from burner or disposable email addresses are blocked.","type":"boolean"},"blockDataCenterIps":{"description":"Whether signups from known data-center IP addresses are blocked.","type":"boolean"},"blockHighRiskReferrers":{"description":"Whether referrals from participants marked as high fraud risk are blocked.","type":"boolean"},"autoBlockHighRiskIps":{"description":"Whether high-risk signup IP addresses are added to the blocked IP list automatically.","type":"boolean"},"maxSignupsPerIp2Min":{"description":"Maximum signups allowed from one IP address in a 2-minute window.","type":"integer","minimum":1,"maximum":100},"maxSignupsPerIp10Min":{"description":"Maximum signups allowed from one IP address in a 10-minute window.","type":"integer","minimum":1,"maximum":100},"recaptcha":{"description":"reCAPTCHA settings for signup forms.","$ref":"#/components/schemas/CampaignOptionsRecaptcha"}}},"CampaignOptionsRecaptcha":{"type":"object","properties":{"isEnabled":{"description":"Whether reCAPTCHA is enabled on signup forms.","type":"boolean"},"siteKey":{"description":"Public reCAPTCHA site key used by the signup form.","type":["string","null"],"maxLength":100},"secretKey":{"type":["string","null"],"maxLength":100,"writeOnly":true,"description":"The reCAPTCHA secret key. Write-only — never returned by GET."}}},"CampaignOptionsTaxDocumentation":{"type":"object","description":"Affiliate programs only. Your company's billing details (name, address, and VAT number) used on affiliate payout invoices and for VAT handling.","properties":{"companyName":{"description":"Legal or billing company name used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"vatNumber":{"description":"Your company VAT number, when VAT collection applies.","type":["string","null"],"maxLength":100},"addressLine1":{"description":"First line of the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"addressLine2":{"description":"Second line of the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"city":{"description":"City for the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"state":{"description":"State, province, or region for the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"postalCode":{"description":"Postal or ZIP code for the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":100},"country":{"type":["string","null"],"description":"ISO 3166-1 alpha-2 country code."},"collectAffiliateVat":{"type":"boolean","description":"Requires `vatNumber` to be set."}}},"CampaignOptionsNotificationEmails":{"type":"object","description":"Owner notification-email settings.","properties":{"recipients":{"type":"array","items":{"type":"string"},"description":"Email addresses that receive program notifications."},"events":{"type":"object","description":"Per-event on/off toggles. The available events depend on the program type.","properties":{"PARTICIPANT_REACHED_A_GOAL":{"description":"Notify recipients when a participant unlocks a reward.","type":"boolean"},"NEW_PARTICIPANT_ADDED_NON_REFERRED":{"description":"Notify recipients when a participant joins without being referred.","type":"boolean"},"NEW_PARTICIPANT_ADDED_REFERRED":{"description":"Notify recipients when a participant joins through a referral link.","type":"boolean"},"CAMPAIGN_ENDED":{"description":"Notify recipients when the program ends.","type":"boolean"},"WEEKLY_PERFORMANCE_REPORT":{"description":"Send recipients the weekly performance report.","type":"boolean"},"MONTHLY_PERFORMANCE_REPORT":{"description":"Send recipients the monthly performance report.","type":"boolean"},"NEW_COMMISSION_ADDED":{"description":"Notify recipients when an affiliate earns a new commission.","type":"boolean"},"COMMISSION_ADJUSTED":{"description":"Notify recipients when an affiliate commission is adjusted.","type":"boolean"},"NEW_PAYOUT_ISSUED":{"description":"Notify recipients when an affiliate payout is issued.","type":"boolean"},"AFFILIATE_BATCH_PAYOUT_COMPLETED":{"description":"Notify recipients when an affiliate payout batch completes.","type":"boolean"},"MONTHLY_PAYOUT_REMINDER":{"description":"Send recipients the monthly affiliate payout reminder.","type":"boolean"},"AFFILIATE_APPLICATIONS_PENDING_REVIEW":{"description":"Affiliate programs only. Notify recipients when new affiliate applications are waiting for review. Digests are sent at most once every 24 hours.","type":"boolean"}}}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/options":{"get":{"tags":["Campaigns"],"operationId":"retrieveCampaignOptions","summary":"Retrieve campaign options","description":"Retrieves a program's options — the same surface as the dashboard Program Editor's **Options** tab. Includes the attribution model, reward/fraud approval, anti-fraud lists + toggles, referral cookie/credit windows, reCAPTCHA, affiliate enrollment + application review, payout threshold + tax settings (affiliate only), and notification-email settings. `fraud.recaptcha.secretKey` is never returned.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Campaign options returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignOptions"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update campaign options

> Updates a program's options. Only the fields you send are changed. Some fields are program-type specific (\`requireManualRewardApproval\`/\`autoFulfillRewards\` are referral-only; \`affiliateApplicationMode\`/\`affiliateReapplicationPolicy\` and \`payoutThreshold\`/\`taxDocumentation\` are affiliate-only, and affiliate programs require \`requireParticipantAuth: true\`). \`fraud.recaptcha.secretKey\` is write-only. \`referralCreditWindowDays: null\` means "never expires".

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignOptions":{"type":"object","description":"A program's options (the dashboard Program Editor's Options tab). To see the full object with every field and its current value, `GET` this resource, then `PATCH` back only the fields you want to change. Some fields are program-type specific (see the per-field notes).","properties":{"affiliateApplicationMode":{"type":"string","enum":["OPEN_ENROLLMENT","MANUAL_REVIEW","AUTO_APPROVE"],"description":"Affiliate programs only. How public signups join the program. `OPEN_ENROLLMENT` enrolls signups directly (the default for programs created before applications existed). `MANUAL_REVIEW` collects an application you approve or deny. `AUTO_APPROVE` collects the full application and approves it immediately. Switching to a reviewed mode requires a saved Program Page. Trusted enrollment through the API, CSV import, dashboard, or invites is never blocked by this setting."},"affiliateReapplicationPolicy":{"type":"string","enum":["AFTER_COOLDOWN","DISABLED"],"description":"Affiliate programs only. Whether a denied applicant may apply again. `AFTER_COOLDOWN` (the default) allows a new application once `affiliateReapplicationCooldownDays` has passed; `DISABLED` never allows one."},"affiliateReapplicationCooldownDays":{"type":"integer","minimum":1,"maximum":365,"description":"Affiliate programs only. How many days a denied applicant waits before they can apply again (1-365, default 30). Only used when `affiliateReapplicationPolicy` is `AFTER_COOLDOWN`."},"affiliateApplicationReviewEstimateBusinessDays":{"type":["integer","null"],"minimum":1,"maximum":60,"description":"Affiliate programs only. Optional review-time expectation (in business days, 1-60) shown to pending applicants. Send `null` to clear it; when unset, applicants see neutral \"we'll email you\" copy instead."},"requireManualRewardApproval":{"type":"boolean","description":"Referral programs only. When `true`, each reward a participant earns is held for you to manually approve before it unlocks (and before the reward-won email is sent); when `false`, earned rewards are approved automatically. Cannot be `true` together with `autoFulfillRewards`."},"autoFulfillRewards":{"type":"boolean","description":"Referral programs only. When `true`, rewards a participant earns are automatically marked as fulfilled (issued) instead of you marking each one by hand, usually paired with an automated reward-delivery integration. Cannot be `true` together with `requireManualRewardApproval`."},"requireManualFraudApproval":{"type":"boolean","description":"When `true`, no signup is automatically blocked for fraud. Every participant is allowed to join, and any suspected fraud is flagged in your dashboard for you to review. Cannot be `true` together with `autoBlockFraud`."},"autoBlockFraud":{"type":"boolean","description":"When `true`, signups flagged as high fraud risk are automatically blocked from joining; when `false`, they are allowed in and flagged in your dashboard for review. Cannot be `true` together with `requireManualFraudApproval`."},"requireParticipantAuth":{"type":"boolean","description":"Require returning participants to authenticate. Affiliate programs require this to be `true`."},"enforceGdprCompliance":{"type":"boolean","description":"When `true`, GrowSurf stores only the minimum participant data needed to help you meet GDPR/CCPA requirements. Some participant fields are omitted and fraud screening is limited while this is on."},"blockPaidAdsTraffic":{"type":"boolean","description":"When `true`, referrals from visitors who arrived through paid ads (for example Google or Facebook ads) are not attributed, so the referring participant is not credited for them. Recommended so participants don't compete with you for your own paid keywords."},"attributionModel":{"type":"string","enum":["LAST_CLICK","FIRST_CLICK"],"default":"LAST_CLICK","description":"Selects which server-verified visit receives conversion credit within `referralCookieWindowDays`. `LAST_CLICK` is used when this field is missing, including for programs created before attribution models were configurable."},"referralCookieWindowDays":{"type":"integer","enum":[1,3,7,14,30,60,90,180,365,400],"description":"How long, in days, a referral-link click is remembered in the visitor's browser. If the visitor signs up within this window, the referral is credited to the participant whose link they clicked."},"referralCreditWindowDays":{"type":["integer","null"],"enum":[1,3,7,14,30,60,90,180,365,null],"description":"How long, in days, a referred friend has to complete a qualifying action for their referrer to earn credit and unlock a reward. After this window the referral no longer counts toward rewards. `null` = never expires."},"payoutThreshold":{"type":["integer","null"],"minimum":0,"description":"Affiliate programs only. Minimum payout in the program's currency minor units (`0` or `null` = no minimum). Requires `currencyISO` to be set."},"fraud":{"$ref":"#/components/schemas/CampaignOptionsFraud","description":"Anti-fraud settings — block and allow lists for emails, IPs, and countries; toggles for blocking burner emails, data-center IPs, and referrals from high-risk referrers; per-IP signup rate limits; and reCAPTCHA."},"taxDocumentation":{"$ref":"#/components/schemas/CampaignOptionsTaxDocumentation","description":"Affiliate programs only. Your company's billing details (name, address, and VAT number) used on affiliate payout invoices and for VAT handling."},"notificationEmails":{"$ref":"#/components/schemas/CampaignOptionsNotificationEmails","description":"Owner notification-email settings — who receives program notifications and which events they are notified about."}}},"CampaignOptionsFraud":{"type":"object","description":"Anti-fraud settings.","properties":{"blockedEmails":{"type":"array","items":{"type":"string","maxLength":100},"description":"Blocked emails (wildcards allowed). Max 10000. Replaces the whole list."},"blockedIps":{"type":"array","items":{"type":"string","maxLength":100},"description":"Blocked IP addresses (wildcards allowed). Max 10000."},"blockedCountries":{"type":"array","items":{"type":"string","maxLength":100},"description":"Blocked countries (ISO 3166-1 alpha-2 codes)."},"allowedEmails":{"description":"Allowed email addresses or patterns. Replaces the whole list.","type":"array","items":{"type":"string","maxLength":100}},"allowedIps":{"description":"Allowed IP addresses or patterns. Replaces the whole list.","type":"array","items":{"type":"string","maxLength":100}},"allowedCountries":{"type":"array","items":{"type":"string","maxLength":100},"description":"Allowed countries (ISO 3166-1 alpha-2 codes)."},"blockBurnerEmails":{"description":"Whether signups from burner or disposable email addresses are blocked.","type":"boolean"},"blockDataCenterIps":{"description":"Whether signups from known data-center IP addresses are blocked.","type":"boolean"},"blockHighRiskReferrers":{"description":"Whether referrals from participants marked as high fraud risk are blocked.","type":"boolean"},"autoBlockHighRiskIps":{"description":"Whether high-risk signup IP addresses are added to the blocked IP list automatically.","type":"boolean"},"maxSignupsPerIp2Min":{"description":"Maximum signups allowed from one IP address in a 2-minute window.","type":"integer","minimum":1,"maximum":100},"maxSignupsPerIp10Min":{"description":"Maximum signups allowed from one IP address in a 10-minute window.","type":"integer","minimum":1,"maximum":100},"recaptcha":{"description":"reCAPTCHA settings for signup forms.","$ref":"#/components/schemas/CampaignOptionsRecaptcha"}}},"CampaignOptionsRecaptcha":{"type":"object","properties":{"isEnabled":{"description":"Whether reCAPTCHA is enabled on signup forms.","type":"boolean"},"siteKey":{"description":"Public reCAPTCHA site key used by the signup form.","type":["string","null"],"maxLength":100},"secretKey":{"type":["string","null"],"maxLength":100,"writeOnly":true,"description":"The reCAPTCHA secret key. Write-only — never returned by GET."}}},"CampaignOptionsTaxDocumentation":{"type":"object","description":"Affiliate programs only. Your company's billing details (name, address, and VAT number) used on affiliate payout invoices and for VAT handling.","properties":{"companyName":{"description":"Legal or billing company name used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"vatNumber":{"description":"Your company VAT number, when VAT collection applies.","type":["string","null"],"maxLength":100},"addressLine1":{"description":"First line of the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"addressLine2":{"description":"Second line of the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"city":{"description":"City for the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"state":{"description":"State, province, or region for the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":255},"postalCode":{"description":"Postal or ZIP code for the company address used on affiliate payout invoices.","type":["string","null"],"maxLength":100},"country":{"type":["string","null"],"description":"ISO 3166-1 alpha-2 country code."},"collectAffiliateVat":{"type":"boolean","description":"Requires `vatNumber` to be set."}}},"CampaignOptionsNotificationEmails":{"type":"object","description":"Owner notification-email settings.","properties":{"recipients":{"type":"array","items":{"type":"string"},"description":"Email addresses that receive program notifications."},"events":{"type":"object","description":"Per-event on/off toggles. The available events depend on the program type.","properties":{"PARTICIPANT_REACHED_A_GOAL":{"description":"Notify recipients when a participant unlocks a reward.","type":"boolean"},"NEW_PARTICIPANT_ADDED_NON_REFERRED":{"description":"Notify recipients when a participant joins without being referred.","type":"boolean"},"NEW_PARTICIPANT_ADDED_REFERRED":{"description":"Notify recipients when a participant joins through a referral link.","type":"boolean"},"CAMPAIGN_ENDED":{"description":"Notify recipients when the program ends.","type":"boolean"},"WEEKLY_PERFORMANCE_REPORT":{"description":"Send recipients the weekly performance report.","type":"boolean"},"MONTHLY_PERFORMANCE_REPORT":{"description":"Send recipients the monthly performance report.","type":"boolean"},"NEW_COMMISSION_ADDED":{"description":"Notify recipients when an affiliate earns a new commission.","type":"boolean"},"COMMISSION_ADJUSTED":{"description":"Notify recipients when an affiliate commission is adjusted.","type":"boolean"},"NEW_PAYOUT_ISSUED":{"description":"Notify recipients when an affiliate payout is issued.","type":"boolean"},"AFFILIATE_BATCH_PAYOUT_COMPLETED":{"description":"Notify recipients when an affiliate payout batch completes.","type":"boolean"},"MONTHLY_PAYOUT_REMINDER":{"description":"Send recipients the monthly affiliate payout reminder.","type":"boolean"},"AFFILIATE_APPLICATIONS_PENDING_REVIEW":{"description":"Affiliate programs only. Notify recipients when new affiliate applications are waiting for review. Digests are sent at most once every 24 hours.","type":"boolean"}}}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/options":{"patch":{"tags":["Campaigns"],"operationId":"updateCampaignOptions","summary":"Update campaign options","description":"Updates a program's options. Only the fields you send are changed. Some fields are program-type specific (`requireManualRewardApproval`/`autoFulfillRewards` are referral-only; `affiliateApplicationMode`/`affiliateReapplicationPolicy` and `payoutThreshold`/`taxDocumentation` are affiliate-only, and affiliate programs require `requireParticipantAuth: true`). `fraud.recaptcha.secretKey` is write-only. `referralCreditWindowDays: null` means \"never expires\".","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignOptions"}}}},"responses":{"200":{"description":"Campaign options updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignOptions"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## CAMPAIGN INSTALLATION ↓

{% hint style="info" %}
Read or update a program's **Installation** tab (plus Mobile SDK settings) — the referral trigger (referral programs only), signup tracking method, share URL and whitelist, custom-form signup settings, and mobile SDK settings. `PATCH` is a partial merge (send only what you want to change). `mobile.publicKey` is read-only (server-generated), and any URLs you send must include an explicit `http://` or `https://` scheme.
{% endhint %}

## Retrieve campaign installation

> Retrieves a program's installation configuration — the same surface as the dashboard Program Editor's \*\*Installation\*\* tab (plus the Mobile SDK settings). Includes the referral trigger (referral programs only), signup tracking method, share URL and whitelist, custom-form signup settings, and mobile SDK settings.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignInstallation":{"type":"object","description":"A program's installation configuration (the dashboard Program Editor's Installation tab). To see the full object with every field and its current value, `GET` this resource, then `PATCH` back only the fields you want to change. `referralTrigger` is only present and writable for referral programs.","properties":{"referralTrigger":{"type":"string","enum":["CUSTOM","ON_SIGNUP"],"description":"Referral programs only. Sets what a referred friend must do before a referral counts. `ON_SIGNUP` (\"Sign Up\") counts the referral as soon as they sign up. `CUSTOM` (\"Sign Up + Qualifying Action\") counts it only after they sign up and also complete a qualifying action you define, such as a first payment."},"signupEvent":{"type":"string","enum":["FORM_DETECTION","PROGRAMMATIC"],"description":"The signup tracking method. `FORM_DETECTION` = GrowSurf automatically detects submissions of an existing form on your site; `PROGRAMMATIC` = you add participants via the JavaScript/mobile SDKs or this API."},"shareUrl":{"type":"string","maxLength":500,"description":"The landing page referred friends are taken to when they click a participant's referral link — usually your homepage or a marketing page. Required; must include an `http(s)://` scheme and contain no `#`, `#!`, or `??`."},"useGrowSurfHostedLinks":{"type":"boolean","description":"When `true`, each participant's referral link becomes a GrowSurf-hosted link instead of a link to your own share URL, and clicks are routed by the visitor's device (Android to Google Play, iOS to your iOS attribution or App Store URL, desktop to your share URL). Mainly used by mobile apps. Applies to referral links everywhere they appear, including the SDKs, integrations, webhooks, the GrowSurf window, and participant exports."},"allowedUrls":{"type":"array","items":{"type":"string","maxLength":500},"description":"Extra domains, beyond your share URL, where the GrowSurf window and SDK are allowed to run — handy for testing on local or staging URLs. Adding an apex domain (e.g. `https://piedpiper.com`) also allows its subdomains. Each entry must include an `http(s)://` scheme. Sending this array replaces the entire list."},"signup":{"description":"Custom signup-form settings used with `FORM_DETECTION`.","$ref":"#/components/schemas/CampaignInstallationSignup"},"mobile":{"description":"Settings for the GrowSurf iOS and Android mobile SDKs.","$ref":"#/components/schemas/CampaignInstallationMobile"}}},"CampaignInstallationSignup":{"type":"object","description":"Custom signup-form settings (applies when the signup tracking method is `FORM_DETECTION`).","properties":{"isCustomForm":{"description":"Whether signups come from your own form instead of automatic form detection.","type":"boolean"},"url":{"type":["string","null"],"maxLength":500,"description":"The custom signup form URL. Must include an `http(s)://` scheme; no `#!` or `??`. Send `null` to clear."},"redirectAfterSignup":{"type":"boolean","description":"Whether the signup form redirects to another page after submission."},"redirectUrl":{"type":["string","null"],"maxLength":500,"description":"URL the signup form redirects to. Must include an `http(s)://` scheme. Send `null` to clear."},"trackInputFields":{"description":"Whether GrowSurf watches signup form fields for referral attribution.","type":"boolean"}}},"CampaignInstallationMobile":{"type":"object","description":"Settings for the GrowSurf iOS and Android mobile SDKs — enable the mobile SDK, the read-only publishable key, and the iOS/Android attribution and app-store URLs used to route mobile referral-link clicks.","properties":{"isEnabled":{"type":"boolean","description":"Whether the mobile SDK is enabled for the campaign."},"publicKey":{"type":"string","readOnly":true,"description":"The publishable mobile SDK key used by the GrowSurf iOS and Android SDKs. Read-only."},"iosAttributionUrl":{"type":["string","null"],"maxLength":500,"description":"iOS attribution URL. Must include an `http(s)://` scheme; no `#!` or `??`. Send `null` to clear."},"iosAppStoreUrl":{"description":"iOS App Store URL used for mobile referral links.","type":["string","null"],"maxLength":500},"androidPackageName":{"type":["string","null"],"maxLength":255,"description":"Android package name, e.g. `com.example.app` (pattern: `^[a-zA-Z][\\w]*(\\.[a-zA-Z][\\w]*)+$`)."},"androidAppStoreUrl":{"description":"Google Play Store URL used for mobile referral links.","type":["string","null"],"maxLength":500}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/installation":{"get":{"tags":["Campaigns"],"operationId":"retrieveCampaignInstallation","summary":"Retrieve campaign installation","description":"Retrieves a program's installation configuration — the same surface as the dashboard Program Editor's **Installation** tab (plus the Mobile SDK settings). Includes the referral trigger (referral programs only), signup tracking method, share URL and whitelist, custom-form signup settings, and mobile SDK settings.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Campaign installation configuration returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignInstallation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update campaign installation

> Updates a program's installation configuration. Only the fields you send are changed; omitted fields are left untouched. \`referralTrigger\` is only available for referral programs. \`mobile.publicKey\` is read-only; if no key exists yet, enabling \`mobile.isEnabled\` creates one. Changing \`shareUrl\` re-resolves its redirect destinations, which may take a moment to complete. URLs must include an explicit \`http\://\` or \`https\://\` scheme.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignInstallation":{"type":"object","description":"A program's installation configuration (the dashboard Program Editor's Installation tab). To see the full object with every field and its current value, `GET` this resource, then `PATCH` back only the fields you want to change. `referralTrigger` is only present and writable for referral programs.","properties":{"referralTrigger":{"type":"string","enum":["CUSTOM","ON_SIGNUP"],"description":"Referral programs only. Sets what a referred friend must do before a referral counts. `ON_SIGNUP` (\"Sign Up\") counts the referral as soon as they sign up. `CUSTOM` (\"Sign Up + Qualifying Action\") counts it only after they sign up and also complete a qualifying action you define, such as a first payment."},"signupEvent":{"type":"string","enum":["FORM_DETECTION","PROGRAMMATIC"],"description":"The signup tracking method. `FORM_DETECTION` = GrowSurf automatically detects submissions of an existing form on your site; `PROGRAMMATIC` = you add participants via the JavaScript/mobile SDKs or this API."},"shareUrl":{"type":"string","maxLength":500,"description":"The landing page referred friends are taken to when they click a participant's referral link — usually your homepage or a marketing page. Required; must include an `http(s)://` scheme and contain no `#`, `#!`, or `??`."},"useGrowSurfHostedLinks":{"type":"boolean","description":"When `true`, each participant's referral link becomes a GrowSurf-hosted link instead of a link to your own share URL, and clicks are routed by the visitor's device (Android to Google Play, iOS to your iOS attribution or App Store URL, desktop to your share URL). Mainly used by mobile apps. Applies to referral links everywhere they appear, including the SDKs, integrations, webhooks, the GrowSurf window, and participant exports."},"allowedUrls":{"type":"array","items":{"type":"string","maxLength":500},"description":"Extra domains, beyond your share URL, where the GrowSurf window and SDK are allowed to run — handy for testing on local or staging URLs. Adding an apex domain (e.g. `https://piedpiper.com`) also allows its subdomains. Each entry must include an `http(s)://` scheme. Sending this array replaces the entire list."},"signup":{"description":"Custom signup-form settings used with `FORM_DETECTION`.","$ref":"#/components/schemas/CampaignInstallationSignup"},"mobile":{"description":"Settings for the GrowSurf iOS and Android mobile SDKs.","$ref":"#/components/schemas/CampaignInstallationMobile"}}},"CampaignInstallationSignup":{"type":"object","description":"Custom signup-form settings (applies when the signup tracking method is `FORM_DETECTION`).","properties":{"isCustomForm":{"description":"Whether signups come from your own form instead of automatic form detection.","type":"boolean"},"url":{"type":["string","null"],"maxLength":500,"description":"The custom signup form URL. Must include an `http(s)://` scheme; no `#!` or `??`. Send `null` to clear."},"redirectAfterSignup":{"type":"boolean","description":"Whether the signup form redirects to another page after submission."},"redirectUrl":{"type":["string","null"],"maxLength":500,"description":"URL the signup form redirects to. Must include an `http(s)://` scheme. Send `null` to clear."},"trackInputFields":{"description":"Whether GrowSurf watches signup form fields for referral attribution.","type":"boolean"}}},"CampaignInstallationMobile":{"type":"object","description":"Settings for the GrowSurf iOS and Android mobile SDKs — enable the mobile SDK, the read-only publishable key, and the iOS/Android attribution and app-store URLs used to route mobile referral-link clicks.","properties":{"isEnabled":{"type":"boolean","description":"Whether the mobile SDK is enabled for the campaign."},"publicKey":{"type":"string","readOnly":true,"description":"The publishable mobile SDK key used by the GrowSurf iOS and Android SDKs. Read-only."},"iosAttributionUrl":{"type":["string","null"],"maxLength":500,"description":"iOS attribution URL. Must include an `http(s)://` scheme; no `#!` or `??`. Send `null` to clear."},"iosAppStoreUrl":{"description":"iOS App Store URL used for mobile referral links.","type":["string","null"],"maxLength":500},"androidPackageName":{"type":["string","null"],"maxLength":255,"description":"Android package name, e.g. `com.example.app` (pattern: `^[a-zA-Z][\\w]*(\\.[a-zA-Z][\\w]*)+$`)."},"androidAppStoreUrl":{"description":"Google Play Store URL used for mobile referral links.","type":["string","null"],"maxLength":500}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/installation":{"patch":{"tags":["Campaigns"],"operationId":"updateCampaignInstallation","summary":"Update campaign installation","description":"Updates a program's installation configuration. Only the fields you send are changed; omitted fields are left untouched. `referralTrigger` is only available for referral programs. `mobile.publicKey` is read-only; if no key exists yet, enabling `mobile.isEnabled` creates one. Changing `shareUrl` re-resolves its redirect destinations, which may take a moment to complete. URLs must include an explicit `http://` or `https://` scheme.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignInstallation"}}}},"responses":{"200":{"description":"Campaign installation configuration updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignInstallation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## CAMPAIGN INTEGRATIONS ↓

{% hint style="info" %}
List every integration a program can connect, each with its current state. `connected` means credentials are stored, `enabled` means the integration is switched on and working, and `autoDisabled` means GrowSurf switched it off after repeated delivery failures — its credentials are still stored, but it delivers nothing until it is reconnected. Integrations that do not apply to the program type are omitted. `connectUrl` opens that integration's connect panel in the Program Editor — connecting an integration is done in the GrowSurf dashboard, not through the API.
{% endhint %}

## List integrations

> Lists every integration this program can connect, each with its current state. Integrations that do not apply to the program type are omitted (for example, Wise on a referral program). Read-only: connecting an integration is an OAuth or credential handshake completed in the GrowSurf dashboard, so it cannot be done over the API. \`connected\` means credentials are stored, \`enabled\` means the integration is switched on and working, and \`autoDisabled\` means GrowSurf switched it off after repeated delivery failures — its credentials are still stored, but it delivers nothing until it is reconnected in the dashboard. \`connectUrl\` is the dashboard link to hand the person running the program when they want to connect one.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaigns","description":"Program retrieval, listing, creation, updates, cloning, and configuration (design, emails, options, installation, and campaign rewards)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"IntegrationListResponse":{"type":"object","required":["integrations"],"properties":{"integrations":{"description":"Every integration this program can connect, in the order the GrowSurf dashboard lists them.","type":"array","items":{"$ref":"#/components/schemas/Integration"}}}},"Integration":{"type":"object","required":["id","name","connected","enabled","autoDisabled","connectUrl"],"properties":{"id":{"type":"string","description":"Stable integration key, the same value the GrowSurf dashboard uses for this integration."},"name":{"type":"string","description":"Display name, matching what the GrowSurf dashboard calls this integration."},"connected":{"type":"boolean","description":"Whether the program has stored credentials for this integration."},"enabled":{"type":"boolean","description":"Whether the integration is switched on and currently working."},"autoDisabled":{"type":"boolean","description":"Whether GrowSurf switched the integration off after repeated delivery failures. Its credentials are still stored, but it delivers nothing until it is reconnected in the GrowSurf dashboard."},"connectUrl":{"type":"string","format":"uri","description":"Dashboard link that opens this integration's connect panel in the GrowSurf Program Editor. Give it to the person running the program: connecting an account is a step they complete in the dashboard, and the API cannot do it for them."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/integrations":{"get":{"tags":["Campaigns"],"operationId":"listIntegrations","summary":"List integrations","description":"Lists every integration this program can connect, each with its current state. Integrations that do not apply to the program type are omitted (for example, Wise on a referral program). Read-only: connecting an integration is an OAuth or credential handshake completed in the GrowSurf dashboard, so it cannot be done over the API. `connected` means credentials are stored, `enabled` means the integration is switched on and working, and `autoDisabled` means GrowSurf switched it off after repeated delivery failures — its credentials are still stored, but it delivers nothing until it is reconnected in the dashboard. `connectUrl` is the dashboard link to hand the person running the program when they want to connect one.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Integrations returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationListResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## CAMPAIGN WEBHOOKS ↓

{% hint style="info" %}
Manage a program's webhooks — the HTTP endpoints GrowSurf calls when program events occur. `secret` is write-only (used to sign the `GrowSurf-Signature` HMAC header, and never returned), each webhook has a persistent `id` (the program's default webhook is `primary`), and `events` is the array of event types the webhook is subscribed to. Use the test endpoint to send a live sample event to a saved webhook using its stored URL and secret.
{% endhint %}

## List webhooks

> Lists a program's webhooks (secrets are never returned).

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaign Webhooks","description":"Program webhook configuration (create, update, delete, and test webhooks)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"WebhookListResponse":{"type":"object","required":["webhooks"],"properties":{"webhooks":{"description":"Webhooks configured for the program.","type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}},"Webhook":{"type":"object","description":"A program webhook's configuration (payload URL, subscribed events, enabled state) plus read-only delivery-health fields.","required":["id","events","isEnabled","autoDisabledDueToFailures","failureCount"],"properties":{"id":{"type":"string","readOnly":true,"description":"The webhook id (`primary` for the program's primary webhook)."},"payloadUrl":{"type":["string","null"],"description":"The URL that receives webhook deliveries."},"events":{"description":"Webhook events this endpoint is subscribed to.","type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"}},"isEnabled":{"description":"Whether deliveries to this webhook are enabled.","type":"boolean"},"autoDisabledDueToFailures":{"type":"boolean","readOnly":true,"description":"Read-only. Whether GrowSurf auto-disabled this webhook after repeated delivery failures."},"failureCount":{"type":"integer","readOnly":true,"description":"Read-only. Consecutive delivery failures."},"lastFailureAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Read-only. When the last delivery failure occurred, as a Unix timestamp in milliseconds."}}},"WebhookEvent":{"type":"string","description":"A webhook event name (the events GrowSurf delivers to subscribed webhooks).","enum":["PARTICIPANT_REACHED_A_GOAL","NEW_PARTICIPANT_ADDED","CAMPAIGN_ENDED","PARTICIPANT_FRAUD_STATUS_UPDATED","NEW_COMMISSION_ADDED","COMMISSION_ADJUSTED","NEW_PAYOUT_ISSUED"]},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/webhooks":{"get":{"tags":["Campaign Webhooks"],"operationId":"listWebhooks","summary":"List webhooks","description":"Lists a program's webhooks (secrets are never returned).","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"responses":{"200":{"description":"Webhooks returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Create webhook

> Adds a webhook to the program.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaign Webhooks","description":"Program webhook configuration (create, update, delete, and test webhooks)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CreateWebhookRequest":{"type":"object","required":["payloadUrl"],"additionalProperties":false,"properties":{"payloadUrl":{"type":"string","description":"The URL that receives webhook deliveries."},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"},"default":[],"description":"The events this webhook is subscribed to. When omitted, the webhook is created subscribed to no events."},"secret":{"type":"string","writeOnly":true,"description":"Write-only. Used to sign deliveries (the `GrowSurf-Signature` HMAC header). Never returned."},"isEnabled":{"description":"Whether the webhook should start enabled. Defaults to `true`.","type":"boolean","default":true}}},"WebhookEvent":{"type":"string","description":"A webhook event name (the events GrowSurf delivers to subscribed webhooks).","enum":["PARTICIPANT_REACHED_A_GOAL","NEW_PARTICIPANT_ADDED","CAMPAIGN_ENDED","PARTICIPANT_FRAUD_STATUS_UPDATED","NEW_COMMISSION_ADDED","COMMISSION_ADJUSTED","NEW_PAYOUT_ISSUED"]},"Webhook":{"type":"object","description":"A program webhook's configuration (payload URL, subscribed events, enabled state) plus read-only delivery-health fields.","required":["id","events","isEnabled","autoDisabledDueToFailures","failureCount"],"properties":{"id":{"type":"string","readOnly":true,"description":"The webhook id (`primary` for the program's primary webhook)."},"payloadUrl":{"type":["string","null"],"description":"The URL that receives webhook deliveries."},"events":{"description":"Webhook events this endpoint is subscribed to.","type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"}},"isEnabled":{"description":"Whether deliveries to this webhook are enabled.","type":"boolean"},"autoDisabledDueToFailures":{"type":"boolean","readOnly":true,"description":"Read-only. Whether GrowSurf auto-disabled this webhook after repeated delivery failures."},"failureCount":{"type":"integer","readOnly":true,"description":"Read-only. Consecutive delivery failures."},"lastFailureAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Read-only. When the last delivery failure occurred, as a Unix timestamp in milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/webhooks":{"post":{"tags":["Campaign Webhooks"],"operationId":"createWebhook","summary":"Create webhook","description":"Adds a webhook to the program.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}}},"responses":{"200":{"description":"Webhook created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update webhook

> Updates a webhook by id.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaign Webhooks","description":"Program webhook configuration (create, update, delete, and test webhooks)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"WebhookId":{"name":"webhookId","in":"path","required":true,"description":"Webhook ID (`primary` for the program's primary webhook, or the id of an additional webhook).","schema":{"type":"string"}}},"schemas":{"UpdateWebhookRequest":{"type":"object","additionalProperties":false,"properties":{"payloadUrl":{"description":"New URL that receives webhook deliveries.","type":"string"},"events":{"description":"Replacement event subscription list for this webhook.","type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"}},"secret":{"type":"string","writeOnly":true,"description":"Write-only."},"isEnabled":{"description":"Whether deliveries to this webhook are enabled.","type":"boolean"}}},"WebhookEvent":{"type":"string","description":"A webhook event name (the events GrowSurf delivers to subscribed webhooks).","enum":["PARTICIPANT_REACHED_A_GOAL","NEW_PARTICIPANT_ADDED","CAMPAIGN_ENDED","PARTICIPANT_FRAUD_STATUS_UPDATED","NEW_COMMISSION_ADDED","COMMISSION_ADJUSTED","NEW_PAYOUT_ISSUED"]},"Webhook":{"type":"object","description":"A program webhook's configuration (payload URL, subscribed events, enabled state) plus read-only delivery-health fields.","required":["id","events","isEnabled","autoDisabledDueToFailures","failureCount"],"properties":{"id":{"type":"string","readOnly":true,"description":"The webhook id (`primary` for the program's primary webhook)."},"payloadUrl":{"type":["string","null"],"description":"The URL that receives webhook deliveries."},"events":{"description":"Webhook events this endpoint is subscribed to.","type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"}},"isEnabled":{"description":"Whether deliveries to this webhook are enabled.","type":"boolean"},"autoDisabledDueToFailures":{"type":"boolean","readOnly":true,"description":"Read-only. Whether GrowSurf auto-disabled this webhook after repeated delivery failures."},"failureCount":{"type":"integer","readOnly":true,"description":"Read-only. Consecutive delivery failures."},"lastFailureAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Read-only. When the last delivery failure occurred, as a Unix timestamp in milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/webhooks/{webhookId}":{"patch":{"tags":["Campaign Webhooks"],"operationId":"updateWebhook","summary":"Update webhook","description":"Updates a webhook by id.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/WebhookId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookRequest"}}}},"responses":{"200":{"description":"Webhook updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Delete webhook

> Removes a webhook by id.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaign Webhooks","description":"Program webhook configuration (create, update, delete, and test webhooks)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"WebhookId":{"name":"webhookId","in":"path","required":true,"description":"Webhook ID (`primary` for the program's primary webhook, or the id of an additional webhook).","schema":{"type":"string"}}},"schemas":{"DeleteWebhookResponse":{"type":"object","required":["id","success"],"properties":{"id":{"description":"ID of the webhook that was deleted.","type":"string"},"success":{"description":"Whether the webhook was deleted.","type":"boolean"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/webhooks/{webhookId}":{"delete":{"tags":["Campaign Webhooks"],"operationId":"deleteWebhook","summary":"Delete webhook","description":"Removes a webhook by id.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/WebhookId"}],"responses":{"200":{"description":"Webhook deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteWebhookResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Test webhook

> Sends a live test event to a webhook using its stored URL and secret.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Campaign Webhooks","description":"Program webhook configuration (create, update, delete, and test webhooks)."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"WebhookId":{"name":"webhookId","in":"path","required":true,"description":"Webhook ID (`primary` for the program's primary webhook, or the id of an additional webhook).","schema":{"type":"string"}}},"schemas":{"WebhookTestRequest":{"type":"object","additionalProperties":false,"properties":{"event":{"$ref":"#/components/schemas/WebhookEvent","description":"The event to simulate. When omitted, the webhook's first enabled event is used, and the request returns `400` if the webhook has no enabled events."}}},"WebhookEvent":{"type":"string","description":"A webhook event name (the events GrowSurf delivers to subscribed webhooks).","enum":["PARTICIPANT_REACHED_A_GOAL","NEW_PARTICIPANT_ADDED","CAMPAIGN_ENDED","PARTICIPANT_FRAUD_STATUS_UPDATED","NEW_COMMISSION_ADDED","COMMISSION_ADJUSTED","NEW_PAYOUT_ISSUED"]},"WebhookTestResponse":{"type":"object","required":["success"],"properties":{"success":{"description":"Whether the test webhook request completed.","type":"boolean"},"payload":{"type":"object","additionalProperties":true,"description":"The mock event payload that was sent."},"response":{"description":"Response returned by the webhook endpoint during the test.","type":"object","properties":{"msg":{"description":"Response message returned by the webhook endpoint.","type":"string"},"statusCode":{"description":"HTTP status code returned by the webhook endpoint.","type":"integer"}}}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/webhooks/{webhookId}/test":{"post":{"tags":["Campaign Webhooks"],"operationId":"testWebhook","summary":"Test webhook","description":"Sends a live test event to a webhook using its stored URL and secret.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/WebhookId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestRequest"}}}},"responses":{"200":{"description":"Test result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## PARTICIPANTS ↓

## List participants

> Retrieves a paged list of participants in a program.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"NextId":{"name":"nextId","in":"query","required":false,"description":"ID to start the next paged result set with.","schema":{"type":"string"}},"Limit100":{"name":"limit","in":"query","required":false,"description":"Number of results to return. Maximum 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}}},"schemas":{"ParticipantListResponse":{"type":"object","required":["participants","limit","nextId"],"properties":{"participants":{"description":"Participants returned for this page.","type":"array","items":{"$ref":"#/components/schemas/Participant"}},"limit":{"description":"Maximum number of participants requested for this page.","type":"integer"},"nextId":{"description":"Participant ID to pass as `nextId` for the next page, or `null` when there are no more results.","type":["string","null"]}}},"Participant":{"type":"object","description":"Detailed information about a program participant.","required":["id","referralCount","monthlyReferralCount","rank","monthlyRank","rewards","email"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant."},"isAffiliate":{"type":"boolean","readOnly":true,"description":"Affiliate programs only. Whether this participant is an enrolled affiliate. A referred customer who has not joined the program is `false`."},"affiliateStatus":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. The enrolled affiliate's status (`APPROVED`, `SUSPENDED`, or `BANNED`). `null` for participants who are not affiliates."},"affiliateEnrollmentSource":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. How the affiliate enrolled (`OPEN_ENROLLMENT`, `APPLICATION`, `PARTICIPANT_AUTH`, `INVITE`, `REST_API`, `CSV`, or `DASHBOARD`). `null` when not recorded."},"firstName":{"type":["string","null"],"description":"The first name of the participant."},"lastName":{"type":["string","null"],"description":"The last name of the participant."},"email":{"type":"string","description":"The email of the participant."},"paypalEmailAddress":{"type":"string","format":"email","readOnly":true,"description":"The PayPal email address on file for the participant, used for affiliate or PayPal reward payouts."},"referralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made by the participant."},"monthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made this month by the participant (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made the previous month by the participant."},"rank":{"type":"integer","readOnly":true,"description":"The rank of the participant."},"monthlyRank":{"type":"integer","readOnly":true,"description":"The monthly rank of the participant. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","readOnly":true,"description":"The previous monthly rank of the participant. Not returned if the participant did not exist in your program during the previous month."},"shareUrl":{"type":"string","readOnly":true,"description":"The unique share URL of the participant. Omitted for affiliate program\nparticipants who are not approved affiliates."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"The date the participant was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","readOnly":true,"description":"The source of how the participant joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the participant was referred, the referrer's status in receiving the referral credit. Only applicable if the participant was referred into the program."},"referredBy":{"type":"string","description":"The ID of the referrer. Only applicable if the participant was referred into the program."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","readOnly":true,"description":"A value that represents the integrity of the participant."},"fraudReasonCode":{"type":"string","readOnly":true,"description":"Machine-readable label for the participant's `fraudRiskLevel`. The label identifies broad\noutcomes such as unique or duplicate participant data, suspicious referral activity,\nconfigured access rules, or a manual update.\n\nCurrent labels include `UNIQUE_IDENTITY`, `DUPLICATE_EMAIL`, `DUPLICATE_IDENTITY`,\n`DUPLICATE_IDENTITY_EXCESSIVE`, `EMAIL_FRAUD`, `SIMILAR_EMAIL`, `SIMILAR_FIRST_NAME`,\n`SIMILAR_LAST_NAME`, `REFERRAL_EMAIL_PATTERN`, `REFERRAL_CHAIN_FRAUD`,\n`REFERRAL_VELOCITY_FRAUD`, `DOMAIN_CLUSTERING_FRAUD`, `MANUAL_UPDATE`, `WHITELISTED`,\n`BLACKLIST_MATCH`, `BLOCKED_IP`, and `REFERRER_HIGH_RISK`. Treat this as a diagnostic\nlabel because GrowSurf can add labels without notice."},"isWinner":{"type":"boolean","readOnly":true,"description":"`true` if the participant has earned one or more rewards."},"shareCount":{"type":"object","readOnly":true,"additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the participant has shared their referral link, keyed by channel. Standard web channels include `email`, `facebook`, and `twitter`; SDK/native-share channels include `iosNativeShare` and `androidNativeShare`."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions the participant has made."},"uniqueImpressionCount":{"type":"integer","readOnly":true,"description":"The total number of unique impressions the participant has made."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites the participant has sent."},"referrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the participant. Limited to the 100 most recently referred friends."},"monthlyReferrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the participant this month (resets at the end of the month). Limited to the 100 most recent."},"referrer":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/ParticipantReferrer"},{"type":"null"}],"description":"An object containing the participant's referrer information. Only applicable if the participant was referred into the program."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the participant, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the participant, or `null` when unavailable."},"mobileInstanceId":{"type":["string","null"],"description":"App-install scoped identifier supplied by a native app, or `null` when unavailable. The official mobile SDKs generate a lowercase UUID. Not stored when strict GDPR/CCPA mode is enabled."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data, useful for saving additional data for the participant (e.g., `company`, `companySize`). Metadata is never used by GrowSurf and usage is optional. Metadata is returned only in REST API calls, and never in JavaScript Web API calls. See [API Guidelines](https://docs.growsurf.com/developer-tools/rest-api/api-guidelines)."},"notes":{"type":["string","null"],"description":"Internal notes about the participant, added via the [GrowSurf Dashboard](https://growsurf.com/dashboard)."},"unsubscribed":{"type":"boolean","description":"`true` if the participant has unsubscribed from program emails."},"rewards":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/ParticipantReward"},"description":"A list of the rewards the participant has earned."},"vanityKeys":{"type":"array","items":{"type":"string"},"description":"The list of vanity keys that the participant has."},"unreadCommissionsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of commissions the participant has not yet viewed."},"unreadPayoutsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of payouts the participant has not yet viewed."},"isNew":{"type":"boolean","readOnly":true,"description":"Whether this participant was newly created by the request. Returned by participant creation calls; `false` when the participant already existed."},"allMatchingFraudsters":{"type":"array","readOnly":true,"items":{"type":"object","additionalProperties":true},"description":"A list of other participant IDs associated with this participant's fraud status."},"payoutSettings":{"type":"object","readOnly":true,"description":"Payout-related actions the participant must complete before a payout can be released (e.g. configuring a payout destination or submitting a W-9/W-8 tax form). Always present; the requiredActions array is empty when no action is required.","properties":{"requiredActions":{"description":"Actions the participant must complete before payouts can be sent.","type":"array","readOnly":true,"items":{"type":"string","enum":["PAYOUT_DESTINATION","TAX_INFO"]}}}}}},"ReferralSource":{"type":"string","enum":["DIRECT","PARTICIPANT","DELETED_PARTICIPANT","IMPORT","MANUAL"],"description":"How the participant joined the program. `DIRECT` means without a referrer, `PARTICIPANT` means through a participant referral, `IMPORT` means through a CSV import, `MANUAL` means through the dashboard, and `DELETED_PARTICIPANT` means the original referrer was later deleted."},"ReferralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED","CREDIT_EXPIRED","INVITE_SENT"]},"FraudRiskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH"]},"ParticipantReferrer":{"type":"object","description":"Summary information about a participant's referrer, returned within the `referrer` field of a `Participant`.","properties":{"id":{"type":"string","description":"The unique identifier of the referrer."},"firstName":{"type":["string","null"],"description":"The first name of the referrer."},"lastName":{"type":["string","null"],"description":"The last name of the referrer."},"email":{"type":"string","description":"The email of the referrer."},"referralCount":{"type":"integer","description":"The total number of referrals made by the referrer."},"monthlyReferralCount":{"type":"integer","description":"The total number of referrals made this month by the referrer (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","description":"The total number of referrals made the previous month by the referrer."},"rank":{"type":"integer","description":"The rank of the referrer."},"monthlyRank":{"type":"integer","description":"The monthly rank of the referrer. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","description":"The previous monthly rank of the referrer."},"shareUrl":{"type":"string","description":"The unique share URL of the referrer."},"createdAt":{"type":"integer","format":"int64","description":"The date the referrer was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","description":"The source of how the referrer joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the referrer was themselves referred, their referrer's status in receiving the referral credit."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","description":"A value that represents the integrity of the referrer."},"fraudReasonCode":{"type":"string","description":"The reason for the referrer's `fraudRiskLevel`. See `Participant.fraudReasonCode` for the list of possible values."},"isWinner":{"type":"boolean","description":"`true` if the referrer has earned one or more rewards."},"shareCount":{"type":"object","additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the referrer has shared their referral link, keyed by channel."},"impressionCount":{"type":"integer","description":"The total number of impressions the referrer has made."},"uniqueImpressionCount":{"type":"integer","description":"The total number of unique impressions the referrer has made."},"inviteCount":{"type":"integer","description":"The total number of invites the referrer has sent."},"referrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the referrer. Limited to the 100 most recent."},"monthlyReferrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the referrer this month (resets at the end of the month). Limited to the 100 most recent."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the referrer, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the referrer, or `null` when unavailable."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data for the referrer."},"unsubscribed":{"type":"boolean","description":"`true` if the referrer has unsubscribed from program emails."}}},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"ParticipantReward":{"type":"object","description":"A reward that a participant has earned. This is different from a program `Reward` Object and contains information pertinent only to the participant that earned the reward.","required":["id","rewardId","status"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant's reward. This is different for every new reward the participant earns."},"rewardId":{"type":"string","readOnly":true,"description":"The ID of the program `Reward` (`CampaignReward`) that this participant has earned."},"status":{"$ref":"#/components/schemas/RewardStatus","readOnly":true,"description":"The status of the participant's reward."},"unread":{"type":"boolean","readOnly":true,"description":"`true` if the participant has not yet seen the reward in a GrowSurf window."},"approved":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been approved."},"approvedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was approved for this participant (UTC milliseconds). `null` for unapproved rewards."},"fulfilledAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was fulfilled for this participant (UTC milliseconds). `null` for unapproved or unfulfilled rewards."},"isReferrer":{"type":"boolean","readOnly":true,"description":"`true` if the participant earned the reward as the referrer; `false` if they earned it as the referred friend (only applicable for double-sided reward types)."},"isAvailable":{"type":"boolean","readOnly":true,"description":"`true` if the reward is available for the participant to claim or redeem."},"isFulfilled":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been fulfilled."},"referredId":{"type":"string","readOnly":true,"description":"The ID of the friend that was referred."},"referrerId":{"type":"string","readOnly":true,"description":"The ID of the participant that made the referral."},"amount":{"type":["number","null"],"readOnly":true,"description":"What this reward delivered, in major units, for the reward rails that record a delivered amount on the reward itself (gift cards today). Read from this reward, never from the Campaign Reward's configured amount, so it reports what the participant actually received. `null` for every other reward, and `null` whenever the reward carries no trustworthy amount and currency pair."},"currencyISO":{"type":["string","null"],"readOnly":true,"description":"The ISO 4217 currency code for `amount`. `null` whenever `amount` is `null`."},"commissionStructure":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The commission structure associated with this reward. Present only for affiliate programs."}}},"RewardStatus":{"type":"string","enum":["PENDING","FULFILLED","CANCELLED"],"description":"The reward lifecycle state. `CANCELLED` means an unpaid `LEAD` reward was reversed before fulfillment."},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participants":{"get":{"tags":["Participants"],"operationId":"listParticipants","summary":"List participants","description":"Retrieves a paged list of participants in a program.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/NextId"},{"$ref":"#/components/parameters/Limit100"}],"responses":{"200":{"description":"Participants returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List leaderboard participants

> Retrieves participants in leaderboard order for the specified leaderboard type.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"NextId":{"name":"nextId","in":"query","required":false,"description":"ID to start the next paged result set with.","schema":{"type":"string"}},"Limit100":{"name":"limit","in":"query","required":false,"description":"Number of results to return. Maximum 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}}},"schemas":{"LeaderboardType":{"type":"string","enum":["ALL_TIME","CURRENT_MONTH","PREV_MONTH","TOTAL_IMPRESSION_COUNT","UNIQUE_IMPRESSION_COUNT","BY_COMMISSIONS","BY_REVENUE","BY_REFERRALS","BY_LEADS"],"default":"ALL_TIME"},"ParticipantListResponse":{"type":"object","required":["participants","limit","nextId"],"properties":{"participants":{"description":"Participants returned for this page.","type":"array","items":{"$ref":"#/components/schemas/Participant"}},"limit":{"description":"Maximum number of participants requested for this page.","type":"integer"},"nextId":{"description":"Participant ID to pass as `nextId` for the next page, or `null` when there are no more results.","type":["string","null"]}}},"Participant":{"type":"object","description":"Detailed information about a program participant.","required":["id","referralCount","monthlyReferralCount","rank","monthlyRank","rewards","email"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant."},"isAffiliate":{"type":"boolean","readOnly":true,"description":"Affiliate programs only. Whether this participant is an enrolled affiliate. A referred customer who has not joined the program is `false`."},"affiliateStatus":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. The enrolled affiliate's status (`APPROVED`, `SUSPENDED`, or `BANNED`). `null` for participants who are not affiliates."},"affiliateEnrollmentSource":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. How the affiliate enrolled (`OPEN_ENROLLMENT`, `APPLICATION`, `PARTICIPANT_AUTH`, `INVITE`, `REST_API`, `CSV`, or `DASHBOARD`). `null` when not recorded."},"firstName":{"type":["string","null"],"description":"The first name of the participant."},"lastName":{"type":["string","null"],"description":"The last name of the participant."},"email":{"type":"string","description":"The email of the participant."},"paypalEmailAddress":{"type":"string","format":"email","readOnly":true,"description":"The PayPal email address on file for the participant, used for affiliate or PayPal reward payouts."},"referralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made by the participant."},"monthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made this month by the participant (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made the previous month by the participant."},"rank":{"type":"integer","readOnly":true,"description":"The rank of the participant."},"monthlyRank":{"type":"integer","readOnly":true,"description":"The monthly rank of the participant. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","readOnly":true,"description":"The previous monthly rank of the participant. Not returned if the participant did not exist in your program during the previous month."},"shareUrl":{"type":"string","readOnly":true,"description":"The unique share URL of the participant. Omitted for affiliate program\nparticipants who are not approved affiliates."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"The date the participant was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","readOnly":true,"description":"The source of how the participant joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the participant was referred, the referrer's status in receiving the referral credit. Only applicable if the participant was referred into the program."},"referredBy":{"type":"string","description":"The ID of the referrer. Only applicable if the participant was referred into the program."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","readOnly":true,"description":"A value that represents the integrity of the participant."},"fraudReasonCode":{"type":"string","readOnly":true,"description":"Machine-readable label for the participant's `fraudRiskLevel`. The label identifies broad\noutcomes such as unique or duplicate participant data, suspicious referral activity,\nconfigured access rules, or a manual update.\n\nCurrent labels include `UNIQUE_IDENTITY`, `DUPLICATE_EMAIL`, `DUPLICATE_IDENTITY`,\n`DUPLICATE_IDENTITY_EXCESSIVE`, `EMAIL_FRAUD`, `SIMILAR_EMAIL`, `SIMILAR_FIRST_NAME`,\n`SIMILAR_LAST_NAME`, `REFERRAL_EMAIL_PATTERN`, `REFERRAL_CHAIN_FRAUD`,\n`REFERRAL_VELOCITY_FRAUD`, `DOMAIN_CLUSTERING_FRAUD`, `MANUAL_UPDATE`, `WHITELISTED`,\n`BLACKLIST_MATCH`, `BLOCKED_IP`, and `REFERRER_HIGH_RISK`. Treat this as a diagnostic\nlabel because GrowSurf can add labels without notice."},"isWinner":{"type":"boolean","readOnly":true,"description":"`true` if the participant has earned one or more rewards."},"shareCount":{"type":"object","readOnly":true,"additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the participant has shared their referral link, keyed by channel. Standard web channels include `email`, `facebook`, and `twitter`; SDK/native-share channels include `iosNativeShare` and `androidNativeShare`."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions the participant has made."},"uniqueImpressionCount":{"type":"integer","readOnly":true,"description":"The total number of unique impressions the participant has made."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites the participant has sent."},"referrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the participant. Limited to the 100 most recently referred friends."},"monthlyReferrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the participant this month (resets at the end of the month). Limited to the 100 most recent."},"referrer":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/ParticipantReferrer"},{"type":"null"}],"description":"An object containing the participant's referrer information. Only applicable if the participant was referred into the program."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the participant, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the participant, or `null` when unavailable."},"mobileInstanceId":{"type":["string","null"],"description":"App-install scoped identifier supplied by a native app, or `null` when unavailable. The official mobile SDKs generate a lowercase UUID. Not stored when strict GDPR/CCPA mode is enabled."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data, useful for saving additional data for the participant (e.g., `company`, `companySize`). Metadata is never used by GrowSurf and usage is optional. Metadata is returned only in REST API calls, and never in JavaScript Web API calls. See [API Guidelines](https://docs.growsurf.com/developer-tools/rest-api/api-guidelines)."},"notes":{"type":["string","null"],"description":"Internal notes about the participant, added via the [GrowSurf Dashboard](https://growsurf.com/dashboard)."},"unsubscribed":{"type":"boolean","description":"`true` if the participant has unsubscribed from program emails."},"rewards":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/ParticipantReward"},"description":"A list of the rewards the participant has earned."},"vanityKeys":{"type":"array","items":{"type":"string"},"description":"The list of vanity keys that the participant has."},"unreadCommissionsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of commissions the participant has not yet viewed."},"unreadPayoutsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of payouts the participant has not yet viewed."},"isNew":{"type":"boolean","readOnly":true,"description":"Whether this participant was newly created by the request. Returned by participant creation calls; `false` when the participant already existed."},"allMatchingFraudsters":{"type":"array","readOnly":true,"items":{"type":"object","additionalProperties":true},"description":"A list of other participant IDs associated with this participant's fraud status."},"payoutSettings":{"type":"object","readOnly":true,"description":"Payout-related actions the participant must complete before a payout can be released (e.g. configuring a payout destination or submitting a W-9/W-8 tax form). Always present; the requiredActions array is empty when no action is required.","properties":{"requiredActions":{"description":"Actions the participant must complete before payouts can be sent.","type":"array","readOnly":true,"items":{"type":"string","enum":["PAYOUT_DESTINATION","TAX_INFO"]}}}}}},"ReferralSource":{"type":"string","enum":["DIRECT","PARTICIPANT","DELETED_PARTICIPANT","IMPORT","MANUAL"],"description":"How the participant joined the program. `DIRECT` means without a referrer, `PARTICIPANT` means through a participant referral, `IMPORT` means through a CSV import, `MANUAL` means through the dashboard, and `DELETED_PARTICIPANT` means the original referrer was later deleted."},"ReferralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED","CREDIT_EXPIRED","INVITE_SENT"]},"FraudRiskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH"]},"ParticipantReferrer":{"type":"object","description":"Summary information about a participant's referrer, returned within the `referrer` field of a `Participant`.","properties":{"id":{"type":"string","description":"The unique identifier of the referrer."},"firstName":{"type":["string","null"],"description":"The first name of the referrer."},"lastName":{"type":["string","null"],"description":"The last name of the referrer."},"email":{"type":"string","description":"The email of the referrer."},"referralCount":{"type":"integer","description":"The total number of referrals made by the referrer."},"monthlyReferralCount":{"type":"integer","description":"The total number of referrals made this month by the referrer (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","description":"The total number of referrals made the previous month by the referrer."},"rank":{"type":"integer","description":"The rank of the referrer."},"monthlyRank":{"type":"integer","description":"The monthly rank of the referrer. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","description":"The previous monthly rank of the referrer."},"shareUrl":{"type":"string","description":"The unique share URL of the referrer."},"createdAt":{"type":"integer","format":"int64","description":"The date the referrer was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","description":"The source of how the referrer joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the referrer was themselves referred, their referrer's status in receiving the referral credit."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","description":"A value that represents the integrity of the referrer."},"fraudReasonCode":{"type":"string","description":"The reason for the referrer's `fraudRiskLevel`. See `Participant.fraudReasonCode` for the list of possible values."},"isWinner":{"type":"boolean","description":"`true` if the referrer has earned one or more rewards."},"shareCount":{"type":"object","additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the referrer has shared their referral link, keyed by channel."},"impressionCount":{"type":"integer","description":"The total number of impressions the referrer has made."},"uniqueImpressionCount":{"type":"integer","description":"The total number of unique impressions the referrer has made."},"inviteCount":{"type":"integer","description":"The total number of invites the referrer has sent."},"referrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the referrer. Limited to the 100 most recent."},"monthlyReferrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the referrer this month (resets at the end of the month). Limited to the 100 most recent."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the referrer, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the referrer, or `null` when unavailable."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data for the referrer."},"unsubscribed":{"type":"boolean","description":"`true` if the referrer has unsubscribed from program emails."}}},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"ParticipantReward":{"type":"object","description":"A reward that a participant has earned. This is different from a program `Reward` Object and contains information pertinent only to the participant that earned the reward.","required":["id","rewardId","status"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant's reward. This is different for every new reward the participant earns."},"rewardId":{"type":"string","readOnly":true,"description":"The ID of the program `Reward` (`CampaignReward`) that this participant has earned."},"status":{"$ref":"#/components/schemas/RewardStatus","readOnly":true,"description":"The status of the participant's reward."},"unread":{"type":"boolean","readOnly":true,"description":"`true` if the participant has not yet seen the reward in a GrowSurf window."},"approved":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been approved."},"approvedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was approved for this participant (UTC milliseconds). `null` for unapproved rewards."},"fulfilledAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was fulfilled for this participant (UTC milliseconds). `null` for unapproved or unfulfilled rewards."},"isReferrer":{"type":"boolean","readOnly":true,"description":"`true` if the participant earned the reward as the referrer; `false` if they earned it as the referred friend (only applicable for double-sided reward types)."},"isAvailable":{"type":"boolean","readOnly":true,"description":"`true` if the reward is available for the participant to claim or redeem."},"isFulfilled":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been fulfilled."},"referredId":{"type":"string","readOnly":true,"description":"The ID of the friend that was referred."},"referrerId":{"type":"string","readOnly":true,"description":"The ID of the participant that made the referral."},"amount":{"type":["number","null"],"readOnly":true,"description":"What this reward delivered, in major units, for the reward rails that record a delivered amount on the reward itself (gift cards today). Read from this reward, never from the Campaign Reward's configured amount, so it reports what the participant actually received. `null` for every other reward, and `null` whenever the reward carries no trustworthy amount and currency pair."},"currencyISO":{"type":["string","null"],"readOnly":true,"description":"The ISO 4217 currency code for `amount`. `null` whenever `amount` is `null`."},"commissionStructure":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The commission structure associated with this reward. Present only for affiliate programs."}}},"RewardStatus":{"type":"string","enum":["PENDING","FULFILLED","CANCELLED"],"description":"The reward lifecycle state. `CANCELLED` means an unpaid `LEAD` reward was reversed before fulfillment."},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/leaderboard":{"get":{"tags":["Participants"],"operationId":"listLeaderboard","summary":"List leaderboard participants","description":"Retrieves participants in leaderboard order for the specified leaderboard type.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/NextId"},{"$ref":"#/components/parameters/Limit100"},{"name":"isMonthly","in":"query","deprecated":true,"description":"Deprecated. Use `leaderboardType=CURRENT_MONTH` instead.","schema":{"type":"boolean","default":false}},{"name":"leaderboardType","in":"query","description":"Leaderboard ordering mode.","schema":{"$ref":"#/components/schemas/LeaderboardType"}}],"responses":{"200":{"description":"Leaderboard participants returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Add participant

> Adds a new participant to the program. If the email already exists, the existing participant is returned unchanged. For affiliate programs, set \`isAffiliate\` to \`true\` to enroll a new participant as an approved affiliate or \`false\` to create a non-affiliate. If you omit \`isAffiliate\`, a valid \`referredBy\` creates a referred non-affiliate; without a valid referrer, the new participant is enrolled as an approved affiliate. You can send a valid \`referredBy\` with \`isAffiliate: true\` to keep the referral attribution and enroll the participant as an affiliate. This private REST operation is manual enrollment: a valid \`referredBy\` preserves attribution, but it does not qualify Lead-event rewards or commissions. Use the public client or mobile signup flow for a referred signup that can qualify Lead value.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CreateParticipantRequest":{"type":"object","required":["email"],"properties":{"email":{"description":"Participant email address.","type":"string","format":"email"},"referredBy":{"type":"string","description":"Referrer participant ID or email address.","maxLength":100},"isAffiliate":{"type":"boolean","description":"Affiliate programs only. Controls affiliate enrollment for a new participant. `true` enrolls the participant with `affiliateStatus: APPROVED`; `false` creates a non-affiliate without `affiliateStatus`. Existing participants are returned unchanged."},"referralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED"],"description":"The referral credit status, only meaningful when `referredBy` resolves to a referrer. When omitted, it is derived from the program's referral trigger: `CREDIT_AWARDED`, `CREDIT_PENDING` (manual or custom trigger), or `CREDIT_EXPIRED` (past the credit window). Left unset when no referrer resolves."},"firstName":{"description":"Participant first name.","type":"string","maxLength":255},"lastName":{"description":"Participant last name.","type":"string","maxLength":255},"ipAddress":{"description":"Participant IP address, when available.","type":"string"},"fingerprint":{"description":"Browser identifier, when available.","type":"string"},"mobileInstanceId":{"type":"string","description":"Optional app-install scoped identifier supplied by a native app. Recommended for mobile participant creation and mobile participant token flows. The official mobile SDKs generate a lowercase UUID."},"metadata":{"description":"Custom metadata to store on the participant.","$ref":"#/components/schemas/Metadata"}}},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"Participant":{"type":"object","description":"Detailed information about a program participant.","required":["id","referralCount","monthlyReferralCount","rank","monthlyRank","rewards","email"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant."},"isAffiliate":{"type":"boolean","readOnly":true,"description":"Affiliate programs only. Whether this participant is an enrolled affiliate. A referred customer who has not joined the program is `false`."},"affiliateStatus":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. The enrolled affiliate's status (`APPROVED`, `SUSPENDED`, or `BANNED`). `null` for participants who are not affiliates."},"affiliateEnrollmentSource":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. How the affiliate enrolled (`OPEN_ENROLLMENT`, `APPLICATION`, `PARTICIPANT_AUTH`, `INVITE`, `REST_API`, `CSV`, or `DASHBOARD`). `null` when not recorded."},"firstName":{"type":["string","null"],"description":"The first name of the participant."},"lastName":{"type":["string","null"],"description":"The last name of the participant."},"email":{"type":"string","description":"The email of the participant."},"paypalEmailAddress":{"type":"string","format":"email","readOnly":true,"description":"The PayPal email address on file for the participant, used for affiliate or PayPal reward payouts."},"referralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made by the participant."},"monthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made this month by the participant (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made the previous month by the participant."},"rank":{"type":"integer","readOnly":true,"description":"The rank of the participant."},"monthlyRank":{"type":"integer","readOnly":true,"description":"The monthly rank of the participant. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","readOnly":true,"description":"The previous monthly rank of the participant. Not returned if the participant did not exist in your program during the previous month."},"shareUrl":{"type":"string","readOnly":true,"description":"The unique share URL of the participant. Omitted for affiliate program\nparticipants who are not approved affiliates."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"The date the participant was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","readOnly":true,"description":"The source of how the participant joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the participant was referred, the referrer's status in receiving the referral credit. Only applicable if the participant was referred into the program."},"referredBy":{"type":"string","description":"The ID of the referrer. Only applicable if the participant was referred into the program."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","readOnly":true,"description":"A value that represents the integrity of the participant."},"fraudReasonCode":{"type":"string","readOnly":true,"description":"Machine-readable label for the participant's `fraudRiskLevel`. The label identifies broad\noutcomes such as unique or duplicate participant data, suspicious referral activity,\nconfigured access rules, or a manual update.\n\nCurrent labels include `UNIQUE_IDENTITY`, `DUPLICATE_EMAIL`, `DUPLICATE_IDENTITY`,\n`DUPLICATE_IDENTITY_EXCESSIVE`, `EMAIL_FRAUD`, `SIMILAR_EMAIL`, `SIMILAR_FIRST_NAME`,\n`SIMILAR_LAST_NAME`, `REFERRAL_EMAIL_PATTERN`, `REFERRAL_CHAIN_FRAUD`,\n`REFERRAL_VELOCITY_FRAUD`, `DOMAIN_CLUSTERING_FRAUD`, `MANUAL_UPDATE`, `WHITELISTED`,\n`BLACKLIST_MATCH`, `BLOCKED_IP`, and `REFERRER_HIGH_RISK`. Treat this as a diagnostic\nlabel because GrowSurf can add labels without notice."},"isWinner":{"type":"boolean","readOnly":true,"description":"`true` if the participant has earned one or more rewards."},"shareCount":{"type":"object","readOnly":true,"additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the participant has shared their referral link, keyed by channel. Standard web channels include `email`, `facebook`, and `twitter`; SDK/native-share channels include `iosNativeShare` and `androidNativeShare`."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions the participant has made."},"uniqueImpressionCount":{"type":"integer","readOnly":true,"description":"The total number of unique impressions the participant has made."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites the participant has sent."},"referrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the participant. Limited to the 100 most recently referred friends."},"monthlyReferrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the participant this month (resets at the end of the month). Limited to the 100 most recent."},"referrer":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/ParticipantReferrer"},{"type":"null"}],"description":"An object containing the participant's referrer information. Only applicable if the participant was referred into the program."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the participant, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the participant, or `null` when unavailable."},"mobileInstanceId":{"type":["string","null"],"description":"App-install scoped identifier supplied by a native app, or `null` when unavailable. The official mobile SDKs generate a lowercase UUID. Not stored when strict GDPR/CCPA mode is enabled."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data, useful for saving additional data for the participant (e.g., `company`, `companySize`). Metadata is never used by GrowSurf and usage is optional. Metadata is returned only in REST API calls, and never in JavaScript Web API calls. See [API Guidelines](https://docs.growsurf.com/developer-tools/rest-api/api-guidelines)."},"notes":{"type":["string","null"],"description":"Internal notes about the participant, added via the [GrowSurf Dashboard](https://growsurf.com/dashboard)."},"unsubscribed":{"type":"boolean","description":"`true` if the participant has unsubscribed from program emails."},"rewards":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/ParticipantReward"},"description":"A list of the rewards the participant has earned."},"vanityKeys":{"type":"array","items":{"type":"string"},"description":"The list of vanity keys that the participant has."},"unreadCommissionsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of commissions the participant has not yet viewed."},"unreadPayoutsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of payouts the participant has not yet viewed."},"isNew":{"type":"boolean","readOnly":true,"description":"Whether this participant was newly created by the request. Returned by participant creation calls; `false` when the participant already existed."},"allMatchingFraudsters":{"type":"array","readOnly":true,"items":{"type":"object","additionalProperties":true},"description":"A list of other participant IDs associated with this participant's fraud status."},"payoutSettings":{"type":"object","readOnly":true,"description":"Payout-related actions the participant must complete before a payout can be released (e.g. configuring a payout destination or submitting a W-9/W-8 tax form). Always present; the requiredActions array is empty when no action is required.","properties":{"requiredActions":{"description":"Actions the participant must complete before payouts can be sent.","type":"array","readOnly":true,"items":{"type":"string","enum":["PAYOUT_DESTINATION","TAX_INFO"]}}}}}},"ReferralSource":{"type":"string","enum":["DIRECT","PARTICIPANT","DELETED_PARTICIPANT","IMPORT","MANUAL"],"description":"How the participant joined the program. `DIRECT` means without a referrer, `PARTICIPANT` means through a participant referral, `IMPORT` means through a CSV import, `MANUAL` means through the dashboard, and `DELETED_PARTICIPANT` means the original referrer was later deleted."},"ReferralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED","CREDIT_EXPIRED","INVITE_SENT"]},"FraudRiskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH"]},"ParticipantReferrer":{"type":"object","description":"Summary information about a participant's referrer, returned within the `referrer` field of a `Participant`.","properties":{"id":{"type":"string","description":"The unique identifier of the referrer."},"firstName":{"type":["string","null"],"description":"The first name of the referrer."},"lastName":{"type":["string","null"],"description":"The last name of the referrer."},"email":{"type":"string","description":"The email of the referrer."},"referralCount":{"type":"integer","description":"The total number of referrals made by the referrer."},"monthlyReferralCount":{"type":"integer","description":"The total number of referrals made this month by the referrer (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","description":"The total number of referrals made the previous month by the referrer."},"rank":{"type":"integer","description":"The rank of the referrer."},"monthlyRank":{"type":"integer","description":"The monthly rank of the referrer. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","description":"The previous monthly rank of the referrer."},"shareUrl":{"type":"string","description":"The unique share URL of the referrer."},"createdAt":{"type":"integer","format":"int64","description":"The date the referrer was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","description":"The source of how the referrer joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the referrer was themselves referred, their referrer's status in receiving the referral credit."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","description":"A value that represents the integrity of the referrer."},"fraudReasonCode":{"type":"string","description":"The reason for the referrer's `fraudRiskLevel`. See `Participant.fraudReasonCode` for the list of possible values."},"isWinner":{"type":"boolean","description":"`true` if the referrer has earned one or more rewards."},"shareCount":{"type":"object","additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the referrer has shared their referral link, keyed by channel."},"impressionCount":{"type":"integer","description":"The total number of impressions the referrer has made."},"uniqueImpressionCount":{"type":"integer","description":"The total number of unique impressions the referrer has made."},"inviteCount":{"type":"integer","description":"The total number of invites the referrer has sent."},"referrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the referrer. Limited to the 100 most recent."},"monthlyReferrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the referrer this month (resets at the end of the month). Limited to the 100 most recent."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the referrer, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the referrer, or `null` when unavailable."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data for the referrer."},"unsubscribed":{"type":"boolean","description":"`true` if the referrer has unsubscribed from program emails."}}},"ParticipantReward":{"type":"object","description":"A reward that a participant has earned. This is different from a program `Reward` Object and contains information pertinent only to the participant that earned the reward.","required":["id","rewardId","status"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant's reward. This is different for every new reward the participant earns."},"rewardId":{"type":"string","readOnly":true,"description":"The ID of the program `Reward` (`CampaignReward`) that this participant has earned."},"status":{"$ref":"#/components/schemas/RewardStatus","readOnly":true,"description":"The status of the participant's reward."},"unread":{"type":"boolean","readOnly":true,"description":"`true` if the participant has not yet seen the reward in a GrowSurf window."},"approved":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been approved."},"approvedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was approved for this participant (UTC milliseconds). `null` for unapproved rewards."},"fulfilledAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was fulfilled for this participant (UTC milliseconds). `null` for unapproved or unfulfilled rewards."},"isReferrer":{"type":"boolean","readOnly":true,"description":"`true` if the participant earned the reward as the referrer; `false` if they earned it as the referred friend (only applicable for double-sided reward types)."},"isAvailable":{"type":"boolean","readOnly":true,"description":"`true` if the reward is available for the participant to claim or redeem."},"isFulfilled":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been fulfilled."},"referredId":{"type":"string","readOnly":true,"description":"The ID of the friend that was referred."},"referrerId":{"type":"string","readOnly":true,"description":"The ID of the participant that made the referral."},"amount":{"type":["number","null"],"readOnly":true,"description":"What this reward delivered, in major units, for the reward rails that record a delivered amount on the reward itself (gift cards today). Read from this reward, never from the Campaign Reward's configured amount, so it reports what the participant actually received. `null` for every other reward, and `null` whenever the reward carries no trustworthy amount and currency pair."},"currencyISO":{"type":["string","null"],"readOnly":true,"description":"The ISO 4217 currency code for `amount`. `null` whenever `amount` is `null`."},"commissionStructure":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The commission structure associated with this reward. Present only for affiliate programs."}}},"RewardStatus":{"type":"string","enum":["PENDING","FULFILLED","CANCELLED"],"description":"The reward lifecycle state. `CANCELLED` means an unpaid `LEAD` reward was reversed before fulfillment."},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}},"ParticipantBlockedError":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"fraudRiskLevel":{"description":"Fraud risk level assigned to the blocked signup.","$ref":"#/components/schemas/FraudRiskLevel"},"fraudReasonCode":{"description":"Machine-readable reason code for the fraud decision.","type":"string"},"matchedParticipantIds":{"description":"Existing participant IDs related to the fraud match.","type":"array","items":{"type":"string"}},"email":{"description":"Email address from the blocked signup attempt.","type":"string","format":"email"},"referrerId":{"description":"Referrer participant ID supplied with the blocked signup, or `null`.","type":["string","null"]},"ipAddress":{"description":"IP address supplied with the blocked signup, or `null`.","type":["string","null"]},"fingerprint":{"description":"Browser fingerprint supplied with the blocked signup, or `null`.","type":["string","null"]},"blockedAt":{"description":"When the signup was blocked.","type":"string","format":"date-time"}}}]}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"Conflicting duplicate request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant":{"post":{"tags":["Participants"],"operationId":"createParticipant","summary":"Add participant","description":"Adds a new participant to the program. If the email already exists, the existing participant is returned unchanged. For affiliate programs, set `isAffiliate` to `true` to enroll a new participant as an approved affiliate or `false` to create a non-affiliate. If you omit `isAffiliate`, a valid `referredBy` creates a referred non-affiliate; without a valid referrer, the new participant is enrolled as an approved affiliate. You can send a valid `referredBy` with `isAffiliate: true` to keep the referral attribution and enroll the participant as an affiliate. This private REST operation is manual enrollment: a valid `referredBy` preserves attribution, but it does not qualify Lead-event rewards or commissions. Use the public client or mobile signup flow for a referred signup that can qualify Lead value.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateParticipantRequest"}}}},"responses":{"200":{"description":"Participant returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Participant"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"description":"Participant was blocked or the endpoint is not available for this program type.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ParticipantBlockedError"},{"$ref":"#/components/schemas/Error"}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Retrieve participant

> Retrieves a single participant by GrowSurf participant ID or email address.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"Participant":{"type":"object","description":"Detailed information about a program participant.","required":["id","referralCount","monthlyReferralCount","rank","monthlyRank","rewards","email"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant."},"isAffiliate":{"type":"boolean","readOnly":true,"description":"Affiliate programs only. Whether this participant is an enrolled affiliate. A referred customer who has not joined the program is `false`."},"affiliateStatus":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. The enrolled affiliate's status (`APPROVED`, `SUSPENDED`, or `BANNED`). `null` for participants who are not affiliates."},"affiliateEnrollmentSource":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. How the affiliate enrolled (`OPEN_ENROLLMENT`, `APPLICATION`, `PARTICIPANT_AUTH`, `INVITE`, `REST_API`, `CSV`, or `DASHBOARD`). `null` when not recorded."},"firstName":{"type":["string","null"],"description":"The first name of the participant."},"lastName":{"type":["string","null"],"description":"The last name of the participant."},"email":{"type":"string","description":"The email of the participant."},"paypalEmailAddress":{"type":"string","format":"email","readOnly":true,"description":"The PayPal email address on file for the participant, used for affiliate or PayPal reward payouts."},"referralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made by the participant."},"monthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made this month by the participant (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made the previous month by the participant."},"rank":{"type":"integer","readOnly":true,"description":"The rank of the participant."},"monthlyRank":{"type":"integer","readOnly":true,"description":"The monthly rank of the participant. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","readOnly":true,"description":"The previous monthly rank of the participant. Not returned if the participant did not exist in your program during the previous month."},"shareUrl":{"type":"string","readOnly":true,"description":"The unique share URL of the participant. Omitted for affiliate program\nparticipants who are not approved affiliates."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"The date the participant was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","readOnly":true,"description":"The source of how the participant joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the participant was referred, the referrer's status in receiving the referral credit. Only applicable if the participant was referred into the program."},"referredBy":{"type":"string","description":"The ID of the referrer. Only applicable if the participant was referred into the program."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","readOnly":true,"description":"A value that represents the integrity of the participant."},"fraudReasonCode":{"type":"string","readOnly":true,"description":"Machine-readable label for the participant's `fraudRiskLevel`. The label identifies broad\noutcomes such as unique or duplicate participant data, suspicious referral activity,\nconfigured access rules, or a manual update.\n\nCurrent labels include `UNIQUE_IDENTITY`, `DUPLICATE_EMAIL`, `DUPLICATE_IDENTITY`,\n`DUPLICATE_IDENTITY_EXCESSIVE`, `EMAIL_FRAUD`, `SIMILAR_EMAIL`, `SIMILAR_FIRST_NAME`,\n`SIMILAR_LAST_NAME`, `REFERRAL_EMAIL_PATTERN`, `REFERRAL_CHAIN_FRAUD`,\n`REFERRAL_VELOCITY_FRAUD`, `DOMAIN_CLUSTERING_FRAUD`, `MANUAL_UPDATE`, `WHITELISTED`,\n`BLACKLIST_MATCH`, `BLOCKED_IP`, and `REFERRER_HIGH_RISK`. Treat this as a diagnostic\nlabel because GrowSurf can add labels without notice."},"isWinner":{"type":"boolean","readOnly":true,"description":"`true` if the participant has earned one or more rewards."},"shareCount":{"type":"object","readOnly":true,"additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the participant has shared their referral link, keyed by channel. Standard web channels include `email`, `facebook`, and `twitter`; SDK/native-share channels include `iosNativeShare` and `androidNativeShare`."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions the participant has made."},"uniqueImpressionCount":{"type":"integer","readOnly":true,"description":"The total number of unique impressions the participant has made."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites the participant has sent."},"referrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the participant. Limited to the 100 most recently referred friends."},"monthlyReferrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the participant this month (resets at the end of the month). Limited to the 100 most recent."},"referrer":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/ParticipantReferrer"},{"type":"null"}],"description":"An object containing the participant's referrer information. Only applicable if the participant was referred into the program."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the participant, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the participant, or `null` when unavailable."},"mobileInstanceId":{"type":["string","null"],"description":"App-install scoped identifier supplied by a native app, or `null` when unavailable. The official mobile SDKs generate a lowercase UUID. Not stored when strict GDPR/CCPA mode is enabled."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data, useful for saving additional data for the participant (e.g., `company`, `companySize`). Metadata is never used by GrowSurf and usage is optional. Metadata is returned only in REST API calls, and never in JavaScript Web API calls. See [API Guidelines](https://docs.growsurf.com/developer-tools/rest-api/api-guidelines)."},"notes":{"type":["string","null"],"description":"Internal notes about the participant, added via the [GrowSurf Dashboard](https://growsurf.com/dashboard)."},"unsubscribed":{"type":"boolean","description":"`true` if the participant has unsubscribed from program emails."},"rewards":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/ParticipantReward"},"description":"A list of the rewards the participant has earned."},"vanityKeys":{"type":"array","items":{"type":"string"},"description":"The list of vanity keys that the participant has."},"unreadCommissionsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of commissions the participant has not yet viewed."},"unreadPayoutsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of payouts the participant has not yet viewed."},"isNew":{"type":"boolean","readOnly":true,"description":"Whether this participant was newly created by the request. Returned by participant creation calls; `false` when the participant already existed."},"allMatchingFraudsters":{"type":"array","readOnly":true,"items":{"type":"object","additionalProperties":true},"description":"A list of other participant IDs associated with this participant's fraud status."},"payoutSettings":{"type":"object","readOnly":true,"description":"Payout-related actions the participant must complete before a payout can be released (e.g. configuring a payout destination or submitting a W-9/W-8 tax form). Always present; the requiredActions array is empty when no action is required.","properties":{"requiredActions":{"description":"Actions the participant must complete before payouts can be sent.","type":"array","readOnly":true,"items":{"type":"string","enum":["PAYOUT_DESTINATION","TAX_INFO"]}}}}}},"ReferralSource":{"type":"string","enum":["DIRECT","PARTICIPANT","DELETED_PARTICIPANT","IMPORT","MANUAL"],"description":"How the participant joined the program. `DIRECT` means without a referrer, `PARTICIPANT` means through a participant referral, `IMPORT` means through a CSV import, `MANUAL` means through the dashboard, and `DELETED_PARTICIPANT` means the original referrer was later deleted."},"ReferralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED","CREDIT_EXPIRED","INVITE_SENT"]},"FraudRiskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH"]},"ParticipantReferrer":{"type":"object","description":"Summary information about a participant's referrer, returned within the `referrer` field of a `Participant`.","properties":{"id":{"type":"string","description":"The unique identifier of the referrer."},"firstName":{"type":["string","null"],"description":"The first name of the referrer."},"lastName":{"type":["string","null"],"description":"The last name of the referrer."},"email":{"type":"string","description":"The email of the referrer."},"referralCount":{"type":"integer","description":"The total number of referrals made by the referrer."},"monthlyReferralCount":{"type":"integer","description":"The total number of referrals made this month by the referrer (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","description":"The total number of referrals made the previous month by the referrer."},"rank":{"type":"integer","description":"The rank of the referrer."},"monthlyRank":{"type":"integer","description":"The monthly rank of the referrer. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","description":"The previous monthly rank of the referrer."},"shareUrl":{"type":"string","description":"The unique share URL of the referrer."},"createdAt":{"type":"integer","format":"int64","description":"The date the referrer was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","description":"The source of how the referrer joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the referrer was themselves referred, their referrer's status in receiving the referral credit."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","description":"A value that represents the integrity of the referrer."},"fraudReasonCode":{"type":"string","description":"The reason for the referrer's `fraudRiskLevel`. See `Participant.fraudReasonCode` for the list of possible values."},"isWinner":{"type":"boolean","description":"`true` if the referrer has earned one or more rewards."},"shareCount":{"type":"object","additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the referrer has shared their referral link, keyed by channel."},"impressionCount":{"type":"integer","description":"The total number of impressions the referrer has made."},"uniqueImpressionCount":{"type":"integer","description":"The total number of unique impressions the referrer has made."},"inviteCount":{"type":"integer","description":"The total number of invites the referrer has sent."},"referrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the referrer. Limited to the 100 most recent."},"monthlyReferrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the referrer this month (resets at the end of the month). Limited to the 100 most recent."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the referrer, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the referrer, or `null` when unavailable."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data for the referrer."},"unsubscribed":{"type":"boolean","description":"`true` if the referrer has unsubscribed from program emails."}}},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"ParticipantReward":{"type":"object","description":"A reward that a participant has earned. This is different from a program `Reward` Object and contains information pertinent only to the participant that earned the reward.","required":["id","rewardId","status"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant's reward. This is different for every new reward the participant earns."},"rewardId":{"type":"string","readOnly":true,"description":"The ID of the program `Reward` (`CampaignReward`) that this participant has earned."},"status":{"$ref":"#/components/schemas/RewardStatus","readOnly":true,"description":"The status of the participant's reward."},"unread":{"type":"boolean","readOnly":true,"description":"`true` if the participant has not yet seen the reward in a GrowSurf window."},"approved":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been approved."},"approvedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was approved for this participant (UTC milliseconds). `null` for unapproved rewards."},"fulfilledAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was fulfilled for this participant (UTC milliseconds). `null` for unapproved or unfulfilled rewards."},"isReferrer":{"type":"boolean","readOnly":true,"description":"`true` if the participant earned the reward as the referrer; `false` if they earned it as the referred friend (only applicable for double-sided reward types)."},"isAvailable":{"type":"boolean","readOnly":true,"description":"`true` if the reward is available for the participant to claim or redeem."},"isFulfilled":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been fulfilled."},"referredId":{"type":"string","readOnly":true,"description":"The ID of the friend that was referred."},"referrerId":{"type":"string","readOnly":true,"description":"The ID of the participant that made the referral."},"amount":{"type":["number","null"],"readOnly":true,"description":"What this reward delivered, in major units, for the reward rails that record a delivered amount on the reward itself (gift cards today). Read from this reward, never from the Campaign Reward's configured amount, so it reports what the participant actually received. `null` for every other reward, and `null` whenever the reward carries no trustworthy amount and currency pair."},"currencyISO":{"type":["string","null"],"readOnly":true,"description":"The ISO 4217 currency code for `amount`. `null` whenever `amount` is `null`."},"commissionStructure":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The commission structure associated with this reward. Present only for affiliate programs."}}},"RewardStatus":{"type":"string","enum":["PENDING","FULFILLED","CANCELLED"],"description":"The reward lifecycle state. `CANCELLED` means an unpaid `LEAD` reward was reversed before fulfillment."},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}":{"get":{"tags":["Participants"],"operationId":"retrieveParticipant","summary":"Retrieve participant","description":"Retrieves a single participant by GrowSurf participant ID or email address.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"responses":{"200":{"description":"Participant returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Participant"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Update participant

> Updates a participant by GrowSurf participant ID or email address. For affiliate programs, set \`affiliateStatus\` to \`APPROVED\`, \`SUSPENDED\`, or \`BANNED\`. \`APPROVED\` enrolls the participant as an affiliate. \`SUSPENDED\` and \`BANNED\` require an existing affiliate. This endpoint does not accept \`isAffiliate\`, and affiliate enrollment cannot be removed through REST.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"UpdateParticipantRequest":{"type":"object","description":"The participant fields you can update. Any property not listed here is rejected with a `400` (you cannot update read-only fields such as counters, `isAffiliate`, `origin`, or fraud state).","additionalProperties":false,"properties":{"referredBy":{"description":"Referrer participant ID or email address to assign to this participant.","type":"string","maxLength":100},"email":{"description":"Participant email address.","type":"string","format":"email"},"firstName":{"description":"Participant first name.","type":"string","maxLength":255},"lastName":{"description":"Participant last name.","type":"string","maxLength":255},"metadata":{"description":"Custom metadata to store on the participant.","$ref":"#/components/schemas/Metadata"},"referralStatus":{"description":"Referral credit status for this participant.","type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED","CREDIT_EXPIRED"]},"affiliateStatus":{"type":"string","enum":["APPROVED","SUSPENDED","BANNED"],"description":"Affiliate programs only. Sets the affiliate status. `APPROVED` also enrolls a participant who is not yet an affiliate. `SUSPENDED` and `BANNED` are rejected for non-affiliates."},"vanityKeys":{"description":"Custom referral-code aliases for this participant. Sending this array replaces the existing aliases.","type":"array","maxItems":5,"items":{"type":"string","minLength":1,"maxLength":20,"pattern":"^[A-Za-z0-9_-]+$"}},"unsubscribed":{"description":"Whether the participant is unsubscribed from program emails.","type":"boolean"},"notes":{"type":"string","maxLength":500,"description":"Freeform internal notes about the participant (internal only, never exposed to participants)."}}},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"Participant":{"type":"object","description":"Detailed information about a program participant.","required":["id","referralCount","monthlyReferralCount","rank","monthlyRank","rewards","email"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant."},"isAffiliate":{"type":"boolean","readOnly":true,"description":"Affiliate programs only. Whether this participant is an enrolled affiliate. A referred customer who has not joined the program is `false`."},"affiliateStatus":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. The enrolled affiliate's status (`APPROVED`, `SUSPENDED`, or `BANNED`). `null` for participants who are not affiliates."},"affiliateEnrollmentSource":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. How the affiliate enrolled (`OPEN_ENROLLMENT`, `APPLICATION`, `PARTICIPANT_AUTH`, `INVITE`, `REST_API`, `CSV`, or `DASHBOARD`). `null` when not recorded."},"firstName":{"type":["string","null"],"description":"The first name of the participant."},"lastName":{"type":["string","null"],"description":"The last name of the participant."},"email":{"type":"string","description":"The email of the participant."},"paypalEmailAddress":{"type":"string","format":"email","readOnly":true,"description":"The PayPal email address on file for the participant, used for affiliate or PayPal reward payouts."},"referralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made by the participant."},"monthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made this month by the participant (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made the previous month by the participant."},"rank":{"type":"integer","readOnly":true,"description":"The rank of the participant."},"monthlyRank":{"type":"integer","readOnly":true,"description":"The monthly rank of the participant. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","readOnly":true,"description":"The previous monthly rank of the participant. Not returned if the participant did not exist in your program during the previous month."},"shareUrl":{"type":"string","readOnly":true,"description":"The unique share URL of the participant. Omitted for affiliate program\nparticipants who are not approved affiliates."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"The date the participant was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","readOnly":true,"description":"The source of how the participant joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the participant was referred, the referrer's status in receiving the referral credit. Only applicable if the participant was referred into the program."},"referredBy":{"type":"string","description":"The ID of the referrer. Only applicable if the participant was referred into the program."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","readOnly":true,"description":"A value that represents the integrity of the participant."},"fraudReasonCode":{"type":"string","readOnly":true,"description":"Machine-readable label for the participant's `fraudRiskLevel`. The label identifies broad\noutcomes such as unique or duplicate participant data, suspicious referral activity,\nconfigured access rules, or a manual update.\n\nCurrent labels include `UNIQUE_IDENTITY`, `DUPLICATE_EMAIL`, `DUPLICATE_IDENTITY`,\n`DUPLICATE_IDENTITY_EXCESSIVE`, `EMAIL_FRAUD`, `SIMILAR_EMAIL`, `SIMILAR_FIRST_NAME`,\n`SIMILAR_LAST_NAME`, `REFERRAL_EMAIL_PATTERN`, `REFERRAL_CHAIN_FRAUD`,\n`REFERRAL_VELOCITY_FRAUD`, `DOMAIN_CLUSTERING_FRAUD`, `MANUAL_UPDATE`, `WHITELISTED`,\n`BLACKLIST_MATCH`, `BLOCKED_IP`, and `REFERRER_HIGH_RISK`. Treat this as a diagnostic\nlabel because GrowSurf can add labels without notice."},"isWinner":{"type":"boolean","readOnly":true,"description":"`true` if the participant has earned one or more rewards."},"shareCount":{"type":"object","readOnly":true,"additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the participant has shared their referral link, keyed by channel. Standard web channels include `email`, `facebook`, and `twitter`; SDK/native-share channels include `iosNativeShare` and `androidNativeShare`."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions the participant has made."},"uniqueImpressionCount":{"type":"integer","readOnly":true,"description":"The total number of unique impressions the participant has made."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites the participant has sent."},"referrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the participant. Limited to the 100 most recently referred friends."},"monthlyReferrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the participant this month (resets at the end of the month). Limited to the 100 most recent."},"referrer":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/ParticipantReferrer"},{"type":"null"}],"description":"An object containing the participant's referrer information. Only applicable if the participant was referred into the program."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the participant, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the participant, or `null` when unavailable."},"mobileInstanceId":{"type":["string","null"],"description":"App-install scoped identifier supplied by a native app, or `null` when unavailable. The official mobile SDKs generate a lowercase UUID. Not stored when strict GDPR/CCPA mode is enabled."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data, useful for saving additional data for the participant (e.g., `company`, `companySize`). Metadata is never used by GrowSurf and usage is optional. Metadata is returned only in REST API calls, and never in JavaScript Web API calls. See [API Guidelines](https://docs.growsurf.com/developer-tools/rest-api/api-guidelines)."},"notes":{"type":["string","null"],"description":"Internal notes about the participant, added via the [GrowSurf Dashboard](https://growsurf.com/dashboard)."},"unsubscribed":{"type":"boolean","description":"`true` if the participant has unsubscribed from program emails."},"rewards":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/ParticipantReward"},"description":"A list of the rewards the participant has earned."},"vanityKeys":{"type":"array","items":{"type":"string"},"description":"The list of vanity keys that the participant has."},"unreadCommissionsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of commissions the participant has not yet viewed."},"unreadPayoutsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of payouts the participant has not yet viewed."},"isNew":{"type":"boolean","readOnly":true,"description":"Whether this participant was newly created by the request. Returned by participant creation calls; `false` when the participant already existed."},"allMatchingFraudsters":{"type":"array","readOnly":true,"items":{"type":"object","additionalProperties":true},"description":"A list of other participant IDs associated with this participant's fraud status."},"payoutSettings":{"type":"object","readOnly":true,"description":"Payout-related actions the participant must complete before a payout can be released (e.g. configuring a payout destination or submitting a W-9/W-8 tax form). Always present; the requiredActions array is empty when no action is required.","properties":{"requiredActions":{"description":"Actions the participant must complete before payouts can be sent.","type":"array","readOnly":true,"items":{"type":"string","enum":["PAYOUT_DESTINATION","TAX_INFO"]}}}}}},"ReferralSource":{"type":"string","enum":["DIRECT","PARTICIPANT","DELETED_PARTICIPANT","IMPORT","MANUAL"],"description":"How the participant joined the program. `DIRECT` means without a referrer, `PARTICIPANT` means through a participant referral, `IMPORT` means through a CSV import, `MANUAL` means through the dashboard, and `DELETED_PARTICIPANT` means the original referrer was later deleted."},"ReferralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED","CREDIT_EXPIRED","INVITE_SENT"]},"FraudRiskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH"]},"ParticipantReferrer":{"type":"object","description":"Summary information about a participant's referrer, returned within the `referrer` field of a `Participant`.","properties":{"id":{"type":"string","description":"The unique identifier of the referrer."},"firstName":{"type":["string","null"],"description":"The first name of the referrer."},"lastName":{"type":["string","null"],"description":"The last name of the referrer."},"email":{"type":"string","description":"The email of the referrer."},"referralCount":{"type":"integer","description":"The total number of referrals made by the referrer."},"monthlyReferralCount":{"type":"integer","description":"The total number of referrals made this month by the referrer (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","description":"The total number of referrals made the previous month by the referrer."},"rank":{"type":"integer","description":"The rank of the referrer."},"monthlyRank":{"type":"integer","description":"The monthly rank of the referrer. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","description":"The previous monthly rank of the referrer."},"shareUrl":{"type":"string","description":"The unique share URL of the referrer."},"createdAt":{"type":"integer","format":"int64","description":"The date the referrer was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","description":"The source of how the referrer joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the referrer was themselves referred, their referrer's status in receiving the referral credit."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","description":"A value that represents the integrity of the referrer."},"fraudReasonCode":{"type":"string","description":"The reason for the referrer's `fraudRiskLevel`. See `Participant.fraudReasonCode` for the list of possible values."},"isWinner":{"type":"boolean","description":"`true` if the referrer has earned one or more rewards."},"shareCount":{"type":"object","additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the referrer has shared their referral link, keyed by channel."},"impressionCount":{"type":"integer","description":"The total number of impressions the referrer has made."},"uniqueImpressionCount":{"type":"integer","description":"The total number of unique impressions the referrer has made."},"inviteCount":{"type":"integer","description":"The total number of invites the referrer has sent."},"referrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the referrer. Limited to the 100 most recent."},"monthlyReferrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the referrer this month (resets at the end of the month). Limited to the 100 most recent."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the referrer, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the referrer, or `null` when unavailable."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data for the referrer."},"unsubscribed":{"type":"boolean","description":"`true` if the referrer has unsubscribed from program emails."}}},"ParticipantReward":{"type":"object","description":"A reward that a participant has earned. This is different from a program `Reward` Object and contains information pertinent only to the participant that earned the reward.","required":["id","rewardId","status"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant's reward. This is different for every new reward the participant earns."},"rewardId":{"type":"string","readOnly":true,"description":"The ID of the program `Reward` (`CampaignReward`) that this participant has earned."},"status":{"$ref":"#/components/schemas/RewardStatus","readOnly":true,"description":"The status of the participant's reward."},"unread":{"type":"boolean","readOnly":true,"description":"`true` if the participant has not yet seen the reward in a GrowSurf window."},"approved":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been approved."},"approvedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was approved for this participant (UTC milliseconds). `null` for unapproved rewards."},"fulfilledAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was fulfilled for this participant (UTC milliseconds). `null` for unapproved or unfulfilled rewards."},"isReferrer":{"type":"boolean","readOnly":true,"description":"`true` if the participant earned the reward as the referrer; `false` if they earned it as the referred friend (only applicable for double-sided reward types)."},"isAvailable":{"type":"boolean","readOnly":true,"description":"`true` if the reward is available for the participant to claim or redeem."},"isFulfilled":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been fulfilled."},"referredId":{"type":"string","readOnly":true,"description":"The ID of the friend that was referred."},"referrerId":{"type":"string","readOnly":true,"description":"The ID of the participant that made the referral."},"amount":{"type":["number","null"],"readOnly":true,"description":"What this reward delivered, in major units, for the reward rails that record a delivered amount on the reward itself (gift cards today). Read from this reward, never from the Campaign Reward's configured amount, so it reports what the participant actually received. `null` for every other reward, and `null` whenever the reward carries no trustworthy amount and currency pair."},"currencyISO":{"type":["string","null"],"readOnly":true,"description":"The ISO 4217 currency code for `amount`. `null` whenever `amount` is `null`."},"commissionStructure":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The commission structure associated with this reward. Present only for affiliate programs."}}},"RewardStatus":{"type":"string","enum":["PENDING","FULFILLED","CANCELLED"],"description":"The reward lifecycle state. `CANCELLED` means an unpaid `LEAD` reward was reversed before fulfillment."},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"Conflicting duplicate request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}":{"post":{"tags":["Participants"],"operationId":"updateParticipant","summary":"Update participant","description":"Updates a participant by GrowSurf participant ID or email address. For affiliate programs, set `affiliateStatus` to `APPROVED`, `SUSPENDED`, or `BANNED`. `APPROVED` enrolls the participant as an affiliate. `SUSPENDED` and `BANNED` require an existing affiliate. This endpoint does not accept `isAffiliate`, and affiliate enrollment cannot be removed through REST.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateParticipantRequest"}}}},"responses":{"200":{"description":"Updated participant returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Participant"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Delete participant

> Removes a participant by GrowSurf participant ID or email address. A \`202\` response means participant cleanup completed and analytics erasure is pending. Analytics reports can retain the removed participant until erasure completes. Do not repeat the deletion to finish analytics erasure; GrowSurf processes it automatically.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"responses":{"Success":{"description":"Success response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Locked":{"description":"The resource is temporarily locked for this operation. Retry the request in a few minutes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"SuccessResponse":{"type":"object","required":["success"],"properties":{"success":{"description":"Whether the request succeeded.","type":"boolean"}}},"PendingAnalyticsErasure":{"type":"object","description":"Analytics erasure accepted for automatic processing; erasure has not yet been confirmed.","required":["status","operationId"],"properties":{"status":{"type":"string","enum":["pending"],"description":"`pending` means analytics erasure has been accepted but is not confirmed complete."},"operationId":{"type":"string","description":"Opaque reference for support inquiries about this analytics erasure."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}":{"delete":{"tags":["Participants"],"operationId":"deleteParticipant","summary":"Delete participant","description":"Removes a participant by GrowSurf participant ID or email address. A `202` response means participant cleanup completed and analytics erasure is pending. Analytics reports can retain the removed participant until erasure completes. Do not repeat the deletion to finish analytics erasure; GrowSurf processes it automatically.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"responses":{"200":{"$ref":"#/components/responses/Success"},"202":{"description":"Participant cleanup completed; analytics erasure is pending.","content":{"application/json":{"schema":{"type":"object","required":["success","analyticsErasure"],"properties":{"success":{"type":"boolean","const":true,"description":"Whether participant cleanup completed."},"analyticsErasure":{"$ref":"#/components/schemas/PendingAnalyticsErasure","description":"Analytics erasure accepted for automatic processing."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Bulk delete participants

> Deletes a list of participants from a program in one request. Each entry in \`participants\` is a GrowSurf participant ID or an email address (mixed lists are allowed). Up to \`200\` entries per request — chunk larger lists across multiple calls. The response reports a per-row \`status\` for every submitted entry, so a \`200\` can include rows that were \`NOT\_FOUND\` or failed. Deletion is permanent and removes the participants' referrals, rewards, commissions, and payout records. A \`202\` response also includes \`analyticsErasure\` when analytics erasure is pending. A \`DELETED\` row means participant cleanup completed; analytics reports can retain that participant until erasure completes. Do not repeat successful rows to finish analytics erasure.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"BulkDeleteParticipantsRequest":{"type":"object","required":["participants"],"additionalProperties":false,"properties":{"participants":{"type":"array","minItems":1,"maxItems":200,"description":"GrowSurf participant IDs and/or email addresses to delete. Mixed entries are allowed.","items":{"type":"string","minLength":1}}}},"BulkDeleteParticipantsResponse":{"type":"object","required":["summary","results"],"properties":{"summary":{"description":"Counts of submitted, deleted, not-found, duplicate, and failed entries.","$ref":"#/components/schemas/BulkDeleteParticipantsSummary"},"results":{"type":"array","description":"One entry per submitted identifier, in the same order as the request.","items":{"$ref":"#/components/schemas/BulkDeleteParticipantResult"}}}},"BulkDeleteParticipantsSummary":{"type":"object","required":["total","deletedCount","notFoundCount","duplicateCount","errorCount"],"properties":{"total":{"type":"integer","description":"Number of entries submitted in this request."},"deletedCount":{"type":"integer","description":"Entries that resolved to a participant and were deleted."},"notFoundCount":{"type":"integer","description":"Entries that did not match any participant."},"duplicateCount":{"type":"integer","description":"Entries that resolved to the same participant as an earlier entry."},"errorCount":{"type":"integer","description":"Entries that failed to look up or delete."}}},"BulkDeleteParticipantResult":{"type":"object","required":["index","identifier","status"],"properties":{"index":{"type":"integer","description":"Zero-based position of this entry in the submitted `participants` array."},"identifier":{"type":"string","description":"The submitted participant ID or email address, echoed back as received."},"status":{"description":"Outcome for this submitted participant identifier.","$ref":"#/components/schemas/BulkDeleteParticipantStatus"},"participantId":{"type":"string","description":"The resolved GrowSurf participant ID. Present when the entry resolved to a participant."},"email":{"type":"string","format":"email","description":"The resolved participant's email address. Present on `DELETED` rows."},"message":{"type":"string","description":"Human-readable detail for `NOT_FOUND`, `DUPLICATE`, and `ERROR` rows."}}},"BulkDeleteParticipantStatus":{"type":"string","enum":["DELETED","NOT_FOUND","DUPLICATE","ERROR"],"description":"Per-row outcome. `DELETED` — the participant was resolved and removed. `NOT_FOUND` — no participant matches the ID or email. `DUPLICATE` — the entry resolves to the same participant as an earlier entry in the same request. `ERROR` — the lookup or deletion failed for this row."},"PendingAnalyticsErasure":{"type":"object","description":"Analytics erasure accepted for automatic processing; erasure has not yet been confirmed.","required":["status","operationId"],"properties":{"status":{"type":"string","enum":["pending"],"description":"`pending` means analytics erasure has been accepted but is not confirmed complete."},"operationId":{"type":"string","description":"Opaque reference for support inquiries about this analytics erasure."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participants/bulk-delete":{"post":{"tags":["Participants"],"operationId":"bulkDeleteParticipants","summary":"Bulk delete participants","description":"Deletes a list of participants from a program in one request. Each entry in `participants` is a GrowSurf participant ID or an email address (mixed lists are allowed). Up to `200` entries per request — chunk larger lists across multiple calls. The response reports a per-row `status` for every submitted entry, so a `200` can include rows that were `NOT_FOUND` or failed. Deletion is permanent and removes the participants' referrals, rewards, commissions, and payout records. A `202` response also includes `analyticsErasure` when analytics erasure is pending. A `DELETED` row means participant cleanup completed; analytics reports can retain that participant until erasure completes. Do not repeat successful rows to finish analytics erasure.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkDeleteParticipantsRequest"}}}},"responses":{"200":{"description":"Per-row outcomes returned (individual rows may still be `NOT_FOUND`, `DUPLICATE`, or `ERROR`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkDeleteParticipantsResponse"}}}},"202":{"description":"Per-row outcomes returned; analytics erasure is pending. Individual rows may still be `NOT_FOUND`, `DUPLICATE`, or `ERROR`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkDeleteParticipantsResponse"},{"type":"object","required":["analyticsErasure"],"properties":{"analyticsErasure":{"$ref":"#/components/schemas/PendingAnalyticsErasure","description":"Analytics erasure accepted for automatic processing."}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Trigger referral

> Triggers referral credit for an existing referred participant by GrowSurf participant ID or email address. Optionally pass \`delayInDays\` to hold the credit for a number of days before it is awarded (for example, to cover your own refund window). A delayed trigger can be cancelled before it is awarded with the Cancel delayed referral trigger request (DELETE on this same path).

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"TriggerReferralRequest":{"type":"object","description":"Optional body for a referral trigger. Omit it (or send an empty object) to award credit immediately.","properties":{"delayInDays":{"type":"integer","minimum":1,"maximum":90,"description":"Number of whole days to hold referral credit before it is awarded. Useful for honoring a refund window before crediting a referrer. Omit this field to award credit immediately. The credit is awarded automatically once the delay elapses, and can be cancelled before then with the Cancel delayed referral trigger request."}}},"ReferralTriggerResponse":{"type":"object","required":["success"],"properties":{"success":{"description":"Whether referral credit was awarded or scheduled.","type":"boolean"},"message":{"description":"Human-readable result message, present when credit was not awarded immediately or when a delayed trigger was scheduled.","type":"string"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/ref":{"post":{"tags":["Participants"],"operationId":"triggerReferral","summary":"Trigger referral","description":"Triggers referral credit for an existing referred participant by GrowSurf participant ID or email address. Optionally pass `delayInDays` to hold the credit for a number of days before it is awarded (for example, to cover your own refund window). A delayed trigger can be cancelled before it is awarded with the Cancel delayed referral trigger request (DELETE on this same path).","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerReferralRequest"}}}},"responses":{"200":{"description":"Referral trigger result returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralTriggerResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Cancel delayed referral trigger

> Cancels a pending delayed referral trigger for a participant (the companion to a delayed Trigger referral request). Use this to undo a scheduled referral credit before it is awarded, for example when a refund occurs inside your refund window. If the participant has no pending delayed trigger, \`success\` is returned as \`false\`.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"ReferralTriggerResponse":{"type":"object","required":["success"],"properties":{"success":{"description":"Whether referral credit was awarded or scheduled.","type":"boolean"},"message":{"description":"Human-readable result message, present when credit was not awarded immediately or when a delayed trigger was scheduled.","type":"string"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/ref":{"delete":{"tags":["Participants"],"operationId":"cancelDelayedReferral","summary":"Cancel delayed referral trigger","description":"Cancels a pending delayed referral trigger for a participant (the companion to a delayed Trigger referral request). Use this to undo a scheduled referral credit before it is awarded, for example when a refund occurs inside your refund window. If the participant has no pending delayed trigger, `success` is returned as `false`.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"responses":{"200":{"description":"Delayed referral trigger cancellation result returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralTriggerResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Create mobile participant token

> Creates or returns a participant using the same input behavior as Add Participant, then returns a participant-scoped token for GrowSurf mobile SDK participant endpoints. Use this endpoint from your backend after your mobile app authenticates a signed-in user. The program must have mobile SDK access enabled.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CreateParticipantRequest":{"type":"object","required":["email"],"properties":{"email":{"description":"Participant email address.","type":"string","format":"email"},"referredBy":{"type":"string","description":"Referrer participant ID or email address.","maxLength":100},"isAffiliate":{"type":"boolean","description":"Affiliate programs only. Controls affiliate enrollment for a new participant. `true` enrolls the participant with `affiliateStatus: APPROVED`; `false` creates a non-affiliate without `affiliateStatus`. Existing participants are returned unchanged."},"referralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED"],"description":"The referral credit status, only meaningful when `referredBy` resolves to a referrer. When omitted, it is derived from the program's referral trigger: `CREDIT_AWARDED`, `CREDIT_PENDING` (manual or custom trigger), or `CREDIT_EXPIRED` (past the credit window). Left unset when no referrer resolves."},"firstName":{"description":"Participant first name.","type":"string","maxLength":255},"lastName":{"description":"Participant last name.","type":"string","maxLength":255},"ipAddress":{"description":"Participant IP address, when available.","type":"string"},"fingerprint":{"description":"Browser identifier, when available.","type":"string"},"mobileInstanceId":{"type":"string","description":"Optional app-install scoped identifier supplied by a native app. Recommended for mobile participant creation and mobile participant token flows. The official mobile SDKs generate a lowercase UUID."},"metadata":{"description":"Custom metadata to store on the participant.","$ref":"#/components/schemas/Metadata"}}},"Metadata":{"type":"object","description":"Shallow custom metadata object.","additionalProperties":true},"MobileParticipantTokenResponse":{"type":"object","required":["participantToken","expiresIn","participant","isNew"],"properties":{"participantToken":{"type":"string","description":"Participant-scoped bearer token for GrowSurf mobile SDK participant endpoints."},"expiresIn":{"type":"integer","description":"Token lifetime in seconds."},"participant":{"description":"The participant record associated with the new token.","$ref":"#/components/schemas/Participant"},"isNew":{"type":"boolean","description":"Whether this request created a new participant. Returns false when the participant already existed."}}},"Participant":{"type":"object","description":"Detailed information about a program participant.","required":["id","referralCount","monthlyReferralCount","rank","monthlyRank","rewards","email"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant."},"isAffiliate":{"type":"boolean","readOnly":true,"description":"Affiliate programs only. Whether this participant is an enrolled affiliate. A referred customer who has not joined the program is `false`."},"affiliateStatus":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. The enrolled affiliate's status (`APPROVED`, `SUSPENDED`, or `BANNED`). `null` for participants who are not affiliates."},"affiliateEnrollmentSource":{"type":["string","null"],"readOnly":true,"description":"Affiliate programs only. How the affiliate enrolled (`OPEN_ENROLLMENT`, `APPLICATION`, `PARTICIPANT_AUTH`, `INVITE`, `REST_API`, `CSV`, or `DASHBOARD`). `null` when not recorded."},"firstName":{"type":["string","null"],"description":"The first name of the participant."},"lastName":{"type":["string","null"],"description":"The last name of the participant."},"email":{"type":"string","description":"The email of the participant."},"paypalEmailAddress":{"type":"string","format":"email","readOnly":true,"description":"The PayPal email address on file for the participant, used for affiliate or PayPal reward payouts."},"referralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made by the participant."},"monthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made this month by the participant (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","readOnly":true,"description":"The total number of referrals made the previous month by the participant."},"rank":{"type":"integer","readOnly":true,"description":"The rank of the participant."},"monthlyRank":{"type":"integer","readOnly":true,"description":"The monthly rank of the participant. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","readOnly":true,"description":"The previous monthly rank of the participant. Not returned if the participant did not exist in your program during the previous month."},"shareUrl":{"type":"string","readOnly":true,"description":"The unique share URL of the participant. Omitted for affiliate program\nparticipants who are not approved affiliates."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"The date the participant was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","readOnly":true,"description":"The source of how the participant joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the participant was referred, the referrer's status in receiving the referral credit. Only applicable if the participant was referred into the program."},"referredBy":{"type":"string","description":"The ID of the referrer. Only applicable if the participant was referred into the program."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","readOnly":true,"description":"A value that represents the integrity of the participant."},"fraudReasonCode":{"type":"string","readOnly":true,"description":"Machine-readable label for the participant's `fraudRiskLevel`. The label identifies broad\noutcomes such as unique or duplicate participant data, suspicious referral activity,\nconfigured access rules, or a manual update.\n\nCurrent labels include `UNIQUE_IDENTITY`, `DUPLICATE_EMAIL`, `DUPLICATE_IDENTITY`,\n`DUPLICATE_IDENTITY_EXCESSIVE`, `EMAIL_FRAUD`, `SIMILAR_EMAIL`, `SIMILAR_FIRST_NAME`,\n`SIMILAR_LAST_NAME`, `REFERRAL_EMAIL_PATTERN`, `REFERRAL_CHAIN_FRAUD`,\n`REFERRAL_VELOCITY_FRAUD`, `DOMAIN_CLUSTERING_FRAUD`, `MANUAL_UPDATE`, `WHITELISTED`,\n`BLACKLIST_MATCH`, `BLOCKED_IP`, and `REFERRER_HIGH_RISK`. Treat this as a diagnostic\nlabel because GrowSurf can add labels without notice."},"isWinner":{"type":"boolean","readOnly":true,"description":"`true` if the participant has earned one or more rewards."},"shareCount":{"type":"object","readOnly":true,"additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the participant has shared their referral link, keyed by channel. Standard web channels include `email`, `facebook`, and `twitter`; SDK/native-share channels include `iosNativeShare` and `androidNativeShare`."},"impressionCount":{"type":"integer","readOnly":true,"description":"The total number of impressions the participant has made."},"uniqueImpressionCount":{"type":"integer","readOnly":true,"description":"The total number of unique impressions the participant has made."},"inviteCount":{"type":"integer","readOnly":true,"description":"The total number of invites the participant has sent."},"referrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the participant. Limited to the 100 most recently referred friends."},"monthlyReferrals":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the participant this month (resets at the end of the month). Limited to the 100 most recent."},"referrer":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/ParticipantReferrer"},{"type":"null"}],"description":"An object containing the participant's referrer information. Only applicable if the participant was referred into the program."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the participant, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the participant, or `null` when unavailable."},"mobileInstanceId":{"type":["string","null"],"description":"App-install scoped identifier supplied by a native app, or `null` when unavailable. The official mobile SDKs generate a lowercase UUID. Not stored when strict GDPR/CCPA mode is enabled."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data, useful for saving additional data for the participant (e.g., `company`, `companySize`). Metadata is never used by GrowSurf and usage is optional. Metadata is returned only in REST API calls, and never in JavaScript Web API calls. See [API Guidelines](https://docs.growsurf.com/developer-tools/rest-api/api-guidelines)."},"notes":{"type":["string","null"],"description":"Internal notes about the participant, added via the [GrowSurf Dashboard](https://growsurf.com/dashboard)."},"unsubscribed":{"type":"boolean","description":"`true` if the participant has unsubscribed from program emails."},"rewards":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/ParticipantReward"},"description":"A list of the rewards the participant has earned."},"vanityKeys":{"type":"array","items":{"type":"string"},"description":"The list of vanity keys that the participant has."},"unreadCommissionsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of commissions the participant has not yet viewed."},"unreadPayoutsCount":{"type":"integer","readOnly":true,"description":"Affiliate programs only. The number of payouts the participant has not yet viewed."},"isNew":{"type":"boolean","readOnly":true,"description":"Whether this participant was newly created by the request. Returned by participant creation calls; `false` when the participant already existed."},"allMatchingFraudsters":{"type":"array","readOnly":true,"items":{"type":"object","additionalProperties":true},"description":"A list of other participant IDs associated with this participant's fraud status."},"payoutSettings":{"type":"object","readOnly":true,"description":"Payout-related actions the participant must complete before a payout can be released (e.g. configuring a payout destination or submitting a W-9/W-8 tax form). Always present; the requiredActions array is empty when no action is required.","properties":{"requiredActions":{"description":"Actions the participant must complete before payouts can be sent.","type":"array","readOnly":true,"items":{"type":"string","enum":["PAYOUT_DESTINATION","TAX_INFO"]}}}}}},"ReferralSource":{"type":"string","enum":["DIRECT","PARTICIPANT","DELETED_PARTICIPANT","IMPORT","MANUAL"],"description":"How the participant joined the program. `DIRECT` means without a referrer, `PARTICIPANT` means through a participant referral, `IMPORT` means through a CSV import, `MANUAL` means through the dashboard, and `DELETED_PARTICIPANT` means the original referrer was later deleted."},"ReferralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED","CREDIT_EXPIRED","INVITE_SENT"]},"FraudRiskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH"]},"ParticipantReferrer":{"type":"object","description":"Summary information about a participant's referrer, returned within the `referrer` field of a `Participant`.","properties":{"id":{"type":"string","description":"The unique identifier of the referrer."},"firstName":{"type":["string","null"],"description":"The first name of the referrer."},"lastName":{"type":["string","null"],"description":"The last name of the referrer."},"email":{"type":"string","description":"The email of the referrer."},"referralCount":{"type":"integer","description":"The total number of referrals made by the referrer."},"monthlyReferralCount":{"type":"integer","description":"The total number of referrals made this month by the referrer (resets at the end of the month)."},"prevMonthlyReferralCount":{"type":"integer","description":"The total number of referrals made the previous month by the referrer."},"rank":{"type":"integer","description":"The rank of the referrer."},"monthlyRank":{"type":"integer","description":"The monthly rank of the referrer. This rank resets to 0 at the end of each month."},"prevMonthlyRank":{"type":"integer","description":"The previous monthly rank of the referrer."},"shareUrl":{"type":"string","description":"The unique share URL of the referrer."},"createdAt":{"type":"integer","format":"int64","description":"The date the referrer was added to the program (UTC milliseconds)."},"referralSource":{"$ref":"#/components/schemas/ReferralSource","description":"The source of how the referrer joined the program."},"referralStatus":{"$ref":"#/components/schemas/ReferralStatus","description":"If the referrer was themselves referred, their referrer's status in receiving the referral credit."},"fraudRiskLevel":{"$ref":"#/components/schemas/FraudRiskLevel","description":"A value that represents the integrity of the referrer."},"fraudReasonCode":{"type":"string","description":"The reason for the referrer's `fraudRiskLevel`. See `Participant.fraudReasonCode` for the list of possible values."},"isWinner":{"type":"boolean","description":"`true` if the referrer has earned one or more rewards."},"shareCount":{"type":"object","additionalProperties":{"type":"integer"},"description":"An object containing counts of how many times the referrer has shared their referral link, keyed by channel."},"impressionCount":{"type":"integer","description":"The total number of impressions the referrer has made."},"uniqueImpressionCount":{"type":"integer","description":"The total number of unique impressions the referrer has made."},"inviteCount":{"type":"integer","description":"The total number of invites the referrer has sent."},"referrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs who were successfully referred by the referrer. Limited to the 100 most recent."},"monthlyReferrals":{"type":"array","items":{"type":"string"},"description":"A list of `Participant` IDs successfully referred by the referrer this month (resets at the end of the month). Limited to the 100 most recent."},"ipAddress":{"type":["string","null"],"description":"The IP address recorded for the referrer, or `null` when unavailable."},"fingerprint":{"type":["string","null"],"description":"The browser identifier recorded for the referrer, or `null` when unavailable."},"metadata":{"$ref":"#/components/schemas/Metadata","description":"An object containing any custom key-value data for the referrer."},"unsubscribed":{"type":"boolean","description":"`true` if the referrer has unsubscribed from program emails."}}},"ParticipantReward":{"type":"object","description":"A reward that a participant has earned. This is different from a program `Reward` Object and contains information pertinent only to the participant that earned the reward.","required":["id","rewardId","status"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant's reward. This is different for every new reward the participant earns."},"rewardId":{"type":"string","readOnly":true,"description":"The ID of the program `Reward` (`CampaignReward`) that this participant has earned."},"status":{"$ref":"#/components/schemas/RewardStatus","readOnly":true,"description":"The status of the participant's reward."},"unread":{"type":"boolean","readOnly":true,"description":"`true` if the participant has not yet seen the reward in a GrowSurf window."},"approved":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been approved."},"approvedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was approved for this participant (UTC milliseconds). `null` for unapproved rewards."},"fulfilledAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was fulfilled for this participant (UTC milliseconds). `null` for unapproved or unfulfilled rewards."},"isReferrer":{"type":"boolean","readOnly":true,"description":"`true` if the participant earned the reward as the referrer; `false` if they earned it as the referred friend (only applicable for double-sided reward types)."},"isAvailable":{"type":"boolean","readOnly":true,"description":"`true` if the reward is available for the participant to claim or redeem."},"isFulfilled":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been fulfilled."},"referredId":{"type":"string","readOnly":true,"description":"The ID of the friend that was referred."},"referrerId":{"type":"string","readOnly":true,"description":"The ID of the participant that made the referral."},"amount":{"type":["number","null"],"readOnly":true,"description":"What this reward delivered, in major units, for the reward rails that record a delivered amount on the reward itself (gift cards today). Read from this reward, never from the Campaign Reward's configured amount, so it reports what the participant actually received. `null` for every other reward, and `null` whenever the reward carries no trustworthy amount and currency pair."},"currencyISO":{"type":["string","null"],"readOnly":true,"description":"The ISO 4217 currency code for `amount`. `null` whenever `amount` is `null`."},"commissionStructure":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The commission structure associated with this reward. Present only for affiliate programs."}}},"RewardStatus":{"type":"string","enum":["PENDING","FULFILLED","CANCELLED"],"description":"The reward lifecycle state. `CANCELLED` means an unpaid `LEAD` reward was reversed before fulfillment."},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}},"ParticipantBlockedError":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"fraudRiskLevel":{"description":"Fraud risk level assigned to the blocked signup.","$ref":"#/components/schemas/FraudRiskLevel"},"fraudReasonCode":{"description":"Machine-readable reason code for the fraud decision.","type":"string"},"matchedParticipantIds":{"description":"Existing participant IDs related to the fraud match.","type":"array","items":{"type":"string"}},"email":{"description":"Email address from the blocked signup attempt.","type":"string","format":"email"},"referrerId":{"description":"Referrer participant ID supplied with the blocked signup, or `null`.","type":["string","null"]},"ipAddress":{"description":"IP address supplied with the blocked signup, or `null`.","type":["string","null"]},"fingerprint":{"description":"Browser fingerprint supplied with the blocked signup, or `null`.","type":["string","null"]},"blockedAt":{"description":"When the signup was blocked.","type":"string","format":"date-time"}}}]}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"Conflicting duplicate request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/mobile-participant-token":{"post":{"tags":["Participants"],"operationId":"createMobileParticipantToken","summary":"Create mobile participant token","description":"Creates or returns a participant using the same input behavior as Add Participant, then returns a participant-scoped token for GrowSurf mobile SDK participant endpoints. Use this endpoint from your backend after your mobile app authenticates a signed-in user. The program must have mobile SDK access enabled.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateParticipantRequest"}}}},"responses":{"200":{"description":"Participant and mobile participant token returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileParticipantTokenResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"description":"Participant was blocked or the endpoint is not available for this program type.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ParticipantBlockedError"},{"$ref":"#/components/schemas/Error"}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Email participant

> Sends an email to a participant. Provide EITHER \`emailType\` to trigger one of the program's configured email templates, OR \`subject\` + \`body\` for a free-form email. Free-form emails are sent with the same compliance handling (company name, postal address, and an unsubscribe link are added automatically, and unsubscribed participants are suppressed). Sending requires the team to be verified by GrowSurf. Requires a \*\*verified custom email domain\*\* on the program (which can be completed in \*Campaign Editor > 3. Emails > Email Settings\*). Returns \`400\` until one is verified. The email is accepted for delivery.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"EmailParticipantRequest":{"type":"object","additionalProperties":false,"description":"Provide EITHER `emailType` (template mode) OR `subject` + `body` (free-form mode), not both.","properties":{"emailType":{"type":"string","description":"The program email template to trigger (template mode). Send the camelCase email-type key. The available types depend on the program type; the template's `isEnabled` setting controls automatic sends only. This endpoint can trigger any sendable template. System and transactional types (login link, payout destination confirmation, tax notifications) and the invite email cannot be sent. Referral programs: `welcomeNonReferred`, `referralLinkViewedFirstTime`, `referralLinkUsed`, `referredSignup`, `welcomeReferred`, `goalAchieved`, `campaignEndedWinners`, `campaignEndedNonWinners`, `progressUpdateMonthly`. Affiliate programs: `welcomeNonReferred`, `referralLinkViewedFirstTime`, `referredSignup`, `commissionGenerated`, `commissionAdjusted`, `payoutPending`, `payoutSentSuccess`, `progressUpdateMonthly`."},"subject":{"type":"string","maxLength":255,"description":"Subject line for a free-form email. Supports dynamic text (`{{...}}` tokens), the same as the body."},"body":{"type":"string","description":"HTML body for a free-form email. You can personalize it with dynamic text, inserting `{{...}}` tokens like `{{firstName}}` or `{{shareUrl}}`. See [Guide to using dynamic text in GrowSurf emails](https://support.growsurf.com/article/213-guide-to-using-dynamic-text-in-growsurf-emails)."},"preheader":{"type":"string","description":"Optional preheader text for a free-form email."}}},"EmailParticipantResponse":{"type":"object","required":["success","status"],"properties":{"success":{"description":"Whether the email request was accepted.","type":"boolean"},"status":{"type":"string","enum":["queued"],"description":"The email was accepted for delivery."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/email":{"post":{"tags":["Participants"],"operationId":"emailParticipant","summary":"Email participant","description":"Sends an email to a participant. Provide EITHER `emailType` to trigger one of the program's configured email templates, OR `subject` + `body` for a free-form email. Free-form emails are sent with the same compliance handling (company name, postal address, and an unsubscribe link are added automatically, and unsubscribed participants are suppressed). Sending requires the team to be verified by GrowSurf. Requires a **verified custom email domain** on the program (which can be completed in *Campaign Editor > 3. Emails > Email Settings*). Returns `400` until one is verified. The email is accepted for delivery.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailParticipantRequest"}}}},"responses":{"200":{"description":"Email accepted for delivery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailParticipantResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Retrieve participant analytics

> Retrieves analytics for a single participant — all-time engagement counters, leaderboard ranks, and per-channel share counts (plus affiliate revenue, commission, and payout metrics for affiliate programs). Pass \`include=email\` for \`sent\` (accepted for delivery), \`delivered\`, \`opened\`, \`clicked\`, \`bounced\`, and \`spamComplaints\` metrics attributed to this participant, including invitations they sent. Use \`include=email,series\` to include the same counts in each UTC series bucket. Pass \`include=activation\` for covered participant enrollment, cohort anchor, and first milestones. Request both \`activation\` and \`series\` to add covered \`portalViews\` and \`shareActions\` to each series bucket. \`days\`, \`startDate\`, and \`endDate\` filter only the optional \`series\` and \`email\` data. They do not filter the top-level \`analytics\`, \`ranks\`, \`shareCount\`, or \`activation\` values.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Analytics","description":"Program analytics."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}},"ParticipantAnalyticsDays":{"name":"days","in":"query","required":false,"description":"Last number of days for optional `series` and `email` analytics. Defaults to 365. Maximum 1825. Does not filter the top-level all-time totals.","schema":{"type":"integer","minimum":1,"maximum":1825,"default":365}},"ParticipantAnalyticsStartDate":{"name":"startDate","in":"query","required":false,"description":"Start of a custom `series` and `email` analytics window as a Unix timestamp in milliseconds. Set it together with `endDate`. Does not filter the top-level all-time totals.","schema":{"type":"integer","format":"int64"}},"ParticipantAnalyticsEndDate":{"name":"endDate","in":"query","required":false,"description":"End of a custom `series` and `email` analytics window as a Unix timestamp in milliseconds. Set it together with `startDate`. Does not filter the top-level all-time totals.","schema":{"type":"integer","format":"int64"}}},"schemas":{"ParticipantAnalyticsResponse":{"type":"object","required":["analytics","ranks","shareCount"],"properties":{"analytics":{"description":"All-time participant analytics totals. Date-window query parameters do not filter these fields.","type":"object","properties":{"referrals":{"description":"All-time referrals credited to this participant.","type":"integer"},"monthlyReferrals":{"description":"Referrals credited to this participant in the current month.","type":"integer"},"leads":{"description":"Current pending referral credits for this participant.","type":"integer"},"expiredReferrals":{"description":"Expired referral credits for this participant.","type":"integer"},"impressions":{"description":"Total referral-link views for this participant.","type":"integer"},"uniqueImpressions":{"description":"Unique referral-link views for this participant.","type":"integer"},"invitesSent":{"description":"Invites sent by this participant.","type":"integer"},"rewardStatus":{"description":"This participant's reward counts grouped by review and fulfillment status.","$ref":"#/components/schemas/RewardStatusCounts"},"referralRevenue":{"type":"integer","description":"Affiliate only. Revenue attributed to this participant's referrals, in minor currency units."},"totalCommissions":{"type":"integer","description":"Affiliate only. Total commissions earned, in minor currency units."},"totalPaidOut":{"type":"integer","description":"Affiliate only. Total paid out, in minor currency units."},"upcomingPayout":{"type":"integer","description":"Affiliate only. Approved commissions ready to pay, in minor currency units."},"currencyISO":{"description":"Program currency for this participant's money metrics.","type":"string"}}},"ranks":{"description":"Leaderboard ranks for this participant.","type":"object","properties":{"rank":{"type":["integer","null"],"description":"All-time rank (1-indexed), or `null` when unranked."},"monthlyRank":{"description":"Current-month rank, or `null` when unranked.","type":["integer","null"]},"prevMonthlyRank":{"description":"Previous-month rank, or `null` when unranked.","type":["integer","null"]}}},"shareCount":{"type":"object","additionalProperties":{"type":"integer"},"description":"Per-channel share counts (e.g. `email`, `facebook`, `twitter`, ...)."},"email":{"description":"Present only when `include` contains `email`.","$ref":"#/components/schemas/EmailAnalytics"},"activation":{"description":"Present only when `include` contains `activation`.","$ref":"#/components/schemas/ParticipantActivationAnalytics"},"series":{"type":"array","description":"Present only when `include=series`. This participant's own referral-link activity per period (ascending), windowed by `days`/`startDate`/`endDate` and bucketed by `interval`.","items":{"allOf":[{"type":"object","properties":{"periodStart":{"type":"integer","format":"int64","description":"Start of the period, as a Unix timestamp in milliseconds (UTC)."},"email":{"description":"Present in each period only when `include` contains both `series` and `email`.","$ref":"#/components/schemas/EmailAnalyticsCounts"},"portalViews":{"type":["integer","null"],"minimum":0,"description":"Present only when `include` contains both `series` and `activation`. Covered signed-in portal views in this period, or `null` outside known coverage."},"shareActions":{"type":["integer","null"],"minimum":0,"description":"Present only when `include` contains both `series` and `activation`. Covered accepted share actions in this period, or `null` outside known coverage."}}},{"$ref":"#/components/schemas/CampaignAnalytics"}]}},"startDate":{"type":"integer","format":"int64","description":"Present only when `include` contains `series` or `email`. Window start (Unix ms)."},"endDate":{"type":"integer","format":"int64","description":"Present only when `include` contains `series` or `email`. Window end (Unix ms)."}}},"RewardStatusCounts":{"type":"object","description":"Reward counts grouped by review and fulfillment status.","properties":{"unapproved":{"description":"Unapproved rewards awaiting review.","type":"integer"},"unfulfilled":{"description":"Rewards that are approved but not fulfilled.","type":"integer"},"completed":{"description":"Approved rewards that are fulfilled.","type":"integer"}}},"EmailAnalytics":{"allOf":[{"$ref":"#/components/schemas/EmailAnalyticsMetrics"},{"type":"object","required":["byType","coverageStartDate","isPartial"],"properties":{"byType":{"type":"array","description":"Email metrics grouped by configured email type, ordered by `emailType`.","items":{"$ref":"#/components/schemas/EmailAnalyticsByType"}},"coverageStartDate":{"description":"Earliest date from which email analytics capture is expected to be complete, as a Unix timestamp in milliseconds, or `null` while the rollout cutoff is unavailable.","type":["integer","null"],"format":"int64"},"isPartial":{"type":"boolean","description":"Whether the requested window begins before `coverageStartDate`."}}}]},"EmailAnalyticsMetrics":{"allOf":[{"$ref":"#/components/schemas/EmailAnalyticsCounts"},{"type":"object","required":["deliveryRate","openRate","clickRate","bounceRate"],"properties":{"deliveryRate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Delivered emails divided by accepted emails, or `0` when none were accepted."},"openRate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Opened emails divided by delivered emails, or `0` when none were delivered."},"clickRate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Clicked emails divided by delivered emails, or `0` when none were delivered."},"bounceRate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Bounced emails divided by accepted emails, or `0` when none were accepted."}}}]},"EmailAnalyticsCounts":{"type":"object","required":["sent","delivered","opened","clicked","bounced","spamComplaints"],"properties":{"sent":{"type":"integer","minimum":0,"description":"Number of emails accepted for delivery."},"delivered":{"type":"integer","minimum":0,"description":"Number of emails delivered or subsequently opened or clicked."},"opened":{"type":"integer","minimum":0,"description":"Number of emails opened at least once."},"clicked":{"type":"integer","minimum":0,"description":"Number of emails with at least one tracked link click."},"bounced":{"type":"integer","minimum":0,"description":"Number of emails that bounced."},"spamComplaints":{"type":"integer","minimum":0,"description":"Number of emails reported as spam."}}},"EmailAnalyticsByType":{"allOf":[{"type":"object","required":["emailType"],"properties":{"emailType":{"type":"string","description":"Configured program email type or step."}}},{"$ref":"#/components/schemas/EmailAnalyticsMetrics"}]},"ParticipantActivationAnalytics":{"type":"object","description":"Opt-in covered enrollment and first-milestone analytics for one participant.","required":["coverageStartAt","metricContractVersion","programType","state","reason","cohort","enrolledAsAdvocateAt","milestones"],"properties":{"coverageStartAt":{"type":["integer","null"],"format":"int64","description":"Earliest date from which participant activation capture is expected to be complete."},"metricContractVersion":{"type":"integer","minimum":1,"description":"Version of the shared activation and engagement metric definitions."},"programType":{"description":"Program type that selects eligibility and the cohort anchor.","$ref":"#/components/schemas/CampaignType"},"state":{"description":"Whether this participant's activation values are complete, partial, or unavailable.","$ref":"#/components/schemas/AnalyticsAvailability"},"reason":{"description":"Why activation is partial or unavailable, or `null` when it is complete.","oneOf":[{"$ref":"#/components/schemas/AnalyticsUnavailableReason"},{"type":"null"}]},"cohort":{"description":"Program-specific cohort anchor and its covered value.","$ref":"#/components/schemas/ParticipantActivationCohort"},"enrolledAsAdvocateAt":{"type":["integer","null"],"format":"int64","description":"Referral program only. Covered advocate enrollment as a Unix timestamp in milliseconds. `null` is unavailable and does not mean advocate enrollment never occurred."},"milestones":{"description":"Covered first milestones for this participant.","$ref":"#/components/schemas/ParticipantActivationMilestones"}}},"CampaignType":{"type":"string","enum":["REFERRAL","AFFILIATE"]},"AnalyticsAvailability":{"type":"string","description":"Whether a metric is complete, partial, or unavailable for the requested bounds.","enum":["AVAILABLE","PARTIAL","UNAVAILABLE"]},"AnalyticsUnavailableReason":{"type":"string","description":"Why a metric or response is partial or unavailable.","enum":["COVERAGE_UNAVAILABLE","PRE_COVERAGE","PARTIAL_COVERAGE","INSUFFICIENT_COVERAGE","EMPTY_DENOMINATOR","QUERY_LIMIT_EXCEEDED","PARTICIPANT_NOT_ELIGIBLE"]},"ParticipantActivationCohort":{"type":"object","description":"The participant field and covered value used for activation cohort placement.","required":["anchorField","anchorAt"],"properties":{"anchorField":{"type":"string","enum":["enrolledAsAdvocateAt","approvedAsAffiliateAt"],"description":"Referral programs use `enrolledAsAdvocateAt`; affiliate programs use `approvedAsAffiliateAt`."},"anchorAt":{"type":["integer","null"],"format":"int64","description":"Covered cohort-anchor time as a Unix timestamp in milliseconds. `null` means the value is outside known coverage or unavailable; it does not mean enrollment never occurred."}}},"ParticipantActivationMilestones":{"type":"object","description":"Covered first-milestone values. A `null` timestamp means GrowSurf has no value inside known capture coverage; it does not mean the participant never completed that action.","required":["firstPortalViewedAt","firstReferralLinkCopiedAt","firstShareAt","firstShareChannel","firstUniqueClickAt","firstLeadAt","firstReferralAt","firstRewardAt","firstCommissionAt","payoutSetupCompletedAt"],"properties":{"firstPortalViewedAt":{"type":["integer","null"],"format":"int64","description":"First covered signed-in portal view, as a Unix timestamp in milliseconds."},"firstReferralLinkCopiedAt":{"type":["integer","null"],"format":"int64","description":"First covered referral-link copy, as a Unix timestamp in milliseconds."},"firstShareAt":{"type":["integer","null"],"format":"int64","description":"First covered accepted share action through any channel, as a Unix timestamp in milliseconds."},"firstShareChannel":{"type":["string","null"],"description":"Channel for `firstShareAt`, or `null` when the first covered share is unavailable.","enum":["email","facebook","twitter","linkedin","pinterest","threads","bluesky","sms","messenger","whatsapp","wechat","telegram","reddit","tumblr","qrcode","copyRefLink","iosNativeShare","androidNativeShare",null]},"firstUniqueClickAt":{"type":["integer","null"],"format":"int64","description":"First covered unique referral visit, as a Unix timestamp in milliseconds."},"firstLeadAt":{"type":["integer","null"],"format":"int64","description":"First covered referred lead, as a Unix timestamp in milliseconds."},"firstReferralAt":{"type":["integer","null"],"format":"int64","description":"First covered credited referral, as a Unix timestamp in milliseconds."},"firstRewardAt":{"type":["integer","null"],"format":"int64","description":"Referral program only. First covered participant reward, as a Unix timestamp in milliseconds."},"firstCommissionAt":{"type":["integer","null"],"format":"int64","description":"Affiliate program only. First covered commission, as a Unix timestamp in milliseconds."},"payoutSetupCompletedAt":{"type":["integer","null"],"format":"int64","description":"First covered payout-setup completion, as a Unix timestamp in milliseconds."}}},"CampaignAnalytics":{"type":"object","properties":{"invites":{"description":"Number of invites sent by participants.","type":"integer"},"impressions":{"description":"Total referral-link views.","type":"integer"},"uniqueImpressions":{"description":"Unique referral-link views.","type":"integer"},"participants":{"description":"Number of participants added.","type":"integer"},"referrals":{"description":"Number of referrals credited.","type":"integer"},"referralCreditPendings":{"description":"Number of referrals waiting for credit.","type":"integer"},"referralCreditExpireds":{"description":"Number of referrals whose credit window expired.","type":"integer"},"emailShares":{"description":"Number of shares through email.","type":"integer"},"facebookShares":{"description":"Number of shares through Facebook.","type":"integer"},"twitterShares":{"description":"Number of shares through Twitter/X.","type":"integer"},"threadsShares":{"description":"Number of shares through Threads.","type":"integer"},"blueskyShares":{"description":"Number of shares through Bluesky.","type":"integer"},"pinterestShares":{"description":"Number of shares through Pinterest.","type":"integer"},"linkedInShares":{"description":"Number of shares through LinkedIn.","type":"integer"},"smsShares":{"description":"Number of shares through SMS.","type":"integer"},"messengerShares":{"description":"Number of shares through Messenger.","type":"integer"},"whatsAppShares":{"description":"Number of shares through WhatsApp.","type":"integer"},"wechatShares":{"description":"Number of shares through WeChat.","type":"integer"},"telegramShares":{"description":"Number of shares through Telegram.","type":"integer"},"qrcodeShares":{"description":"Number of shares through QR code.","type":"integer"},"redditShares":{"description":"Number of shares through Reddit.","type":"integer"},"tumblrShares":{"description":"Number of shares through Tumblr.","type":"integer"},"copyRefLinkShares":{"description":"Number of times participants copied their referral link.","type":"integer"},"iosNativeShares":{"description":"Number of shares through iOS native sharing.","type":"integer"},"androidNativeShares":{"description":"Number of shares through Android native sharing.","type":"integer"},"totalRevenue":{"type":"integer","description":"Affiliate programs only. Revenue in the smallest unit of the program currency."},"totalCommissions":{"type":"integer","description":"Affiliate programs only. Commissions in the smallest unit of the program currency."},"totalCommissionCount":{"type":"integer","description":"Affiliate programs only. Number of commission records."},"uniqueCommissionReferrals":{"type":"integer","description":"Affiliate programs only. Number of unique referred participants represented by commissions in the requested timeframe."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/analytics":{"get":{"tags":["Analytics"],"operationId":"retrieveParticipantAnalytics","summary":"Retrieve participant analytics","description":"Retrieves analytics for a single participant — all-time engagement counters, leaderboard ranks, and per-channel share counts (plus affiliate revenue, commission, and payout metrics for affiliate programs). Pass `include=email` for `sent` (accepted for delivery), `delivered`, `opened`, `clicked`, `bounced`, and `spamComplaints` metrics attributed to this participant, including invitations they sent. Use `include=email,series` to include the same counts in each UTC series bucket. Pass `include=activation` for covered participant enrollment, cohort anchor, and first milestones. Request both `activation` and `series` to add covered `portalViews` and `shareActions` to each series bucket. `days`, `startDate`, and `endDate` filter only the optional `series` and `email` data. They do not filter the top-level `analytics`, `ranks`, `shareCount`, or `activation` values.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"},{"name":"include","in":"query","description":"Comma-separated optional data. `series` returns this participant's own activity per period; `email` returns `sent`, `delivered`, `opened`, `clicked`, `bounced`, `spamComplaints`, and per-email-type metrics attributed to the participant for the requested analytics window (including invitations they sent). Request both in either order to add email counts to every series item for emails sent during that period. `activation` returns covered enrollment, cohort-anchor, and first-milestone values. Request `activation,series` to add `portalViews` and `shareActions`; those values are `null` outside known coverage. Only documented tokens are accepted; an unknown token returns `400`.","schema":{"type":"string"}},{"$ref":"#/components/parameters/ParticipantAnalyticsDays"},{"$ref":"#/components/parameters/ParticipantAnalyticsStartDate"},{"$ref":"#/components/parameters/ParticipantAnalyticsEndDate"},{"name":"interval","in":"query","description":"Bucket size for the `series` (only used when `include` contains `series`). Defaults to `day`.","schema":{"type":"string","enum":["day","week","month"],"default":"day"}}],"responses":{"200":{"description":"Participant analytics returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantAnalyticsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List participant activity logs

> Returns a participant's activity logs, most recent first (offset/limit paginated).

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"ParticipantActivityLogsResponse":{"type":"object","required":["activityLogs","limit"],"properties":{"activityLogs":{"description":"Activity log entries for the participant.","type":"array","items":{"$ref":"#/components/schemas/ParticipantActivityLog"}},"offset":{"type":["integer","null"],"description":"The offset for the next page, or `null` when there are no more logs."},"limit":{"description":"Number of activity logs returned per page.","type":"integer"}}},"ParticipantActivityLog":{"type":"object","required":["type","text","createdAt"],"properties":{"type":{"type":"string","description":"The activity family (e.g. `REFERRAL`, `SHARE`, `REWARD`, `EMAIL`, `COMMON`)."},"text":{"type":"string","description":"A human-readable description of the activity."},"createdAt":{"type":"integer","format":"int64","description":"When the activity occurred, as a Unix timestamp in milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/activity-logs":{"get":{"tags":["Participants"],"operationId":"listParticipantActivityLogs","summary":"List participant activity logs","description":"Returns a participant's activity logs, most recent first (offset/limit paginated).","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"},{"name":"limit","in":"query","description":"Number of logs to return (1–100, default 20).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","description":"Number of logs to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Activity logs returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantActivityLogsResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Get a participant's payout destination status

> Returns a participant's payout-destination status across every payout provider enabled for the program (PayPal and/or Wise). For each provider it reports the current status, the confirmed claim email, the legal recipient type, and — when a delivery bounced or a recipient was invalidated — the repair reason. \`activeProvider\` is the provider that currently gets paid, or \`null\` until the participant confirms one.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"PayoutDestinationStatusResponse":{"type":"object","description":"A participant's payout-destination status across every enabled payout provider.","required":["activeProvider","enabledProviders","destinations"],"properties":{"activeProvider":{"type":["string","null"],"description":"The provider that currently gets paid, or `null` until the participant confirms one."},"enabledProviders":{"type":"array","description":"The payout providers enabled for this program.","items":{"type":"string"}},"destinations":{"type":"array","description":"One entry per enabled payout provider describing the participant's destination for it.","items":{"type":"object","required":["provider","providerDisplayName","status","claimEmail","legalEntityType","confirmedAt","needsRepairReason"],"properties":{"provider":{"type":"string","description":"The payout provider this entry describes."},"providerDisplayName":{"type":"string","description":"The customer-facing provider name (e.g. \"PayPal\", \"Wise\")."},"status":{"type":"string","description":"The destination's current status: `NONE` (not set up), `PENDING_CONFIRMATION`, `CONFIRMED`, `ACTIVE`, `NEEDS_REPAIR`, or `EXPIRED`. Historical superseded or revoked destinations are projected as `NONE`."},"claimEmail":{"type":["string","null"],"description":"The confirmed payout email for this provider."},"legalEntityType":{"type":["string","null"],"enum":["INDIVIDUAL","BUSINESS",null],"description":"The legal recipient type the participant confirmed, if any."},"confirmedAt":{"type":["integer","null"],"format":"int64","description":"When the destination was confirmed, in epoch milliseconds."},"needsRepairReason":{"type":["string","null"],"description":"When status is `NEEDS_REPAIR`, why (e.g. a bounced delivery)."}}}}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/payout-destination":{"get":{"tags":["Participants"],"operationId":"getParticipantPayoutDestination","summary":"Get a participant's payout destination status","description":"Returns a participant's payout-destination status across every payout provider enabled for the program (PayPal and/or Wise). For each provider it reports the current status, the confirmed claim email, the legal recipient type, and — when a delivery bounced or a recipient was invalidated — the repair reason. `activeProvider` is the provider that currently gets paid, or `null` until the participant confirms one.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"responses":{"200":{"description":"Payout destination status returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayoutDestinationStatusResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Request a payout destination confirmation

> Sends the participant a one-time link to confirm their payout destination for the chosen provider. Only the participant can open the link and confirm — this endpoint just triggers the message. The provider must be enabled for the program.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participants","description":"Program participant retrieval, creation, updates, deletion, and referral triggering."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"RequestPayoutDestinationConfirmationRequest":{"type":"object","required":["provider"],"properties":{"provider":{"type":"string","enum":["PAYPAL","WISECOM"],"description":"The payout provider the participant should confirm a destination for."}}},"RequestPayoutDestinationConfirmationResponse":{"type":"object","required":["status","provider","providerDisplayName","expiresAt"],"properties":{"status":{"type":"string","enum":["CONFIRMATION_REQUESTED"],"description":"Confirms the message was requested."},"provider":{"type":"string","description":"The provider the participant was asked to confirm."},"providerDisplayName":{"type":"string","description":"The customer-facing provider name (e.g. \"PayPal\", \"Wise\")."},"expiresAt":{"type":["integer","null"],"format":"int64","description":"When the confirmation link expires, in epoch milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/payout-destination/request-confirmation":{"post":{"tags":["Participants"],"operationId":"requestParticipantPayoutDestinationConfirmation","summary":"Request a payout destination confirmation","description":"Sends the participant a one-time link to confirm their payout destination for the chosen provider. Only the participant can open the link and confirm — this endpoint just triggers the message. The provider must be enabled for the program.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestPayoutDestinationConfirmationRequest"}}}},"responses":{"200":{"description":"Confirmation message requested.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestPayoutDestinationConfirmationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## PARTICIPANT REWARDS ↓

## List participant rewards

> Retrieves a paged list of rewards earned by a participant.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participant Rewards","description":"Participant reward retrieval and manual reward operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}},"NextId":{"name":"nextId","in":"query","required":false,"description":"ID to start the next paged result set with.","schema":{"type":"string"}},"Limit100":{"name":"limit","in":"query","required":false,"description":"Number of results to return. Maximum 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}}},"schemas":{"ParticipantRewardListResponse":{"type":"object","required":["rewards","limit","nextId"],"properties":{"rewards":{"description":"Participant rewards returned for this page.","type":"array","items":{"$ref":"#/components/schemas/ParticipantReward"}},"limit":{"description":"Maximum number of rewards requested for this page.","type":"integer"},"nextId":{"description":"Reward ID to pass as `nextId` for the next page, or `null` when there are no more results.","type":["string","null"]}}},"ParticipantReward":{"type":"object","description":"A reward that a participant has earned. This is different from a program `Reward` Object and contains information pertinent only to the participant that earned the reward.","required":["id","rewardId","status"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant's reward. This is different for every new reward the participant earns."},"rewardId":{"type":"string","readOnly":true,"description":"The ID of the program `Reward` (`CampaignReward`) that this participant has earned."},"status":{"$ref":"#/components/schemas/RewardStatus","readOnly":true,"description":"The status of the participant's reward."},"unread":{"type":"boolean","readOnly":true,"description":"`true` if the participant has not yet seen the reward in a GrowSurf window."},"approved":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been approved."},"approvedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was approved for this participant (UTC milliseconds). `null` for unapproved rewards."},"fulfilledAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The date and time the reward was fulfilled for this participant (UTC milliseconds). `null` for unapproved or unfulfilled rewards."},"isReferrer":{"type":"boolean","readOnly":true,"description":"`true` if the participant earned the reward as the referrer; `false` if they earned it as the referred friend (only applicable for double-sided reward types)."},"isAvailable":{"type":"boolean","readOnly":true,"description":"`true` if the reward is available for the participant to claim or redeem."},"isFulfilled":{"type":"boolean","readOnly":true,"description":"`true` if the participant's reward has been fulfilled."},"referredId":{"type":"string","readOnly":true,"description":"The ID of the friend that was referred."},"referrerId":{"type":"string","readOnly":true,"description":"The ID of the participant that made the referral."},"amount":{"type":["number","null"],"readOnly":true,"description":"What this reward delivered, in major units, for the reward rails that record a delivered amount on the reward itself (gift cards today). Read from this reward, never from the Campaign Reward's configured amount, so it reports what the participant actually received. `null` for every other reward, and `null` whenever the reward carries no trustworthy amount and currency pair."},"currencyISO":{"type":["string","null"],"readOnly":true,"description":"The ISO 4217 currency code for `amount`. `null` whenever `amount` is `null`."},"commissionStructure":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CommissionStructure"},{"type":"null"}],"description":"The commission structure associated with this reward. Present only for affiliate programs."}}},"RewardStatus":{"type":"string","enum":["PENDING","FULFILLED","CANCELLED"],"description":"The reward lifecycle state. `CANCELLED` means an unpaid `LEAD` reward was reversed before fulfillment."},"CommissionStructure":{"type":"object","description":"The commission configuration for an affiliate reward. Present only for affiliate programs.","properties":{"amount":{"type":["integer","null"],"minimum":1,"description":"Fixed commission amount in the currency's smallest denomination, used when `type` is `FIXED`. `null` for percentage-based commissions."},"amountISO":{"type":["string","null"],"description":"ISO 4217 currency code for the fixed `amount`. Defaults to the program's currency when omitted. Must match the campaign `currencyISO` when provided. `null` for percentage-based commissions."},"event":{"type":["string","null"],"enum":["CLICK","LEAD","SALE",null],"description":"The event that generates a commission. Missing legacy values read as `SALE`."},"type":{"type":["string","null"],"enum":["PERCENT","FIXED",null],"description":"How the commission is calculated: `PERCENT` (a percentage of the sale) or `FIXED` (a fixed `amount`)."},"minPaidReferrals":{"type":["integer","null"],"description":"The minimum number of paid referrals required before commissions are earned."},"holdDuration":{"type":["integer","null"],"description":"Number of days a commission is held before it can be paid out."},"duration":{"type":["string","null"],"description":"How long commissions continue to be earned for a referred customer: `FOREVER`, `REPEATING`, or `ONCE`. Only applies when `event` is `SALE`. `null` for `CLICK` and `LEAD` commissions, which pay once per event."},"durationInMonths":{"type":["integer","null"],"description":"When `duration` is `REPEATING`, the number of months over which commissions are earned. `null` otherwise."},"approvalRequired":{"type":["boolean","null"],"description":"`true` if commissions require manual approval before they can be paid out."},"percent":{"type":["number","null"],"description":"The commission percentage, used when `type` is `PERCENT`."},"hasMaxAmount":{"type":["boolean","null"],"description":"`true` if a maximum commission amount cap is configured."},"maxAmount":{"type":["integer","null"],"description":"The maximum commission amount cap in the currency's smallest denomination. `null` if no cap is set."},"maxAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `maxAmount`. Must match the campaign `currencyISO` when provided."},"hasIntro":{"type":["boolean","null"],"description":"`true` if an introductory commission rate is configured. Only applies when `event` is `SALE`. Always `false` for `CLICK` and `LEAD` commissions, whose `intro*` fields are `null`."},"introType":{"type":["string","null"],"description":"How the introductory commission is calculated: `PERCENT` or `FIXED`."},"introPercent":{"type":["number","null"],"description":"The introductory commission percentage, used when `introType` is `PERCENT`."},"introAmount":{"type":["integer","null"],"description":"The introductory commission amount in the currency's smallest denomination, used when `introType` is `FIXED`."},"introAmountISO":{"type":["string","null"],"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code for `introAmount`. Must match the campaign `currencyISO` when provided."},"introDuration":{"type":["string","null"],"description":"How long the introductory rate applies."},"introDurationInMonths":{"type":["integer","null"],"description":"When `introDuration` is repeating, the number of months the introductory rate applies."}},"additionalProperties":false},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/rewards":{"get":{"tags":["Participant Rewards"],"operationId":"listParticipantRewards","summary":"List participant rewards","description":"Retrieves a paged list of rewards earned by a participant.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"},{"$ref":"#/components/parameters/NextId"},{"$ref":"#/components/parameters/Limit100"}],"responses":{"200":{"description":"Participant rewards returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantRewardListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Approve participant reward

> Approves a manually approved reward earned by a participant. This requires \`reward:write\`. When the request also sets \`fulfill\` to \`true\`, it additionally requires \`reward:fulfill\`.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participant Rewards","description":"Participant reward retrieval and manual reward operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"RewardId":{"name":"rewardId","in":"path","required":true,"description":"Participant reward ID.","schema":{"type":"string"}}},"schemas":{"ApproveRewardRequest":{"type":"object","properties":{"fulfill":{"type":"boolean","default":false,"description":"Set true to mark the reward as fulfilled after approval."}}},"SuccessResponse":{"type":"object","required":["success"],"properties":{"success":{"description":"Whether the request succeeded.","type":"boolean"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"Success":{"description":"Success response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InvalidState":{"description":"Resource is not in a state that permits this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/reward/{rewardId}/approve":{"post":{"tags":["Participant Rewards"],"operationId":"approveParticipantReward","summary":"Approve participant reward","description":"Approves a manually approved reward earned by a participant. This requires `reward:write`. When the request also sets `fulfill` to `true`, it additionally requires `reward:fulfill`.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/RewardId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApproveRewardRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/Success"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"406":{"$ref":"#/components/responses/InvalidState"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Fulfill participant reward

> Marks an approved participant reward as fulfilled.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participant Rewards","description":"Participant reward retrieval and manual reward operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"RewardId":{"name":"rewardId","in":"path","required":true,"description":"Participant reward ID.","schema":{"type":"string"}}},"responses":{"Success":{"description":"Success response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InvalidState":{"description":"Resource is not in a state that permits this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"SuccessResponse":{"type":"object","required":["success"],"properties":{"success":{"description":"Whether the request succeeded.","type":"boolean"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}}},"paths":{"/campaign/{id}/reward/{rewardId}/fulfill":{"post":{"tags":["Participant Rewards"],"operationId":"fulfillParticipantReward","summary":"Fulfill participant reward","description":"Marks an approved participant reward as fulfilled.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/RewardId"}],"responses":{"200":{"$ref":"#/components/responses/Success"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"406":{"$ref":"#/components/responses/InvalidState"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Delete participant reward

> Removes a manually approved participant reward that has not already been approved.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Participant Rewards","description":"Participant reward retrieval and manual reward operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"RewardId":{"name":"rewardId","in":"path","required":true,"description":"Participant reward ID.","schema":{"type":"string"}}},"responses":{"Success":{"description":"Success response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InvalidState":{"description":"Resource is not in a state that permits this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"SuccessResponse":{"type":"object","required":["success"],"properties":{"success":{"description":"Whether the request succeeded.","type":"boolean"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}}},"paths":{"/campaign/{id}/reward/{rewardId}":{"delete":{"tags":["Participant Rewards"],"operationId":"deleteParticipantReward","summary":"Delete participant reward","description":"Removes a manually approved participant reward that has not already been approved.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/RewardId"}],"responses":{"200":{"$ref":"#/components/responses/Success"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"406":{"$ref":"#/components/responses/InvalidState"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## REFERRALS AND INVITES ↓

## List referrals and invites

> Retrieves a list of all referrals and email invites made by participants in a program.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Referrals and Invites","description":"Referral, invite, and participant email invite operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ReferralSortBy":{"name":"sortBy","in":"query","required":false,"description":"Field used to sort referral results.","schema":{"type":"string","enum":["updatedAt","createdAt","email","firstName","lastName","referralStatus","referralTriggeredAt"],"default":"updatedAt"}},"Desc":{"name":"desc","in":"query","required":false,"description":"Return results in descending order when true.","schema":{"type":"boolean","default":true}},"Limit100":{"name":"limit","in":"query","required":false,"description":"Number of results to return. Maximum 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}},"Offset":{"name":"offset","in":"query","required":false,"description":"Offset number used to skip through a result set.","schema":{"type":"integer","minimum":0,"default":0}},"ReferralEmailFilter":{"name":"email","in":"query","required":false,"description":"URL-encoded email value to filter referral results.","schema":{"type":"string"}},"FirstNameFilter":{"name":"firstName","in":"query","required":false,"description":"First name value to filter results.","schema":{"type":"string"}},"LastNameFilter":{"name":"lastName","in":"query","required":false,"description":"Last name value to filter results.","schema":{"type":"string"}},"ReferralStatusFilter":{"name":"referralStatus","in":"query","required":false,"description":"Referral status to filter results.","schema":{"$ref":"#/components/schemas/ReferralStatus"}},"NextId":{"name":"nextId","in":"query","required":false,"description":"ID to start the next paged result set with.","schema":{"type":"string"}}},"schemas":{"ReferralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED","CREDIT_EXPIRED","INVITE_SENT"]},"ReferralListResponse":{"type":"object","required":["referrals","limit","more"],"properties":{"referrals":{"description":"Referrals and invites returned for this page.","type":"array","items":{"$ref":"#/components/schemas/Referral"}},"limit":{"description":"Maximum number of referrals requested for this page.","type":"integer"},"nextOffset":{"description":"Offset to pass as `offset` for the next page, or `null` when offset pagination is not used.","type":["integer","null"]},"nextId":{"description":"Referral ID to pass as `nextId` for the next page, or `null` when there are no more results.","type":["string","null"]},"more":{"description":"Whether another page of referrals is available.","type":"boolean"}}},"Referral":{"type":"object","required":["id","email","referralStatus","referredBy","createdAt","updatedAt"],"properties":{"id":{"description":"Participant ID for the referred friend or invitee.","type":"string"},"email":{"description":"Email address for the referred friend or invitee.","type":"string"},"firstName":{"description":"First name for the referred friend or invitee.","type":["string","null"]},"lastName":{"description":"Last name for the referred friend or invitee.","type":["string","null"]},"referralStatus":{"description":"Current referral status for this referred friend or invite.","$ref":"#/components/schemas/ReferralStatus"},"referredBy":{"description":"Participant ID of the referrer.","type":"string"},"createdAt":{"description":"When the referral or invite was created, as a Unix timestamp in milliseconds.","type":"integer","format":"int64"},"updatedAt":{"description":"When the referral or invite was last updated, as a Unix timestamp in milliseconds.","type":"integer","format":"int64"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/referrals":{"get":{"tags":["Referrals and Invites"],"operationId":"listReferrals","summary":"List referrals and invites","description":"Retrieves a list of all referrals and email invites made by participants in a program.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ReferralSortBy"},{"$ref":"#/components/parameters/Desc"},{"$ref":"#/components/parameters/Limit100"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/ReferralEmailFilter"},{"$ref":"#/components/parameters/FirstNameFilter"},{"$ref":"#/components/parameters/LastNameFilter"},{"$ref":"#/components/parameters/ReferralStatusFilter"},{"$ref":"#/components/parameters/NextId"}],"responses":{"200":{"description":"Referrals and invites returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List participant referrals and invites

> Retrieves referrals and email invites made by a participant.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Referrals and Invites","description":"Referral, invite, and participant email invite operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}},"ReferralSortBy":{"name":"sortBy","in":"query","required":false,"description":"Field used to sort referral results.","schema":{"type":"string","enum":["updatedAt","createdAt","email","firstName","lastName","referralStatus","referralTriggeredAt"],"default":"updatedAt"}},"Desc":{"name":"desc","in":"query","required":false,"description":"Return results in descending order when true.","schema":{"type":"boolean","default":true}},"Limit100":{"name":"limit","in":"query","required":false,"description":"Number of results to return. Maximum 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}},"Offset":{"name":"offset","in":"query","required":false,"description":"Offset number used to skip through a result set.","schema":{"type":"integer","minimum":0,"default":0}},"ReferralEmailFilter":{"name":"email","in":"query","required":false,"description":"URL-encoded email value to filter referral results.","schema":{"type":"string"}},"FirstNameFilter":{"name":"firstName","in":"query","required":false,"description":"First name value to filter results.","schema":{"type":"string"}},"LastNameFilter":{"name":"lastName","in":"query","required":false,"description":"Last name value to filter results.","schema":{"type":"string"}},"ReferralStatusFilter":{"name":"referralStatus","in":"query","required":false,"description":"Referral status to filter results.","schema":{"$ref":"#/components/schemas/ReferralStatus"}},"NextId":{"name":"nextId","in":"query","required":false,"description":"ID to start the next paged result set with.","schema":{"type":"string"}}},"schemas":{"ReferralStatus":{"type":"string","enum":["CREDIT_PENDING","CREDIT_AWARDED","CREDIT_EXPIRED","INVITE_SENT"]},"ReferralListResponse":{"type":"object","required":["referrals","limit","more"],"properties":{"referrals":{"description":"Referrals and invites returned for this page.","type":"array","items":{"$ref":"#/components/schemas/Referral"}},"limit":{"description":"Maximum number of referrals requested for this page.","type":"integer"},"nextOffset":{"description":"Offset to pass as `offset` for the next page, or `null` when offset pagination is not used.","type":["integer","null"]},"nextId":{"description":"Referral ID to pass as `nextId` for the next page, or `null` when there are no more results.","type":["string","null"]},"more":{"description":"Whether another page of referrals is available.","type":"boolean"}}},"Referral":{"type":"object","required":["id","email","referralStatus","referredBy","createdAt","updatedAt"],"properties":{"id":{"description":"Participant ID for the referred friend or invitee.","type":"string"},"email":{"description":"Email address for the referred friend or invitee.","type":"string"},"firstName":{"description":"First name for the referred friend or invitee.","type":["string","null"]},"lastName":{"description":"Last name for the referred friend or invitee.","type":["string","null"]},"referralStatus":{"description":"Current referral status for this referred friend or invite.","$ref":"#/components/schemas/ReferralStatus"},"referredBy":{"description":"Participant ID of the referrer.","type":"string"},"createdAt":{"description":"When the referral or invite was created, as a Unix timestamp in milliseconds.","type":"integer","format":"int64"},"updatedAt":{"description":"When the referral or invite was last updated, as a Unix timestamp in milliseconds.","type":"integer","format":"int64"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/referrals":{"get":{"tags":["Referrals and Invites"],"operationId":"listParticipantReferrals","summary":"List participant referrals and invites","description":"Retrieves referrals and email invites made by a participant.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"},{"$ref":"#/components/parameters/ReferralSortBy"},{"$ref":"#/components/parameters/Desc"},{"$ref":"#/components/parameters/Limit100"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/ReferralEmailFilter"},{"$ref":"#/components/parameters/FirstNameFilter"},{"$ref":"#/components/parameters/LastNameFilter"},{"$ref":"#/components/parameters/ReferralStatusFilter"},{"$ref":"#/components/parameters/NextId"}],"responses":{"200":{"description":"Participant referrals and invites returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Send participant invites

> Sends email invites on behalf of a participant to a list of email addresses. Sending invites via the API requires a \*\*verified custom email domain\*\* on the program; the request fails until one is verified.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Referrals and Invites","description":"Referral, invite, and participant email invite operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"SendInvitesRequest":{"type":"object","required":["emailAddresses","messageText","subjectText"],"properties":{"emailAddresses":{"description":"Email addresses to invite.","type":"array","minItems":1,"items":{"type":"string","format":"email"}},"messageText":{"description":"Message body for the invite email.","type":"string"},"subjectText":{"description":"Subject line for the invite email.","type":"string"}}},"SendInvitesResponse":{"type":"object","required":["success","messageType","invitesSent"],"properties":{"success":{"description":"Whether the invites request succeeded.","type":"boolean"},"messageType":{"description":"Result category for the invites request.","type":"string"},"invitesSent":{"description":"Number of invite emails sent.","type":"integer"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/invites":{"post":{"tags":["Referrals and Invites"],"operationId":"sendParticipantInvites","summary":"Send participant invites","description":"Sends email invites on behalf of a participant to a list of email addresses. Sending invites via the API requires a **verified custom email domain** on the program; the request fails until one is verified.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendInvitesRequest"}}}},"responses":{"200":{"description":"Invite send result returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendInvitesResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## ANALYTICS ↓

{% hint style="info" %}
Engagement and activation answer different date questions. Engagement uses activity periods: portal views and share actions are grouped by when they happened. Activation uses enrollment cohorts: referral participants are grouped by `enrolledAsAdvocateAt`, affiliate participants by `approvedAsAffiliateAt`, and each person gets the selected 7- or 30-day observation window.

The strict activation stages are ordered. `ELIGIBLE` means the participant reached the program-specific eligibility milestone. `PORTAL_VIEWED` means GrowSurf observed a signed-in participant portal view. `SHARE_ACTION` means GrowSurf accepted a referral-link share action. `UNIQUE_REFERRAL_VISIT` means a unique visitor opened the participant's referral link. `LEAD` means a referred lead was recorded. `CREDITED_REFERRAL` means referral credit or an affiliate conversion was awarded. First reward, first commission, and payout setup are separate outcomes, not strict stages.

Use `coverageStartAt` with each `state` and `reason`. A `null` value or an unavailable pre-coverage milestone means GrowSurf does not know the historical value. It does not mean the action never happened. Use [Retrieve participant analytics](https://docs.growsurf.com/developer-tools/rest-api/api-reference#get-campaign-id-participant-participantidoremail-analytics) with `include=activation` for one participant's covered first milestones.
{% endhint %}

## Retrieve campaign analytics

> Retrieves analytics for a program. Pass \`interval\` to also get a time-series (\`series\`) alongside the totals, and \`include\` to add previous-period totals, status breakdowns, derived rates, email performance, or participant engagement. Add \`email\` to \`include\` for \`sent\` (accepted for delivery), \`delivered\`, \`opened\`, \`clicked\`, \`bounced\`, and \`spamComplaints\` metrics plus per-email-type breakdowns. Add \`engagement\` for unique active, sharing, repeat, and retained participants plus portal-view and share-action totals. Email rates and engagement comparisons are ratios from \`0\` to \`1\`. Email \`isPartial\` and engagement coverage states distinguish unavailable or partial data from measured zeroes.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Analytics","description":"Program analytics."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"AnalyticsDays":{"name":"days","in":"query","required":false,"description":"Last number of days to retrieve analytics for. Defaults to 365. Maximum 1825.","schema":{"type":"integer","minimum":1,"maximum":1825,"default":365}},"AnalyticsStartDate":{"name":"startDate","in":"query","required":false,"description":"Start date of the analytics timeframe as a Unix timestamp in milliseconds. Required if `days` is not set.","schema":{"type":"integer","format":"int64"}},"AnalyticsEndDate":{"name":"endDate","in":"query","required":false,"description":"End date of the analytics timeframe as a Unix timestamp in milliseconds. Required if `days` is not set.","schema":{"type":"integer","format":"int64"}}},"schemas":{"CampaignAnalyticsResponse":{"type":"object","required":["analytics","startDate","endDate"],"properties":{"analytics":{"description":"Campaign analytics totals for the requested timeframe.","$ref":"#/components/schemas/CampaignAnalytics"},"startDate":{"description":"Start of the analytics timeframe, as a Unix timestamp in milliseconds.","type":"integer","format":"int64"},"endDate":{"description":"End of the analytics timeframe, as a Unix timestamp in milliseconds.","type":"integer","format":"int64"},"email":{"description":"Present only when `include` contains `email`.","$ref":"#/components/schemas/EmailAnalytics"},"series":{"type":"array","description":"Present only when `interval` is `day`, `week`, or `month`. Per-period totals, ascending.","items":{"allOf":[{"type":"object","properties":{"periodStart":{"type":"integer","format":"int64","description":"Start of the period, as a Unix timestamp in milliseconds (UTC)."},"email":{"description":"Present in each period only when `include` contains `email`.","$ref":"#/components/schemas/EmailAnalyticsCounts"}}},{"$ref":"#/components/schemas/CampaignAnalytics"}]}},"previousPeriod":{"description":"Present only when `include` contains `previousPeriod`.","allOf":[{"$ref":"#/components/schemas/PreviousPeriodAnalytics"}]},"statusCounts":{"description":"Present only when `include` contains `statusCounts`.","allOf":[{"$ref":"#/components/schemas/CampaignStatusCounts"}]},"rates":{"description":"Present only when `include` contains `rates`.","allOf":[{"$ref":"#/components/schemas/CampaignAnalyticsRates"}]},"engagement":{"description":"Present only when `include` contains `engagement`.","$ref":"#/components/schemas/CampaignEngagementAnalytics"}}},"CampaignAnalytics":{"type":"object","properties":{"invites":{"description":"Number of invites sent by participants.","type":"integer"},"impressions":{"description":"Total referral-link views.","type":"integer"},"uniqueImpressions":{"description":"Unique referral-link views.","type":"integer"},"participants":{"description":"Number of participants added.","type":"integer"},"referrals":{"description":"Number of referrals credited.","type":"integer"},"referralCreditPendings":{"description":"Number of referrals waiting for credit.","type":"integer"},"referralCreditExpireds":{"description":"Number of referrals whose credit window expired.","type":"integer"},"emailShares":{"description":"Number of shares through email.","type":"integer"},"facebookShares":{"description":"Number of shares through Facebook.","type":"integer"},"twitterShares":{"description":"Number of shares through Twitter/X.","type":"integer"},"threadsShares":{"description":"Number of shares through Threads.","type":"integer"},"blueskyShares":{"description":"Number of shares through Bluesky.","type":"integer"},"pinterestShares":{"description":"Number of shares through Pinterest.","type":"integer"},"linkedInShares":{"description":"Number of shares through LinkedIn.","type":"integer"},"smsShares":{"description":"Number of shares through SMS.","type":"integer"},"messengerShares":{"description":"Number of shares through Messenger.","type":"integer"},"whatsAppShares":{"description":"Number of shares through WhatsApp.","type":"integer"},"wechatShares":{"description":"Number of shares through WeChat.","type":"integer"},"telegramShares":{"description":"Number of shares through Telegram.","type":"integer"},"qrcodeShares":{"description":"Number of shares through QR code.","type":"integer"},"redditShares":{"description":"Number of shares through Reddit.","type":"integer"},"tumblrShares":{"description":"Number of shares through Tumblr.","type":"integer"},"copyRefLinkShares":{"description":"Number of times participants copied their referral link.","type":"integer"},"iosNativeShares":{"description":"Number of shares through iOS native sharing.","type":"integer"},"androidNativeShares":{"description":"Number of shares through Android native sharing.","type":"integer"},"totalRevenue":{"type":"integer","description":"Affiliate programs only. Revenue in the smallest unit of the program currency."},"totalCommissions":{"type":"integer","description":"Affiliate programs only. Commissions in the smallest unit of the program currency."},"totalCommissionCount":{"type":"integer","description":"Affiliate programs only. Number of commission records."},"uniqueCommissionReferrals":{"type":"integer","description":"Affiliate programs only. Number of unique referred participants represented by commissions in the requested timeframe."}}},"EmailAnalytics":{"allOf":[{"$ref":"#/components/schemas/EmailAnalyticsMetrics"},{"type":"object","required":["byType","coverageStartDate","isPartial"],"properties":{"byType":{"type":"array","description":"Email metrics grouped by configured email type, ordered by `emailType`.","items":{"$ref":"#/components/schemas/EmailAnalyticsByType"}},"coverageStartDate":{"description":"Earliest date from which email analytics capture is expected to be complete, as a Unix timestamp in milliseconds, or `null` while the rollout cutoff is unavailable.","type":["integer","null"],"format":"int64"},"isPartial":{"type":"boolean","description":"Whether the requested window begins before `coverageStartDate`."}}}]},"EmailAnalyticsMetrics":{"allOf":[{"$ref":"#/components/schemas/EmailAnalyticsCounts"},{"type":"object","required":["deliveryRate","openRate","clickRate","bounceRate"],"properties":{"deliveryRate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Delivered emails divided by accepted emails, or `0` when none were accepted."},"openRate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Opened emails divided by delivered emails, or `0` when none were delivered."},"clickRate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Clicked emails divided by delivered emails, or `0` when none were delivered."},"bounceRate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Bounced emails divided by accepted emails, or `0` when none were accepted."}}}]},"EmailAnalyticsCounts":{"type":"object","required":["sent","delivered","opened","clicked","bounced","spamComplaints"],"properties":{"sent":{"type":"integer","minimum":0,"description":"Number of emails accepted for delivery."},"delivered":{"type":"integer","minimum":0,"description":"Number of emails delivered or subsequently opened or clicked."},"opened":{"type":"integer","minimum":0,"description":"Number of emails opened at least once."},"clicked":{"type":"integer","minimum":0,"description":"Number of emails with at least one tracked link click."},"bounced":{"type":"integer","minimum":0,"description":"Number of emails that bounced."},"spamComplaints":{"type":"integer","minimum":0,"description":"Number of emails reported as spam."}}},"EmailAnalyticsByType":{"allOf":[{"type":"object","required":["emailType"],"properties":{"emailType":{"type":"string","description":"Configured program email type or step."}}},{"$ref":"#/components/schemas/EmailAnalyticsMetrics"}]},"PreviousPeriodAnalytics":{"type":"object","description":"Totals for the equal-length window immediately preceding the requested one.","required":["analytics","startDate","endDate"],"properties":{"analytics":{"description":"Campaign analytics totals for the previous comparison timeframe.","$ref":"#/components/schemas/CampaignAnalytics"},"startDate":{"description":"Start of the previous comparison timeframe, as a Unix timestamp in milliseconds.","type":"integer","format":"int64"},"endDate":{"description":"End of the previous comparison timeframe, as a Unix timestamp in milliseconds.","type":"integer","format":"int64"},"email":{"description":"Present only when the parent request includes both `previousPeriod` and `email`.","$ref":"#/components/schemas/EmailAnalytics"}}},"CampaignStatusCounts":{"type":"object","description":"Status-count breakdowns. `rewardStatus` is present for every program; `affiliateStatus`, `commissionStatus`, and `payoutStatus` are present only for affiliate programs. Money amounts are in minor units of `currencyISO`.","properties":{"currencyISO":{"description":"Program currency for money amounts in the status-count breakdown.","type":"string"},"rewardStatus":{"description":"Reward counts grouped by review and fulfillment status.","$ref":"#/components/schemas/RewardStatusCounts"},"affiliateStatus":{"type":"object","description":"Affiliate only. Participant counts keyed by affiliate status.","additionalProperties":{"type":"integer"}},"commissionStatus":{"type":"object","description":"Affiliate only. Commission counts and amounts by status.","properties":{"pending":{"description":"Pending commissions.","$ref":"#/components/schemas/CommissionStatusMetric"},"approved":{"description":"Approved commissions.","$ref":"#/components/schemas/CommissionStatusMetric"},"paid":{"description":"Paid commissions.","$ref":"#/components/schemas/CommissionStatusMetric"},"reversed":{"description":"Reversed commissions.","$ref":"#/components/schemas/CommissionStatusMetric"}}},"payoutStatus":{"type":"object","description":"Affiliate only. Payout counts and amounts by status.","properties":{"upcoming":{"description":"Upcoming payouts.","$ref":"#/components/schemas/PayoutStatusMetric"},"queued":{"description":"Queued payouts.","$ref":"#/components/schemas/PayoutStatusMetric"},"issued":{"description":"Issued payouts.","$ref":"#/components/schemas/PayoutStatusMetric"},"failed":{"description":"Failed payouts.","$ref":"#/components/schemas/PayoutStatusMetric"},"reversed":{"description":"Reversed payouts.","$ref":"#/components/schemas/PayoutStatusMetric"}}}}},"RewardStatusCounts":{"type":"object","description":"Reward counts grouped by review and fulfillment status.","properties":{"unapproved":{"description":"Unapproved rewards awaiting review.","type":"integer"},"unfulfilled":{"description":"Rewards that are approved but not fulfilled.","type":"integer"},"completed":{"description":"Approved rewards that are fulfilled.","type":"integer"}}},"CommissionStatusMetric":{"type":"object","properties":{"count":{"description":"Number of commissions in this status.","type":"integer"},"totalAmount":{"type":"integer","description":"Total commission amount in minor currency units."},"totalRevenue":{"type":"integer","description":"Total attributed revenue in minor currency units."}}},"PayoutStatusMetric":{"type":"object","properties":{"count":{"description":"Number of payouts in this status.","type":"integer"},"totalAmount":{"type":"integer","description":"Total payout amount in minor currency units."}}},"CampaignAnalyticsRates":{"type":"object","description":"Derived referral rates, each a ratio in the range 0–1 (0 when its denominator is 0).","properties":{"referralConversionRate":{"type":"number","format":"float","description":"`referrals` divided by `uniqueImpressions`."},"participationRate":{"type":"number","format":"float","description":"`participants` divided by `uniqueImpressions`."},"sharesPerParticipant":{"type":"number","format":"float","description":"Total shares across all channels divided by `participants`."}}},"CampaignEngagementAnalytics":{"type":"object","description":"Opt-in participant engagement for one exact activity period.","required":["coverageStartAt","metricContractVersion","programType","timezone","interval","platform","period","state","reason","totals","previousPeriod","comparison","series","breakdowns"],"properties":{"coverageStartAt":{"type":["integer","null"],"format":"int64","description":"Earliest date from which engagement capture is expected to be complete, or `null` until coverage begins."},"metricContractVersion":{"type":"integer","minimum":1,"description":"Version of the shared activation and engagement metric definitions."},"programType":{"description":"Program type that determines participant eligibility.","$ref":"#/components/schemas/CampaignType"},"timezone":{"type":"string","description":"IANA timezone used for interval and distinct-day calculations."},"interval":{"type":"string","enum":["day","week","month"],"description":"Bucket size used for `series`."},"platform":{"description":"Requested and applied platform filter.","$ref":"#/components/schemas/ParticipantEngagementPlatformFilter"},"period":{"description":"Exact current and previous activity bounds.","$ref":"#/components/schemas/ParticipantEngagementPeriod"},"state":{"description":"Overall engagement availability.","$ref":"#/components/schemas/AnalyticsAvailability"},"reason":{"description":"Why engagement is partial or unavailable, or `null` when it is complete.","oneOf":[{"$ref":"#/components/schemas/AnalyticsUnavailableReason"},{"type":"null"}]},"totals":{"description":"Current-period unique participant metrics and action totals.","$ref":"#/components/schemas/ParticipantEngagementTotals"},"previousPeriod":{"description":"Immediately previous equal-period totals.","$ref":"#/components/schemas/ParticipantEngagementPreviousPeriod"},"comparison":{"description":"Changes from the immediately previous equal period.","$ref":"#/components/schemas/ParticipantEngagementComparison"},"series":{"type":"array","description":"Continuous engagement intervals in ascending order. Empty when unavailable.","items":{"$ref":"#/components/schemas/ParticipantEngagementSeriesPoint"}},"breakdowns":{"description":"Engagement grouped by platform, portal source, and share channel.","$ref":"#/components/schemas/ParticipantEngagementBreakdowns"}}},"CampaignType":{"type":"string","enum":["REFERRAL","AFFILIATE"]},"ParticipantEngagementPlatformFilter":{"type":"object","description":"Requested and applied client-platform filter.","required":["requested","applied","state"],"properties":{"requested":{"type":"string","description":"Platform requested by the caller.","enum":["ALL","WEB","IOS","ANDROID"]},"applied":{"type":"string","description":"Platform applied to the response.","enum":["ALL","WEB","IOS","ANDROID"]},"state":{"description":"Availability of the platform filter.","$ref":"#/components/schemas/AnalyticsAvailability"}}},"AnalyticsAvailability":{"type":"string","description":"Whether a metric is complete, partial, or unavailable for the requested bounds.","enum":["AVAILABLE","PARTIAL","UNAVAILABLE"]},"ParticipantEngagementPeriod":{"type":"object","description":"Exact half-open activity and comparison bounds.","required":["from","to","effectiveFrom","previousFrom","previousTo"],"properties":{"from":{"type":"integer","format":"int64","description":"Inclusive requested activity start, as a Unix timestamp in milliseconds."},"to":{"type":"integer","format":"int64","description":"Exclusive requested activity end, as a Unix timestamp in milliseconds."},"effectiveFrom":{"type":["integer","null"],"format":"int64","description":"Inclusive measured start after coverage is applied, or `null` when unavailable."},"previousFrom":{"type":"integer","format":"int64","description":"Inclusive start of the immediately previous equal period."},"previousTo":{"type":"integer","format":"int64","description":"Exclusive end of the immediately previous equal period."}}},"AnalyticsUnavailableReason":{"type":"string","description":"Why a metric or response is partial or unavailable.","enum":["COVERAGE_UNAVAILABLE","PRE_COVERAGE","PARTIAL_COVERAGE","INSUFFICIENT_COVERAGE","EMPTY_DENOMINATOR","QUERY_LIMIT_EXCEEDED","PARTICIPANT_NOT_ELIGIBLE"]},"ParticipantEngagementTotals":{"type":"object","description":"Unique participant metrics and secondary action totals for one activity period.","required":["activeParticipants","sharingParticipants","sharingRate","repeatActiveParticipants","repeatSharingParticipants","retainedActiveParticipants","portalViews","shareActions"],"properties":{"activeParticipants":{"description":"Eligible participants with at least one signed-in portal view.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"sharingParticipants":{"description":"Eligible participants with at least one accepted share action.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"sharingRate":{"description":"Unique sharing participants divided by unique active participants.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"repeatActiveParticipants":{"description":"Eligible participants active on at least two distinct program-local days.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"repeatSharingParticipants":{"description":"Eligible participants who shared on at least two distinct program-local days.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"retainedActiveParticipants":{"description":"Eligible participants active in both the requested and previous equal periods.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"portalViews":{"description":"Total accepted signed-in portal-view actions.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"shareActions":{"description":"Total accepted referral-link share actions.","$ref":"#/components/schemas/ParticipantEngagementMetric"}}},"ParticipantEngagementMetric":{"type":"object","description":"One engagement value with an explicit availability state.","required":["state","value","reason"],"properties":{"state":{"description":"Availability of this value.","$ref":"#/components/schemas/AnalyticsAvailability"},"value":{"type":["number","null"],"description":"Measured value, or `null` when it is unavailable."},"reason":{"description":"Why the value is unavailable, or `null` when it is available.","oneOf":[{"$ref":"#/components/schemas/AnalyticsUnavailableReason"},{"type":"null"}]},"delta":{"type":"number","description":"Optional absolute current-minus-previous difference on comparison metrics."}}},"ParticipantEngagementPreviousPeriod":{"type":"object","description":"Engagement totals for the immediately previous equal period.","required":["state","reason","totals"],"properties":{"state":{"description":"Availability of the previous period.","$ref":"#/components/schemas/AnalyticsAvailability"},"reason":{"description":"Why the previous period is unavailable, or `null` when it is available.","oneOf":[{"$ref":"#/components/schemas/AnalyticsUnavailableReason"},{"type":"null"}]},"totals":{"description":"Previous-period totals, or `null` when coverage is insufficient.","oneOf":[{"$ref":"#/components/schemas/ParticipantEngagementTotals"},{"type":"null"}]}}},"ParticipantEngagementComparison":{"type":"object","description":"Percentage and absolute changes from the immediately previous equal period.","required":["state","reason","metrics"],"properties":{"state":{"description":"Availability of the comparison.","$ref":"#/components/schemas/AnalyticsAvailability"},"reason":{"description":"Why the comparison is unavailable, or `null` when it is available.","oneOf":[{"$ref":"#/components/schemas/AnalyticsUnavailableReason"},{"type":"null"}]},"metrics":{"description":"Comparison metrics, or `null` when coverage is insufficient.","oneOf":[{"$ref":"#/components/schemas/ParticipantEngagementComparisonMetrics"},{"type":"null"}]}}},"ParticipantEngagementComparisonMetrics":{"type":"object","description":"Current-versus-previous changes for comparable engagement metrics.","properties":{"activeParticipants":{"description":"Change in unique active participants.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"sharingParticipants":{"description":"Change in unique sharing participants.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"repeatActiveParticipants":{"description":"Change in repeat active participants.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"repeatSharingParticipants":{"description":"Change in repeat sharing participants.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"portalViews":{"description":"Change in total signed-in portal views.","$ref":"#/components/schemas/ParticipantEngagementMetric"},"shareActions":{"description":"Change in total share actions.","$ref":"#/components/schemas/ParticipantEngagementMetric"}}},"ParticipantEngagementSeriesPoint":{"type":"object","description":"Engagement counts for one exact half-open interval.","required":["from","to","activeParticipants","sharingParticipants","portalViews","shareActions"],"properties":{"from":{"type":"integer","format":"int64","description":"Inclusive interval start, as a Unix timestamp in milliseconds."},"to":{"type":"integer","format":"int64","description":"Exclusive interval end, as a Unix timestamp in milliseconds."},"activeParticipants":{"type":"integer","minimum":0,"description":"Unique active participants in this interval."},"sharingParticipants":{"type":"integer","minimum":0,"description":"Unique sharing participants in this interval."},"portalViews":{"type":"integer","minimum":0,"description":"Total signed-in portal views in this interval."},"shareActions":{"type":"integer","minimum":0,"description":"Total accepted share actions in this interval."}}},"ParticipantEngagementBreakdowns":{"type":"object","description":"Platform, portal-source, and share-channel engagement breakdowns.","required":["platforms","portalViewSources","shareChannels","firstShareChannels"],"properties":{"platforms":{"type":"array","description":"Engagement grouped by client platform.","items":{"$ref":"#/components/schemas/ParticipantEngagementPlatformBreakdown"}},"portalViewSources":{"type":"array","description":"Signed-in portal views grouped by source.","items":{"$ref":"#/components/schemas/ParticipantEngagementPortalSourceBreakdown"}},"shareChannels":{"type":"array","description":"Accepted share actions grouped by channel.","items":{"$ref":"#/components/schemas/ParticipantEngagementShareChannelBreakdown"}},"firstShareChannels":{"type":"array","description":"Eligible sharing participants grouped by first share channel.","items":{"$ref":"#/components/schemas/ParticipantEngagementFirstShareChannelBreakdown"}}}},"ParticipantEngagementPlatformBreakdown":{"type":"object","description":"Engagement counts for one client platform.","required":["key","activeParticipants","sharingParticipants","portalViews","shareActions"],"properties":{"key":{"type":"string","enum":["WEB","IOS","ANDROID"],"description":"Client platform."},"activeParticipants":{"type":"integer","minimum":0,"description":"Unique active participants on this platform."},"sharingParticipants":{"type":"integer","minimum":0,"description":"Unique sharing participants on this platform."},"portalViews":{"type":"integer","minimum":0,"description":"Total signed-in portal views on this platform."},"shareActions":{"type":"integer","minimum":0,"description":"Total accepted share actions on this platform."}}},"ParticipantEngagementPortalSourceBreakdown":{"type":"object","description":"Portal-view counts for one launcher or portal source.","required":["key","activeParticipants","portalViews"],"properties":{"key":{"type":"string","enum":["DEFAULT_LAUNCHER","SDK_OPEN","CSS_CLASS","EMBEDDABLE_ELEMENT","HOSTED_PORTAL","NATIVE_WINDOW","UNKNOWN"],"description":"Source that made the signed-in portal visible."},"activeParticipants":{"type":"integer","minimum":0,"description":"Unique active participants from this source."},"portalViews":{"type":"integer","minimum":0,"description":"Total signed-in portal views from this source."}}},"ParticipantEngagementShareChannelBreakdown":{"type":"object","description":"Sharing counts for one GrowSurf share channel.","required":["key","sharingParticipants","shareActions"],"properties":{"key":{"type":"string","description":"Stable share-channel key, such as `email`, `copyRefLink`, or `iosNativeShare`."},"sharingParticipants":{"type":"integer","minimum":0,"description":"Unique participants who used this channel."},"shareActions":{"type":"integer","minimum":0,"description":"Total accepted share actions through this channel."}}},"ParticipantEngagementFirstShareChannelBreakdown":{"type":"object","description":"Participants grouped by their first accepted share channel.","required":["key","sharingParticipants"],"properties":{"key":{"type":"string","description":"Stable first-share channel key."},"sharingParticipants":{"type":"integer","minimum":0,"description":"Participants whose first accepted share used this channel."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/analytics":{"get":{"tags":["Analytics"],"operationId":"retrieveCampaignAnalytics","summary":"Retrieve campaign analytics","description":"Retrieves analytics for a program. Pass `interval` to also get a time-series (`series`) alongside the totals, and `include` to add previous-period totals, status breakdowns, derived rates, email performance, or participant engagement. Add `email` to `include` for `sent` (accepted for delivery), `delivered`, `opened`, `clicked`, `bounced`, and `spamComplaints` metrics plus per-email-type breakdowns. Add `engagement` for unique active, sharing, repeat, and retained participants plus portal-view and share-action totals. Email rates and engagement comparisons are ratios from `0` to `1`. Email `isPartial` and engagement coverage states distinguish unavailable or partial data from measured zeroes.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/AnalyticsDays"},{"$ref":"#/components/parameters/AnalyticsStartDate"},{"$ref":"#/components/parameters/AnalyticsEndDate"},{"name":"interval","in":"query","description":"When set to `day`, `week`, or `month`, the response also includes a `series` array with per-period totals and uses the same bucket size for `engagement.series`. Defaults to `total` (no legacy series); `engagement.series` uses daily buckets when `interval` is `total` or omitted.","schema":{"type":"string","enum":["day","week","month","total"],"default":"total"}},{"name":"include","in":"query","description":"Comma-separated list of optional data to include: `previousPeriod` adds totals for the equal-length window immediately before the requested one; `statusCounts` adds reward (and, for affiliate programs, affiliate/commission/payout) status breakdowns; `rates` adds derived referral rates; `email` adds `sent`, `delivered`, `opened`, `clicked`, `bounced`, `spamComplaints`, and per-email-type metrics; `engagement` adds unique participant engagement, action totals, comparisons, and platform/source/channel breakdowns. When `email` and an interval are both requested, each `series` item also contains counts for emails sent during that period. Combine `email` with `previousPeriod` to include the same email metrics in both windows.","schema":{"type":"string"}},{"name":"timezone","in":"query","description":"IANA timezone used for engagement interval boundaries and distinct-day calculations. Used only when `include` contains `engagement`.","schema":{"type":"string","default":"UTC"}},{"name":"platform","in":"query","description":"Limits engagement events to one client platform. Used only when `include` contains `engagement`.","schema":{"type":"string","enum":["ALL","WEB","IOS","ANDROID"],"default":"ALL"}}],"responses":{"200":{"description":"Campaign analytics returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignAnalyticsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Retrieve campaign activation analytics

> Retrieves activation for eligible participants in one exact half-open enrollment cohort. \`aggregate\` covers the complete selected cohort, while \`cohorts\` splits that same range by \`cohortInterval\`. Each participant has the selected \`observationWindowDays\` after their eligibility date to reach each ordered stage. If \`cohortFrom\` and \`cohortTo\` are omitted, GrowSurf selects the latest fully matured rolling cohort. Unavailable and partial states preserve unknown pre-coverage data instead of reporting false zeroes. One request can return at most 100 cohort buckets.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Analytics","description":"Program analytics."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CampaignActivationAnalyticsResponse":{"type":"object","description":"One selected activation aggregate and its exact cohort-interval buckets.","required":["coverageStartAt","metricContractVersion","programType","timezone","cohortInterval","observationWindowDays","portalViewedLabel","portalViewedHelperText","aggregate","cohorts"],"properties":{"coverageStartAt":{"type":["integer","null"],"format":"int64","description":"Earliest date from which activation capture is expected to be complete, or `null` until coverage begins."},"metricContractVersion":{"type":"integer","minimum":1,"description":"Version of the shared activation and engagement metric definitions."},"programType":{"description":"Program type that determines eligibility and display labels.","$ref":"#/components/schemas/CampaignType"},"timezone":{"type":"string","description":"IANA timezone used to advance cohort bucket boundaries."},"cohortInterval":{"type":"string","enum":["day","week","month"],"description":"Bucket size used for `cohorts`."},"observationWindowDays":{"type":"integer","enum":[7,30],"description":"Days after each participant's eligibility date in which stages can count."},"portalViewedLabel":{"type":"string","description":"Program-specific display label for the stable `PORTAL_VIEWED` stage.","enum":["Referral portal viewed","Affiliate portal viewed"]},"portalViewedHelperText":{"type":"string","description":"Display helper that defines a qualifying portal view."},"aggregate":{"description":"Activation result for the complete selected cohort range.","$ref":"#/components/schemas/CampaignActivationCohortResult"},"cohorts":{"type":"array","maxItems":100,"description":"Selected range split into exact half-open interval buckets, ordered by `cohort.from`.","items":{"$ref":"#/components/schemas/CampaignActivationCohortResult"}}}},"CampaignType":{"type":"string","enum":["REFERRAL","AFFILIATE"]},"CampaignActivationCohortResult":{"type":"object","description":"Strict activation metrics for one exact cohort range.","required":["state","reason","cohort","strictStages","rawStageCounts","stalledSegments","outcomes","largestDrop"],"properties":{"state":{"description":"Availability of this cohort result.","$ref":"#/components/schemas/AnalyticsAvailability"},"reason":{"description":"Why the cohort is partial or unavailable, or `null` when it is complete.","oneOf":[{"$ref":"#/components/schemas/AnalyticsUnavailableReason"},{"type":"null"}]},"cohort":{"description":"Selected cohort and maturity dates.","$ref":"#/components/schemas/ActivationCohortBounds"},"strictStages":{"description":"Ordered monotonic stages, or `null` when this cohort is unavailable.","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/ActivationStage"}},{"type":"null"}]},"rawStageCounts":{"description":"Raw stage counts, or `null` when this cohort is unavailable.","oneOf":[{"$ref":"#/components/schemas/ActivationStageCounts"},{"type":"null"}]},"stalledSegments":{"description":"Consecutive-stage gaps, or `null` when this cohort is unavailable.","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/ActivationStalledSegment"}},{"type":"null"}]},"outcomes":{"description":"Program-specific outcomes, or `null` when this cohort is unavailable.","oneOf":[{"$ref":"#/components/schemas/ActivationOutcomes"},{"type":"null"}]},"largestDrop":{"description":"Largest strict stage drop, or `null` when no valid denominator exists.","oneOf":[{"$ref":"#/components/schemas/ActivationLargestDrop"},{"type":"null"}]}}},"AnalyticsAvailability":{"type":"string","description":"Whether a metric is complete, partial, or unavailable for the requested bounds.","enum":["AVAILABLE","PARTIAL","UNAVAILABLE"]},"AnalyticsUnavailableReason":{"type":"string","description":"Why a metric or response is partial or unavailable.","enum":["COVERAGE_UNAVAILABLE","PRE_COVERAGE","PARTIAL_COVERAGE","INSUFFICIENT_COVERAGE","EMPTY_DENOMINATOR","QUERY_LIMIT_EXCEEDED","PARTICIPANT_NOT_ELIGIBLE"]},"ActivationCohortBounds":{"type":"object","description":"Exact half-open eligibility cohort and observation maturity dates.","required":["from","to","effectiveFrom","maturedAt","asOf","anchorField"],"properties":{"from":{"type":"integer","format":"int64","description":"Inclusive selected cohort start, as a Unix timestamp in milliseconds."},"to":{"type":"integer","format":"int64","description":"Exclusive selected cohort end, as a Unix timestamp in milliseconds."},"effectiveFrom":{"type":["integer","null"],"format":"int64","description":"Inclusive measured cohort start after coverage is applied, or `null` when unavailable."},"maturedAt":{"type":"integer","format":"int64","description":"Earliest time at which this cohort has its complete observation window."},"asOf":{"type":"integer","format":"int64","description":"Server time used to determine cohort maturity."},"anchorField":{"type":"string","description":"Eligibility milestone used to place participants in the cohort.","enum":["enrolledAsAdvocateAt","approvedAsAffiliateAt"]}}},"ActivationStage":{"type":"object","description":"One ordered strict activation stage for the selected cohort.","required":["key","count","conversionRateFromPrior","conversionRateFromEligible","dropOffCount","dropOffRate","medianTimeToStageMs","stalledSegmentKey"],"properties":{"key":{"type":"string","description":"Stable stage key. Use `portalViewedLabel` to display `PORTAL_VIEWED`.","enum":["ELIGIBLE","PORTAL_VIEWED","SHARE_ACTION","UNIQUE_REFERRAL_VISIT","LEAD","CREDITED_REFERRAL"]},"count":{"type":"integer","minimum":0,"description":"Participants who reached this stage after every prior strict stage."},"conversionRateFromPrior":{"type":["number","null"],"format":"float","description":"Count divided by the prior stage count, or `null` without a denominator."},"conversionRateFromEligible":{"type":["number","null"],"format":"float","description":"Count divided by eligible participants, or `null` without a denominator."},"dropOffCount":{"type":["integer","null"],"minimum":0,"description":"Prior-stage count minus this count, or `null` for `ELIGIBLE`."},"dropOffRate":{"type":["number","null"],"format":"float","description":"`dropOffCount` divided by the prior-stage count, or `null` without a denominator."},"medianTimeToStageMs":{"type":["number","null"],"description":"Median elapsed milliseconds from eligibility to this stage, or `null` when no participant reached it."},"stalledSegmentKey":{"type":["string","null"],"description":"Participant-filter key for the gap before this stage, or `null` for `ELIGIBLE`.","enum":["ELIGIBLE_NO_PORTAL_VIEW","PORTAL_VIEWED_NO_SHARE_ACTION","SHARED_NO_UNIQUE_REFERRAL_VISIT","UNIQUE_VISIT_NO_LEAD","LEAD_NO_CREDITED_REFERRAL",null]}}},"ActivationStageCounts":{"type":"object","description":"Raw outcome counts before strict stage-order enforcement.","required":["ELIGIBLE","PORTAL_VIEWED","SHARE_ACTION","UNIQUE_REFERRAL_VISIT","LEAD","CREDITED_REFERRAL"],"properties":{"ELIGIBLE":{"type":"integer","minimum":0,"description":"Eligible participants in the cohort."},"PORTAL_VIEWED":{"type":"integer","minimum":0,"description":"Eligible participants with an observed signed-in portal view."},"SHARE_ACTION":{"type":"integer","minimum":0,"description":"Eligible participants with an accepted share action."},"UNIQUE_REFERRAL_VISIT":{"type":"integer","minimum":0,"description":"Eligible participants with a unique referral-link visit."},"LEAD":{"type":"integer","minimum":0,"description":"Eligible participants with a referred lead."},"CREDITED_REFERRAL":{"type":"integer","minimum":0,"description":"Eligible participants with a credited referral or affiliate conversion."}}},"ActivationStalledSegment":{"type":"object","description":"Exact participant count in one gap between consecutive strict stages.","required":["key","fromStage","toStage","count"],"properties":{"key":{"type":"string","description":"Stable participant-filter key for this stage gap.","enum":["ELIGIBLE_NO_PORTAL_VIEW","PORTAL_VIEWED_NO_SHARE_ACTION","SHARED_NO_UNIQUE_REFERRAL_VISIT","UNIQUE_VISIT_NO_LEAD","LEAD_NO_CREDITED_REFERRAL"]},"fromStage":{"type":"string","description":"Strict stage before the gap.","enum":["ELIGIBLE","PORTAL_VIEWED","SHARE_ACTION","UNIQUE_REFERRAL_VISIT","LEAD"]},"toStage":{"type":"string","description":"Strict stage after the gap.","enum":["PORTAL_VIEWED","SHARE_ACTION","UNIQUE_REFERRAL_VISIT","LEAD","CREDITED_REFERRAL"]},"count":{"type":"integer","minimum":0,"description":"Participants in this gap."}}},"ActivationOutcomes":{"type":"object","description":"Program-specific outcomes reported outside the strict activation funnel.","properties":{"FIRST_REWARD":{"description":"Referral programs only. Participants who received their first referrer-side reward.","$ref":"#/components/schemas/ActivationOutcomeCount"},"FIRST_COMMISSION":{"description":"Affiliate programs only. Participants who received their first commission.","$ref":"#/components/schemas/ActivationOutcomeCount"},"PAYOUT_SETUP_COMPLETED":{"description":"Participants who completed payout setup.","$ref":"#/components/schemas/ActivationOutcomeCount"}}},"ActivationOutcomeCount":{"type":"object","description":"Count for one downstream activation outcome.","required":["count"],"properties":{"count":{"type":"integer","minimum":0,"description":"Eligible participants with this outcome during their observation window."}}},"ActivationLargestDrop":{"type":"object","description":"Highest percentage loss between consecutive stages with a nonzero prior-stage count.","required":["fromStage","toStage","count","rate","stalledSegmentKey","improvementAreaKey","improvementArea"],"properties":{"fromStage":{"type":"string","description":"Strict stage before the largest drop."},"toStage":{"type":"string","description":"Strict stage after the largest drop."},"count":{"type":"integer","minimum":0,"description":"Participants lost between the two stages."},"rate":{"type":"number","format":"float","description":"Lost participants divided by the prior-stage count."},"stalledSegmentKey":{"type":"string","description":"Participant-filter key for the largest stage gap."},"improvementAreaKey":{"type":"string","description":"Stable key for the participant experience related to this drop.","enum":["PORTAL_ACCESS","SHARING_EXPERIENCE","SHARE_EFFECTIVENESS","VISITOR_SIGNUP","ATTRIBUTION_AND_QUALIFICATION"]},"improvementArea":{"type":"string","description":"Plain-language participant experience related to this drop."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/analytics/activation":{"get":{"tags":["Analytics"],"operationId":"retrieveCampaignActivationAnalytics","summary":"Retrieve campaign activation analytics","description":"Retrieves activation for eligible participants in one exact half-open enrollment cohort. `aggregate` covers the complete selected cohort, while `cohorts` splits that same range by `cohortInterval`. Each participant has the selected `observationWindowDays` after their eligibility date to reach each ordered stage. If `cohortFrom` and `cohortTo` are omitted, GrowSurf selects the latest fully matured rolling cohort. Unavailable and partial states preserve unknown pre-coverage data instead of reporting false zeroes. One request can return at most 100 cohort buckets.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"name":"cohortFrom","in":"query","description":"Inclusive cohort start as a Unix timestamp in milliseconds. Provide it together with `cohortTo`, or omit both for the latest fully matured cohort.","schema":{"type":"integer","format":"int64"}},{"name":"cohortTo","in":"query","description":"Exclusive cohort end as a Unix timestamp in milliseconds. Provide it together with `cohortFrom`, or omit both for the latest fully matured cohort.","schema":{"type":"integer","format":"int64"}},{"name":"cohortInterval","in":"query","description":"Bucket size for the `cohorts` array.","schema":{"type":"string","enum":["day","week","month"],"default":"day"}},{"name":"observationWindowDays","in":"query","description":"Days after each participant's eligibility date in which stages can count.","schema":{"type":"integer","enum":[7,30],"default":30}},{"name":"timezone","in":"query","description":"IANA timezone used to advance cohort bucket boundaries.","schema":{"type":"string","default":"UTC"}}],"responses":{"200":{"description":"Campaign activation analytics returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignActivationAnalyticsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

***

## AFFILIATE PROGRAMS ↓

{% hint style="info" %}
Affiliate application and invite endpoints are available only for affiliate programs. Review and invite write requests use strict request bodies; unknown fields, incorrect types, and incompatible review actions return `400`.
{% endhint %}

## List affiliate applications

> Lists an affiliate program's applications, newest first. Applications exist on programs that review public signups (an \`affiliateApplicationMode\` of \`MANUAL\_REVIEW\` or \`AUTO\_APPROVE\`). A pending applicant is not a participant until their application is approved.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"Offset":{"name":"offset","in":"query","required":false,"description":"Offset number used to skip through a result set.","schema":{"type":"integer","minimum":0,"default":0}}},"schemas":{"AffiliateApplicationListResponse":{"type":"object","required":["applications","total"],"properties":{"applications":{"type":"array","description":"One page of the program's applications, newest first.","items":{"$ref":"#/components/schemas/AffiliateApplication"}},"total":{"type":"integer","description":"Total number of applications matching the filter."},"offset":{"type":"integer","description":"The offset this page started at."},"limit":{"type":"integer","description":"The page size used."}}},"AffiliateApplication":{"type":"object","required":["id","status","email","firstName","lastName","answers","termsAcceptedAt","riskLevel","rejectionReason","reviewedAt","decidedAt","reapplyAllowedAt","participantId","createdAt"],"properties":{"id":{"type":"string","description":"Public application ID."},"status":{"type":"string","enum":["PENDING","APPROVED","DENIED"],"description":"Where the application is in review. Only `PENDING` applications can be decided."},"email":{"type":["string","null"],"description":"Required applicant email address, or `null` after applicant data is removed under the Program's retention policy."},"firstName":{"type":["string","null"],"description":"Required applicant first name, or `null` after applicant data is removed under the Program's retention policy."},"lastName":{"type":["string","null"],"description":"Required applicant last name, or `null` after applicant data is removed under the Program's retention policy."},"answers":{"type":"array","description":"Configurable application responses captured from the saved form. Use `fieldId` as the stable question identifier.","items":{"$ref":"#/components/schemas/AffiliateApplicationAnswer"}},"termsAcceptedAt":{"type":["integer","null"],"format":"int64","description":"When the applicant accepted the Program Terms, in Unix milliseconds, or `null` when acceptance was not required."},"riskLevel":{"type":["string","null"],"enum":["LOW","MEDIUM","HIGH",null],"description":"GrowSurf risk assessment. Applications that are not `LOW` risk are held for manual review; `null` means no assessment was recorded."},"rejectionReason":{"type":["string","null"],"description":"Reason recorded when the application was denied, or `null` before denial."},"reviewedAt":{"type":["integer","null"],"format":"int64","description":"When the application was reviewed, in Unix milliseconds, or `null` while pending."},"decidedAt":{"type":["integer","null"],"format":"int64","description":"When the decision was made, in Unix milliseconds, or `null` while pending."},"reapplyAllowedAt":{"type":["integer","null"],"format":"int64","description":"When a denied applicant may apply again, in Unix milliseconds, or `null` when not applicable."},"participantId":{"type":["string","null"],"description":"Public participant ID created or upgraded by approval, or `null` before approval."},"createdAt":{"type":"integer","format":"int64","description":"When the application was submitted, as a Unix timestamp in milliseconds."}}},"AffiliateApplicationAnswer":{"type":"object","required":["fieldId","label","type","value"],"properties":{"fieldId":{"type":"string","description":"Stable key of the saved application-form field this answer belongs to."},"label":{"type":"string","description":"Customer-configured field label captured when the applicant submitted."},"type":{"type":"string","enum":["text","textarea","url","country","number","dropdown","radio","checkbox"],"description":"Saved field type that determined how the scalar answer was validated."},"value":{"type":["string","number","boolean"],"description":"Applicant answer. Text, URL, country, dropdown, and radio values are strings; number values are numbers; checkbox values are booleans."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/affiliate-applications":{"get":{"tags":["Affiliate Programs"],"operationId":"listAffiliateApplications","summary":"List affiliate applications","description":"Lists an affiliate program's applications, newest first. Applications exist on programs that review public signups (an `affiliateApplicationMode` of `MANUAL_REVIEW` or `AUTO_APPROVE`). A pending applicant is not a participant until their application is approved.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"name":"status","in":"query","required":false,"description":"Only return applications with this status.","schema":{"type":"string","enum":["PENDING","APPROVED","DENIED"]}},{"name":"limit","in":"query","required":false,"description":"How many applications to return per page (1-100).","schema":{"type":"integer","format":"int32","minimum":1,"maximum":100,"default":10}},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Applications returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffiliateApplicationListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Get affiliate application

> Returns one affiliate application, including its submitted form answers.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"AffiliateApplicationId":{"name":"applicationId","in":"path","required":true,"description":"Affiliate application ID.","schema":{"type":"string"}}},"schemas":{"AffiliateApplication":{"type":"object","required":["id","status","email","firstName","lastName","answers","termsAcceptedAt","riskLevel","rejectionReason","reviewedAt","decidedAt","reapplyAllowedAt","participantId","createdAt"],"properties":{"id":{"type":"string","description":"Public application ID."},"status":{"type":"string","enum":["PENDING","APPROVED","DENIED"],"description":"Where the application is in review. Only `PENDING` applications can be decided."},"email":{"type":["string","null"],"description":"Required applicant email address, or `null` after applicant data is removed under the Program's retention policy."},"firstName":{"type":["string","null"],"description":"Required applicant first name, or `null` after applicant data is removed under the Program's retention policy."},"lastName":{"type":["string","null"],"description":"Required applicant last name, or `null` after applicant data is removed under the Program's retention policy."},"answers":{"type":"array","description":"Configurable application responses captured from the saved form. Use `fieldId` as the stable question identifier.","items":{"$ref":"#/components/schemas/AffiliateApplicationAnswer"}},"termsAcceptedAt":{"type":["integer","null"],"format":"int64","description":"When the applicant accepted the Program Terms, in Unix milliseconds, or `null` when acceptance was not required."},"riskLevel":{"type":["string","null"],"enum":["LOW","MEDIUM","HIGH",null],"description":"GrowSurf risk assessment. Applications that are not `LOW` risk are held for manual review; `null` means no assessment was recorded."},"rejectionReason":{"type":["string","null"],"description":"Reason recorded when the application was denied, or `null` before denial."},"reviewedAt":{"type":["integer","null"],"format":"int64","description":"When the application was reviewed, in Unix milliseconds, or `null` while pending."},"decidedAt":{"type":["integer","null"],"format":"int64","description":"When the decision was made, in Unix milliseconds, or `null` while pending."},"reapplyAllowedAt":{"type":["integer","null"],"format":"int64","description":"When a denied applicant may apply again, in Unix milliseconds, or `null` when not applicable."},"participantId":{"type":["string","null"],"description":"Public participant ID created or upgraded by approval, or `null` before approval."},"createdAt":{"type":"integer","format":"int64","description":"When the application was submitted, as a Unix timestamp in milliseconds."}}},"AffiliateApplicationAnswer":{"type":"object","required":["fieldId","label","type","value"],"properties":{"fieldId":{"type":"string","description":"Stable key of the saved application-form field this answer belongs to."},"label":{"type":"string","description":"Customer-configured field label captured when the applicant submitted."},"type":{"type":"string","enum":["text","textarea","url","country","number","dropdown","radio","checkbox"],"description":"Saved field type that determined how the scalar answer was validated."},"value":{"type":["string","number","boolean"],"description":"Applicant answer. Text, URL, country, dropdown, and radio values are strings; number values are numbers; checkbox values are booleans."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/affiliate-applications/{applicationId}":{"get":{"tags":["Affiliate Programs"],"operationId":"getAffiliateApplication","summary":"Get affiliate application","description":"Returns one affiliate application, including its submitted form answers.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/AffiliateApplicationId"}],"responses":{"200":{"description":"Application returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffiliateApplication"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Review affiliate application

> Decides a pending application. Set \`status\` to \`APPROVED\` to enroll the applicant (this creates the participant, or upgrades an existing participant with the same email), or to \`DENIED\` with an optional \`rejectionReason\`. A denied applicant may reapply after the program's reapplication cooldown; send an earlier \`reapplyAllowedAt\` (without \`status\`) to shorten that wait for one applicant. Approval is idempotent: repeating it returns the same participant.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"AffiliateApplicationId":{"name":"applicationId","in":"path","required":true,"description":"Affiliate application ID.","schema":{"type":"string"}}},"schemas":{"ReviewAffiliateApplicationRequest":{"type":"object","additionalProperties":false,"description":"Either decide a pending application with `status`, or move a denied application's reapplication window with `reapplyAllowedAt`. These actions cannot be combined.","oneOf":[{"title":"Review decision","required":["status"],"not":{"required":["reapplyAllowedAt"]}},{"title":"Earlier reapplication override","required":["reapplyAllowedAt"],"not":{"anyOf":[{"required":["status"]},{"required":["rejectionReason"]},{"required":["reviewNote"]},{"required":["allowImmediateReapply"]}]}}],"allOf":[{"if":{"required":["status"],"properties":{"status":{"const":"APPROVED"}}},"then":{"not":{"anyOf":[{"required":["rejectionReason"]},{"required":["reviewNote"]},{"required":["allowImmediateReapply"]}]}}}],"properties":{"status":{"type":"string","enum":["APPROVED","DENIED"],"description":"The decision. `APPROVED` enrolls the applicant as an affiliate; `DENIED` closes the application."},"rejectionReason":{"type":"string","maxLength":255,"description":"Short reason recorded with a denial. Only valid when `status` is `DENIED`."},"reviewNote":{"type":"string","maxLength":500,"description":"Private note recorded with a denial. Never shown to the applicant."},"allowImmediateReapply":{"type":"boolean","description":"When denying, let the applicant reapply right away instead of waiting out the program's reapplication cooldown."},"reapplyAllowedAt":{"type":"integer","format":"int64","description":"For an already-denied application, move the reapplication window to this earlier time, in Unix milliseconds. Send without `status`."}}},"AffiliateApplication":{"type":"object","required":["id","status","email","firstName","lastName","answers","termsAcceptedAt","riskLevel","rejectionReason","reviewedAt","decidedAt","reapplyAllowedAt","participantId","createdAt"],"properties":{"id":{"type":"string","description":"Public application ID."},"status":{"type":"string","enum":["PENDING","APPROVED","DENIED"],"description":"Where the application is in review. Only `PENDING` applications can be decided."},"email":{"type":["string","null"],"description":"Required applicant email address, or `null` after applicant data is removed under the Program's retention policy."},"firstName":{"type":["string","null"],"description":"Required applicant first name, or `null` after applicant data is removed under the Program's retention policy."},"lastName":{"type":["string","null"],"description":"Required applicant last name, or `null` after applicant data is removed under the Program's retention policy."},"answers":{"type":"array","description":"Configurable application responses captured from the saved form. Use `fieldId` as the stable question identifier.","items":{"$ref":"#/components/schemas/AffiliateApplicationAnswer"}},"termsAcceptedAt":{"type":["integer","null"],"format":"int64","description":"When the applicant accepted the Program Terms, in Unix milliseconds, or `null` when acceptance was not required."},"riskLevel":{"type":["string","null"],"enum":["LOW","MEDIUM","HIGH",null],"description":"GrowSurf risk assessment. Applications that are not `LOW` risk are held for manual review; `null` means no assessment was recorded."},"rejectionReason":{"type":["string","null"],"description":"Reason recorded when the application was denied, or `null` before denial."},"reviewedAt":{"type":["integer","null"],"format":"int64","description":"When the application was reviewed, in Unix milliseconds, or `null` while pending."},"decidedAt":{"type":["integer","null"],"format":"int64","description":"When the decision was made, in Unix milliseconds, or `null` while pending."},"reapplyAllowedAt":{"type":["integer","null"],"format":"int64","description":"When a denied applicant may apply again, in Unix milliseconds, or `null` when not applicable."},"participantId":{"type":["string","null"],"description":"Public participant ID created or upgraded by approval, or `null` before approval."},"createdAt":{"type":"integer","format":"int64","description":"When the application was submitted, as a Unix timestamp in milliseconds."}}},"AffiliateApplicationAnswer":{"type":"object","required":["fieldId","label","type","value"],"properties":{"fieldId":{"type":"string","description":"Stable key of the saved application-form field this answer belongs to."},"label":{"type":"string","description":"Customer-configured field label captured when the applicant submitted."},"type":{"type":"string","enum":["text","textarea","url","country","number","dropdown","radio","checkbox"],"description":"Saved field type that determined how the scalar answer was validated."},"value":{"type":["string","number","boolean"],"description":"Applicant answer. Text, URL, country, dropdown, and radio values are strings; number values are numbers; checkbox values are booleans."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/affiliate-applications/{applicationId}":{"patch":{"tags":["Affiliate Programs"],"operationId":"reviewAffiliateApplication","summary":"Review affiliate application","description":"Decides a pending application. Set `status` to `APPROVED` to enroll the applicant (this creates the participant, or upgrades an existing participant with the same email), or to `DENIED` with an optional `rejectionReason`. A denied applicant may reapply after the program's reapplication cooldown; send an earlier `reapplyAllowedAt` (without `status`) to shorten that wait for one applicant. Approval is idempotent: repeating it returns the same participant.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/AffiliateApplicationId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewAffiliateApplicationRequest"}}}},"responses":{"200":{"description":"Updated application returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffiliateApplication"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The application was already decided (approve/deny can only run once), or `reapplyAllowedAt` was sent for an application that is not denied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List affiliate invites

> Lists an affiliate program's enrollment invites, newest first.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"Offset":{"name":"offset","in":"query","required":false,"description":"Offset number used to skip through a result set.","schema":{"type":"integer","minimum":0,"default":0}}},"schemas":{"AffiliateInviteListResponse":{"type":"object","required":["invites","total"],"properties":{"invites":{"type":"array","description":"One page of the program's invites, newest first.","items":{"$ref":"#/components/schemas/AffiliateInvite"}},"total":{"type":"integer","description":"Total number of invites matching the filter."},"offset":{"type":"integer","description":"The offset this page started at."},"limit":{"type":"integer","description":"The page size used."}}},"AffiliateInvite":{"type":"object","properties":{"id":{"type":"string","description":"Invite ID."},"status":{"type":"string","enum":["PENDING","ACCEPTED","EXPIRED","REVOKED"],"description":"The invite's lifecycle state. Accepting a pending invite enrolls the invitee as an approved affiliate."},"email":{"type":"string","description":"Invitee email address."},"firstName":{"type":["string","null"],"description":"Invitee first name, when provided."},"lastName":{"type":["string","null"],"description":"Invitee last name, when provided."},"expiresAt":{"type":"integer","format":"int64","description":"When the emailed accept link stops working, in Unix milliseconds."},"lastSentAt":{"type":"integer","format":"int64","description":"When the invite email was last sent, in Unix milliseconds."},"acceptedAt":{"type":["integer","null"],"format":"int64","description":"When the invite was accepted, in Unix milliseconds. `null` until accepted."},"revokedAt":{"type":["integer","null"],"format":"int64","description":"When the invite was revoked, in Unix milliseconds. `null` unless revoked."},"createdAt":{"type":"integer","format":"int64","description":"When the invite was created, in Unix milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/affiliate-invites":{"get":{"tags":["Affiliate Programs"],"operationId":"listAffiliateInvites","summary":"List affiliate invites","description":"Lists an affiliate program's enrollment invites, newest first.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"name":"status","in":"query","required":false,"description":"Only return invites with this status.","schema":{"type":"string","enum":["PENDING","ACCEPTED","EXPIRED","REVOKED"]}},{"name":"limit","in":"query","required":false,"description":"How many invites to return per page (1-100).","schema":{"type":"integer","format":"int32","minimum":1,"maximum":100,"default":10}},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Invites returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffiliateInviteListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Create affiliate invite

> Invites someone to join the affiliate program. GrowSurf emails them a single-use accept link; accepting it enrolls them as an approved affiliate without going through the public application. One active invite can exist per email address.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}}},"schemas":{"CreateAffiliateInviteRequest":{"type":"object","required":["email"],"additionalProperties":false,"properties":{"email":{"type":"string","format":"email","maxLength":255,"description":"Email address to invite."},"firstName":{"type":"string","maxLength":255,"description":"Invitee first name, used in the invite email."},"lastName":{"type":"string","maxLength":255,"description":"Invitee last name."}}},"AffiliateInvite":{"type":"object","properties":{"id":{"type":"string","description":"Invite ID."},"status":{"type":"string","enum":["PENDING","ACCEPTED","EXPIRED","REVOKED"],"description":"The invite's lifecycle state. Accepting a pending invite enrolls the invitee as an approved affiliate."},"email":{"type":"string","description":"Invitee email address."},"firstName":{"type":["string","null"],"description":"Invitee first name, when provided."},"lastName":{"type":["string","null"],"description":"Invitee last name, when provided."},"expiresAt":{"type":"integer","format":"int64","description":"When the emailed accept link stops working, in Unix milliseconds."},"lastSentAt":{"type":"integer","format":"int64","description":"When the invite email was last sent, in Unix milliseconds."},"acceptedAt":{"type":["integer","null"],"format":"int64","description":"When the invite was accepted, in Unix milliseconds. `null` until accepted."},"revokedAt":{"type":["integer","null"],"format":"int64","description":"When the invite was revoked, in Unix milliseconds. `null` unless revoked."},"createdAt":{"type":"integer","format":"int64","description":"When the invite was created, in Unix milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/affiliate-invites":{"post":{"tags":["Affiliate Programs"],"operationId":"createAffiliateInvite","summary":"Create affiliate invite","description":"Invites someone to join the affiliate program. GrowSurf emails them a single-use accept link; accepting it enrolls them as an approved affiliate without going through the public application. One active invite can exist per email address.","parameters":[{"$ref":"#/components/parameters/CampaignId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAffiliateInviteRequest"}}}},"responses":{"200":{"description":"Invite created and emailed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffiliateInvite"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"An active invite already exists for this email, or the email already belongs to an affiliate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Revoke affiliate invite

> Revokes a pending invite. Its emailed accept link stops working immediately.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"AffiliateInviteId":{"name":"inviteId","in":"path","required":true,"description":"Affiliate invite ID.","schema":{"type":"string"}}},"schemas":{"AffiliateInvite":{"type":"object","properties":{"id":{"type":"string","description":"Invite ID."},"status":{"type":"string","enum":["PENDING","ACCEPTED","EXPIRED","REVOKED"],"description":"The invite's lifecycle state. Accepting a pending invite enrolls the invitee as an approved affiliate."},"email":{"type":"string","description":"Invitee email address."},"firstName":{"type":["string","null"],"description":"Invitee first name, when provided."},"lastName":{"type":["string","null"],"description":"Invitee last name, when provided."},"expiresAt":{"type":"integer","format":"int64","description":"When the emailed accept link stops working, in Unix milliseconds."},"lastSentAt":{"type":"integer","format":"int64","description":"When the invite email was last sent, in Unix milliseconds."},"acceptedAt":{"type":["integer","null"],"format":"int64","description":"When the invite was accepted, in Unix milliseconds. `null` until accepted."},"revokedAt":{"type":["integer","null"],"format":"int64","description":"When the invite was revoked, in Unix milliseconds. `null` unless revoked."},"createdAt":{"type":"integer","format":"int64","description":"When the invite was created, in Unix milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/affiliate-invites/{inviteId}":{"delete":{"tags":["Affiliate Programs"],"operationId":"revokeAffiliateInvite","summary":"Revoke affiliate invite","description":"Revokes a pending invite. Its emailed accept link stops working immediately.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/AffiliateInviteId"}],"responses":{"200":{"description":"Revoked invite returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffiliateInvite"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Only a pending invite can be revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Resend affiliate invite

> Re-sends a pending invite with a fresh accept link (the previous link stops working). Resends are rate limited per invite; retry after a few minutes if a resend was just sent.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"AffiliateInviteId":{"name":"inviteId","in":"path","required":true,"description":"Affiliate invite ID.","schema":{"type":"string"}}},"schemas":{"AffiliateInvite":{"type":"object","properties":{"id":{"type":"string","description":"Invite ID."},"status":{"type":"string","enum":["PENDING","ACCEPTED","EXPIRED","REVOKED"],"description":"The invite's lifecycle state. Accepting a pending invite enrolls the invitee as an approved affiliate."},"email":{"type":"string","description":"Invitee email address."},"firstName":{"type":["string","null"],"description":"Invitee first name, when provided."},"lastName":{"type":["string","null"],"description":"Invitee last name, when provided."},"expiresAt":{"type":"integer","format":"int64","description":"When the emailed accept link stops working, in Unix milliseconds."},"lastSentAt":{"type":"integer","format":"int64","description":"When the invite email was last sent, in Unix milliseconds."},"acceptedAt":{"type":["integer","null"],"format":"int64","description":"When the invite was accepted, in Unix milliseconds. `null` until accepted."},"revokedAt":{"type":["integer","null"],"format":"int64","description":"When the invite was revoked, in Unix milliseconds. `null` unless revoked."},"createdAt":{"type":"integer","format":"int64","description":"When the invite was created, in Unix milliseconds."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"The account's billing requirements for this action are not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/affiliate-invites/{inviteId}/resend":{"post":{"tags":["Affiliate Programs"],"operationId":"resendAffiliateInvite","summary":"Resend affiliate invite","description":"Re-sends a pending invite with a fresh accept link (the previous link stops working). Resends are rate limited per invite; retry after a few minutes if a resend was just sent.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/AffiliateInviteId"}],"responses":{"200":{"description":"Updated invite returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffiliateInvite"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The invite is not pending, or its resend cooldown has not elapsed yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Record affiliate transaction

> \*\*Affiliate programs only.\*\* Records a sale made by a referred customer and generates affiliate commissions for their referrer when applicable. Requires at least one transaction identifier (externalId, transactionId, orderId, paymentId, invoiceId, paymentIntentId, or chargeId) so repeated requests can be de-duplicated — without one, a resent sale would create a second commission. Reuse the same identifier(s) when refunding.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"RecordTransactionRequest":{"type":"object","description":"To record a Stripe, Chargebee or Recurly payment, supply `paymentProvider`, `testMode`, and the provider's payment `transactionId`. For Stripe, use a Charge ID (`ch_...`) or PaymentIntent ID (`pi_...`); both identify the same captured payment. The integration must be enabled in that mode. `grossAmount`, `currency`, and any supplied `paidAt` must match the provider payment. Other payment identifiers and tax or net-amount overrides are not accepted for provider payments. GrowSurf reads those payment details from the connected account. The provider customer must match the participant's email or stored provider identity. Webhook, API, and manual submissions for the same provider payment are treated as duplicates. Omit `paymentProvider` and `testMode` to record a sale using your own transaction details.","required":["currency","grossAmount"],"properties":{"paymentProvider":{"description":"Connected payment provider. Requires `transactionId` and an explicit `testMode`.","type":"string","enum":["stripe","chargebee","recurly"]},"testMode":{"description":"`true` for the provider's test account or `false` for its live account. Required when `paymentProvider` is supplied; otherwise omit this field.","type":"boolean"},"currency":{"description":"3-letter ISO 4217 currency code for the sale. Must match the program currency.","type":"string","minLength":3,"maxLength":3,"pattern":"^[A-Za-z]{3}$"},"grossAmount":{"description":"Total sale amount in the currency's minor unit. Must be a positive integer.","type":"integer","minimum":1},"netAmount":{"description":"Net-of-tax sale amount in the currency's minor unit. Used as the commissionable base when supplied.","type":"integer","minimum":0},"taxAmount":{"description":"Tax collected in the currency's minor unit. Used to derive the commissionable base when `netAmount` is absent.","type":"integer","minimum":0},"amountCashNet":{"description":"Explicit post-tax cash amount in the currency's minor unit. Overrides derived net amounts.","type":"integer","minimum":0},"amountPaid":{"description":"Amount actually paid in the currency's minor unit, when reported by your payment processor.","type":"integer","minimum":0},"paidAt":{"description":"When the payment was captured or settled, as a Unix timestamp in milliseconds.","type":"integer","format":"int64"},"externalId":{"description":"Primary transaction identifier from your billing system. Used to detect duplicate submissions.","type":"string","maxLength":500},"transactionId":{"description":"Transaction identifier to store for this sale. Used to detect duplicate submissions.","type":"string","maxLength":500},"customerId":{"description":"Customer record identifier, such as a payment-provider customer ID.","type":"string","maxLength":500},"orderId":{"description":"Order identifier. Used to detect duplicate submissions when supplied.","type":"string","maxLength":500},"paymentId":{"description":"Payment identifier. Used to detect duplicate submissions when supplied.","type":"string","maxLength":500},"invoiceId":{"description":"Invoice identifier. Used to detect duplicate submissions when supplied.","type":"string","maxLength":500},"subscriptionId":{"description":"Subscription identifier for recurring payments.","type":"string","maxLength":500},"paymentIntentId":{"description":"Payment intent identifier. Used to detect duplicate submissions when supplied.","type":"string","maxLength":500},"chargeId":{"description":"Charge identifier. Used to detect duplicate submissions when supplied.","type":"string","maxLength":500},"description":{"description":"Freeform sale description shown in dashboard context and reports.","type":"string","maxLength":500},"invoiceTotal":{"description":"Invoice total including tax, in the currency's minor unit.","type":"integer","minimum":0},"invoiceTotalExcludingTax":{"description":"Invoice total excluding tax, in the currency's minor unit.","type":"integer","minimum":0},"invoiceSubtotalExcludingTax":{"description":"Invoice subtotal excluding tax, in the currency's minor unit.","type":"integer","minimum":0},"totalTaxAmount":{"description":"Aggregate tax amount in the currency's minor unit.","type":"integer","minimum":0},"totalTaxAmounts":{"description":"Detailed tax breakdown objects from your payment processor.","type":"array","items":{"type":"object","additionalProperties":true}},"totalTaxes":{"description":"Alternate detailed tax breakdown objects from your payment processor.","type":"array","items":{"type":"object","additionalProperties":true}}}},"RecordTransactionResponse":{"oneOf":[{"type":"object","required":["success","firstSale","duplicate","message"],"properties":{"success":{"description":"Whether the sale was recorded.","type":"boolean","const":true},"firstSale":{"description":"Whether this was the referred customer's first recorded sale.","type":"boolean"},"duplicate":{"description":"Always `false` when the sale is newly recorded.","type":"boolean","const":false},"message":{"description":"Human-readable result message for the sale.","type":"string"}}},{"type":"object","required":["success","duplicate","commissionsCreated","duplicateFields","matchingCommissionIds","message"],"properties":{"success":{"description":"Always `false` when the sale matched an existing transaction.","type":"boolean","const":false},"duplicate":{"description":"Always `true` when the sale matched an existing transaction.","type":"boolean","const":true},"commissionsCreated":{"description":"Number of commissions created by this duplicate request.","type":"integer"},"duplicateFields":{"description":"Identifier fields that matched an existing transaction.","type":"array","items":{"type":"string"}},"matchingCommissionIds":{"description":"Commission IDs that matched the submitted identifiers.","type":"array","items":{"type":"string"}},"message":{"description":"Human-readable result message for the duplicate sale.","type":"string"}}}]},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/transaction":{"post":{"tags":["Affiliate Programs"],"operationId":"recordTransaction","summary":"Record affiliate transaction","description":"**Affiliate programs only.** Records a sale made by a referred customer and generates affiliate commissions for their referrer when applicable. Requires at least one transaction identifier (externalId, transactionId, orderId, paymentId, invoiceId, paymentIntentId, or chargeId) so repeated requests can be de-duplicated — without one, a resent sale would create a second commission. Reuse the same identifier(s) when refunding.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordTransactionRequest"}}}},"responses":{"200":{"description":"Transaction result returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordTransactionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Refund or amend affiliate transaction

> \*\*Affiliate programs only.\*\* Records an amendment (refund, partial refund, refund cancellation, or chargeback) against a previously recorded transaction and reverses or adjusts the referrer's commission. The inverse of Record Affiliate Transaction. Identify the original transaction with the same identifier(s) you sent when recording it. Commissions already paid out to the affiliate are not clawed back. The amendment still updates the sale revenue used in program reporting; full refunds and chargebacks also update tax reporting.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}}},"schemas":{"RefundTransactionRequest":{"type":"object","description":"To amend a Stripe, Chargebee or Recurly payment, supply `paymentProvider`, `testMode`, and the original provider payment's `transactionId` (a Charge or PaymentIntent ID for Stripe). These values must identify the same connected account and mode used for the sale. This records an amendment in GrowSurf; it does not send a refund through the payment provider. Omit `paymentProvider` and `testMode` for other transactions recorded through the API or dashboard. At least one transaction identifier is required. Use the same identifier you supplied when recording the sale.","properties":{"paymentProvider":{"description":"Connected payment provider for the original sale. Requires `transactionId` and `testMode`.","type":"string","enum":["stripe","chargebee","recurly"]},"testMode":{"description":"`true` for the original payment's test account or `false` for its live account. Required when `paymentProvider` is supplied; otherwise omit this field.","type":"boolean"},"amendmentType":{"type":"string","enum":["REFUND","CHARGEBACK"],"default":"REFUND","description":"REFUND covers full refunds, partial refunds, and refund cancellations; CHARGEBACK is always a full reversal."},"amountRefunded":{"type":"integer","minimum":0,"description":"Cumulative amount refunded so far, in the currency's minor unit. Omit for a full refund. For a partial refund send the running total, not the per-refund delta."},"amount":{"type":"integer","minimum":1,"description":"Original sale gross (minor units). Optional — the value stored when the transaction was recorded is used when available; only needed for partial refunds of older records."},"refundId":{"type":"string","maxLength":500,"description":"Stable per-refund identifier. Required when canceling a refund or changing the refunded total after a cancellation. Reuse the original refund's identifier for its cancellation. An amendment without enough refund identity returns `409` without applying the cancellation. Newly observed higher cumulative refunds and incomplete coverage are retained for reconciliation."},"refundStatus":{"type":"string","maxLength":500,"description":"Refund status. Send `canceled` with the original `refundId` to cancel that refund. Record each original refund with `refundId` and `refundAmount` first. Missing or conflicting refund history returns `409` without applying the cancellation. Newly observed higher cumulative refunds and incomplete coverage are retained for reconciliation."},"refundAmount":{"type":"integer","minimum":0,"description":"Positive amount for this individual refund, no greater than the sale amount, in the sale currency's minor unit. Send it with `refundId` on each original refund to support cancellations and out-of-order amendments. The amount for a given `refundId` cannot change. A cancellation can omit it when the original amount is already recorded. Incomplete refund history returns `409` without applying the cancellation. Newly observed higher cumulative refunds and incomplete coverage are retained for reconciliation."},"refundHistoryComplete":{"type":"boolean","description":"Set `true` only after reconciling and recording every original refund's `refundId` and `refundAmount`, including refunds later canceled. This confirmation resolves previously incomplete refund history. Omit it during ordinary delivery. Cancellations remain `409` while history is incomplete. Replaying an old confirmation cannot reconcile a later gap; confirm a newly reconciled refund or complete provider list."},"currency":{"type":"string","minLength":3,"maxLength":3,"pattern":"^[A-Za-z]{3}$","description":"3-letter ISO currency. Optional — resolved from the original commission when available."},"externalId":{"description":"Original transaction identifier from your billing system.","type":"string","maxLength":500},"transactionId":{"description":"Original transaction identifier to match against.","type":"string","maxLength":500},"orderId":{"description":"Original order identifier to match against.","type":"string","maxLength":500},"paymentId":{"description":"Original payment identifier to match against.","type":"string","maxLength":500},"invoiceId":{"description":"Original invoice identifier to match against.","type":"string","maxLength":500},"paymentIntentId":{"description":"Original payment intent identifier to match against.","type":"string","maxLength":500},"chargeId":{"description":"Original charge identifier to match against.","type":"string","maxLength":500},"description":{"description":"Freeform amendment description shown in dashboard context and reports.","type":"string","maxLength":500}}},"RefundTransactionResponse":{"type":"object","required":["success","amendmentType","matched","reversed","adjusted","deleted","matchingCommissionIds","message"],"properties":{"success":{"type":"boolean","description":"true when the amendment was processed (including the tax-only case for already-paid commissions); false when no matching transaction was found."},"notFound":{"type":"boolean","description":"Present and true when no commission matched the provided identifiers."},"amendmentType":{"description":"Amendment type that was processed.","type":"string","enum":["REFUND","CHARGEBACK"]},"matched":{"type":"integer","description":"Number of commissions found for the provided identifiers."},"reversed":{"type":"integer","description":"Number of commissions reversed (set to zero amount)."},"adjusted":{"type":"integer","description":"Number of commissions partially adjusted."},"deleted":{"description":"Number of pending commissions deleted by the amendment.","type":"integer"},"matchingCommissionIds":{"description":"Commission IDs that matched the submitted identifiers.","type":"array","items":{"type":"string"}},"message":{"description":"Human-readable result message for the amendment.","type":"string"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/transaction/refund":{"post":{"tags":["Affiliate Programs"],"operationId":"refundTransaction","summary":"Refund or amend affiliate transaction","description":"**Affiliate programs only.** Records an amendment (refund, partial refund, refund cancellation, or chargeback) against a previously recorded transaction and reverses or adjusts the referrer's commission. The inverse of Record Affiliate Transaction. Identify the original transaction with the same identifier(s) you sent when recording it. Commissions already paid out to the affiliate are not clawed back. The amendment still updates the sale revenue used in program reporting; full refunds and chargebacks also update tax reporting.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundTransactionRequest"}}}},"responses":{"200":{"description":"Amendment result returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundTransactionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The refund amendment has missing or conflicting per-refund history. Record each original refund with its stable `refundId` and positive `refundAmount`. After reconciling all original refunds, set `refundHistoryComplete` to `true` to resolve incomplete history. The cancellation is not applied on conflict. Newly observed higher cumulative refunds and incomplete coverage are retained for reconciliation."},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List participant commissions

> \*\*Affiliate programs only.\*\* Retrieves a paged list of all participant commissions in an affiliate program.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"NextId":{"name":"nextId","in":"query","required":false,"description":"ID to start the next paged result set with.","schema":{"type":"string"}},"Limit100":{"name":"limit","in":"query","required":false,"description":"Number of results to return. Maximum 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}},"CommissionStatus":{"name":"status","in":"query","required":false,"description":"Participant commission status.","schema":{"$ref":"#/components/schemas/CommissionStatus"}}},"schemas":{"CommissionStatus":{"type":"string","enum":["PENDING","APPROVED","PAID","REVERSED","DELETED"]},"ParticipantCommissionListResponse":{"type":"object","required":["commissions","limit","nextId"],"properties":{"commissions":{"description":"Participant commissions returned for this page.","type":"array","items":{"$ref":"#/components/schemas/ParticipantCommission"}},"limit":{"description":"Maximum number of commissions requested for this page.","type":"integer"},"nextId":{"description":"Commission ID to pass as `nextId` for the next page, or `null` when there are no more results.","type":["string","null"]}}},"ParticipantCommission":{"type":"object","description":"**Affiliate programs only.** A commission generated for a (referrer) participant in an affiliate program.","required":["id","referrerId","referredId","event","amount","saleAmount","currencyISO","status","createdAt"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant commission."},"referrerId":{"type":"string","readOnly":true,"description":"The participant ID of the referrer who earned the commission."},"referredId":{"type":"string","readOnly":true,"description":"The participant ID of the referred friend whose `LEAD` or `SALE` event generated the commission."},"event":{"$ref":"#/components/schemas/CommissionEvent","readOnly":true,"description":"The event that generated this commission. Legacy commissions return `SALE`."},"amount":{"type":["integer","null"],"readOnly":true,"description":"Commission amount (what the affiliate earned) in the currency's smallest denomination (e.g., `100` cents equals $1.00 USD, and `100` equals ¥100 for a zero-decimal currency). Always non-negative when present; `null` if it could not be computed."},"saleAmount":{"type":["integer","null"],"readOnly":true,"description":"The sale amount from the transaction the referral paid, in the currency's smallest denomination. Always non-negative when present; `null` for LEAD commissions and sale providers that do not report an amount."},"currencyISO":{"type":"string","readOnly":true,"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code of the commission (e.g., `USD`, `GBP`)."},"status":{"$ref":"#/components/schemas/CommissionStatus","readOnly":true,"description":"Current lifecycle state of the commission."},"approvedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission was approved. `null` until the commission transitions to `APPROVED`."},"paidAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission was sent in a payout. `null` until the commission transitions to `PAID`."},"reversedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission was reversed (for example, due to a refund or chargeback). `null` unless the commission transitions to `REVERSED`."},"holdDuration":{"type":["integer","null"],"readOnly":true,"description":"Number of days the commission must age before it can be processed for payout. Defaults to `0` (no hold)."},"payoutQueuedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission was queued for payout. `null` if it has not been scheduled."},"provider":{"type":["string","null"],"readOnly":true,"description":"Origin of the commission event. For example, `stripe` or `api`. `null` for `LEAD` commissions because no sale provider generated them."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission record was created."},"amountInCampaignCurrency":{"type":["integer","null"],"readOnly":true,"description":"Commission amount converted to the program's currency, in the currency's smallest denomination. Always non-negative. `null` if the commission is still pending FX conversion."},"saleAmountInCampaignCurrency":{"type":["integer","null"],"readOnly":true,"description":"The sale amount converted to the program's currency, in the currency's smallest denomination. Always non-negative. `null` for `LEAD` commissions or while FX conversion is pending."},"exchangeRate":{"type":["number","null"],"readOnly":true,"description":"The currency exchange rate used for conversion. `null` if the commission is still pending FX conversion."},"campaignCurrencyISO":{"type":["string","null"],"readOnly":true,"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code of the program used in currency conversion (e.g., `USD`, `GBP`). `null` if the commission is still pending FX conversion."},"exchangeRateAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the currency exchange rate was received. `null` if the commission is still pending FX conversion."},"fxError":{"type":["string","null"],"readOnly":true,"description":"If there was an error with FX conversion, the specific details. `null` if conversion was successful."}}},"CommissionEvent":{"type":"string","enum":["LEAD","SALE"],"description":"The affiliate event that generated the commission."},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/commissions":{"get":{"tags":["Affiliate Programs"],"operationId":"listCommissions","summary":"List participant commissions","description":"**Affiliate programs only.** Retrieves a paged list of all participant commissions in an affiliate program.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/NextId"},{"$ref":"#/components/parameters/Limit100"},{"$ref":"#/components/parameters/CommissionStatus"}],"responses":{"200":{"description":"Participant commissions returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantCommissionListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List commissions for a participant

> \*\*Affiliate programs only.\*\* Retrieves a paged list of commissions earned by a participant.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}},"NextId":{"name":"nextId","in":"query","required":false,"description":"ID to start the next paged result set with.","schema":{"type":"string"}},"Limit100":{"name":"limit","in":"query","required":false,"description":"Number of results to return. Maximum 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}},"CommissionStatus":{"name":"status","in":"query","required":false,"description":"Participant commission status.","schema":{"$ref":"#/components/schemas/CommissionStatus"}}},"schemas":{"CommissionStatus":{"type":"string","enum":["PENDING","APPROVED","PAID","REVERSED","DELETED"]},"ParticipantCommissionListResponse":{"type":"object","required":["commissions","limit","nextId"],"properties":{"commissions":{"description":"Participant commissions returned for this page.","type":"array","items":{"$ref":"#/components/schemas/ParticipantCommission"}},"limit":{"description":"Maximum number of commissions requested for this page.","type":"integer"},"nextId":{"description":"Commission ID to pass as `nextId` for the next page, or `null` when there are no more results.","type":["string","null"]}}},"ParticipantCommission":{"type":"object","description":"**Affiliate programs only.** A commission generated for a (referrer) participant in an affiliate program.","required":["id","referrerId","referredId","event","amount","saleAmount","currencyISO","status","createdAt"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant commission."},"referrerId":{"type":"string","readOnly":true,"description":"The participant ID of the referrer who earned the commission."},"referredId":{"type":"string","readOnly":true,"description":"The participant ID of the referred friend whose `LEAD` or `SALE` event generated the commission."},"event":{"$ref":"#/components/schemas/CommissionEvent","readOnly":true,"description":"The event that generated this commission. Legacy commissions return `SALE`."},"amount":{"type":["integer","null"],"readOnly":true,"description":"Commission amount (what the affiliate earned) in the currency's smallest denomination (e.g., `100` cents equals $1.00 USD, and `100` equals ¥100 for a zero-decimal currency). Always non-negative when present; `null` if it could not be computed."},"saleAmount":{"type":["integer","null"],"readOnly":true,"description":"The sale amount from the transaction the referral paid, in the currency's smallest denomination. Always non-negative when present; `null` for LEAD commissions and sale providers that do not report an amount."},"currencyISO":{"type":"string","readOnly":true,"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code of the commission (e.g., `USD`, `GBP`)."},"status":{"$ref":"#/components/schemas/CommissionStatus","readOnly":true,"description":"Current lifecycle state of the commission."},"approvedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission was approved. `null` until the commission transitions to `APPROVED`."},"paidAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission was sent in a payout. `null` until the commission transitions to `PAID`."},"reversedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission was reversed (for example, due to a refund or chargeback). `null` unless the commission transitions to `REVERSED`."},"holdDuration":{"type":["integer","null"],"readOnly":true,"description":"Number of days the commission must age before it can be processed for payout. Defaults to `0` (no hold)."},"payoutQueuedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission was queued for payout. `null` if it has not been scheduled."},"provider":{"type":["string","null"],"readOnly":true,"description":"Origin of the commission event. For example, `stripe` or `api`. `null` for `LEAD` commissions because no sale provider generated them."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the commission record was created."},"amountInCampaignCurrency":{"type":["integer","null"],"readOnly":true,"description":"Commission amount converted to the program's currency, in the currency's smallest denomination. Always non-negative. `null` if the commission is still pending FX conversion."},"saleAmountInCampaignCurrency":{"type":["integer","null"],"readOnly":true,"description":"The sale amount converted to the program's currency, in the currency's smallest denomination. Always non-negative. `null` for `LEAD` commissions or while FX conversion is pending."},"exchangeRate":{"type":["number","null"],"readOnly":true,"description":"The currency exchange rate used for conversion. `null` if the commission is still pending FX conversion."},"campaignCurrencyISO":{"type":["string","null"],"readOnly":true,"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code of the program used in currency conversion (e.g., `USD`, `GBP`). `null` if the commission is still pending FX conversion."},"exchangeRateAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the currency exchange rate was received. `null` if the commission is still pending FX conversion."},"fxError":{"type":["string","null"],"readOnly":true,"description":"If there was an error with FX conversion, the specific details. `null` if conversion was successful."}}},"CommissionEvent":{"type":"string","enum":["LEAD","SALE"],"description":"The affiliate event that generated the commission."},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/commissions":{"get":{"tags":["Affiliate Programs"],"operationId":"listParticipantCommissions","summary":"List commissions for a participant","description":"**Affiliate programs only.** Retrieves a paged list of commissions earned by a participant.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"},{"$ref":"#/components/parameters/NextId"},{"$ref":"#/components/parameters/Limit100"},{"$ref":"#/components/parameters/CommissionStatus"}],"responses":{"200":{"description":"Participant commissions returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantCommissionListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Approve participant commission

> \*\*Affiliate programs only.\*\* Approves a pending participant commission so it can become eligible for payout.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"CommissionId":{"name":"commissionId","in":"path","required":true,"description":"Participant commission ID.","schema":{"type":"string"}}},"responses":{"Success":{"description":"Success response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InvalidState":{"description":"Resource is not in a state that permits this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"SuccessResponse":{"type":"object","required":["success"],"properties":{"success":{"description":"Whether the request succeeded.","type":"boolean"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}}},"paths":{"/campaign/{id}/commission/{commissionId}/approve":{"post":{"tags":["Affiliate Programs"],"operationId":"approveCommission","summary":"Approve participant commission","description":"**Affiliate programs only.** Approves a pending participant commission so it can become eligible for payout.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/CommissionId"}],"responses":{"200":{"$ref":"#/components/responses/Success"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"406":{"$ref":"#/components/responses/InvalidState"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Delete participant commission

> \*\*Affiliate programs only.\*\* Removes a pending participant commission.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"CommissionId":{"name":"commissionId","in":"path","required":true,"description":"Participant commission ID.","schema":{"type":"string"}}},"responses":{"Success":{"description":"Success response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InvalidState":{"description":"Resource is not in a state that permits this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"SuccessResponse":{"type":"object","required":["success"],"properties":{"success":{"description":"Whether the request succeeded.","type":"boolean"}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}}},"paths":{"/campaign/{id}/commission/{commissionId}":{"delete":{"tags":["Affiliate Programs"],"operationId":"deleteCommission","summary":"Delete participant commission","description":"**Affiliate programs only.** Removes a pending participant commission.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/CommissionId"}],"responses":{"200":{"$ref":"#/components/responses/Success"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"406":{"$ref":"#/components/responses/InvalidState"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List participant payouts

> \*\*Affiliate programs only.\*\* Retrieves a paged list of all participant payouts in an affiliate program.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"NextId":{"name":"nextId","in":"query","required":false,"description":"ID to start the next paged result set with.","schema":{"type":"string"}},"Limit100":{"name":"limit","in":"query","required":false,"description":"Number of results to return. Maximum 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}},"PayoutStatus":{"name":"status","in":"query","required":false,"description":"Participant payout status.","schema":{"$ref":"#/components/schemas/PayoutStatus"}}},"schemas":{"PayoutStatus":{"type":"string","enum":["UPCOMING","QUEUED","ISSUED","FAILED","REVERSED"]},"ParticipantPayoutListResponse":{"type":"object","required":["payouts","limit","nextId"],"properties":{"payouts":{"description":"Participant payouts returned for this page.","type":"array","items":{"$ref":"#/components/schemas/ParticipantPayout"}},"limit":{"description":"Maximum number of payouts requested for this page.","type":"integer"},"nextId":{"description":"Payout ID to pass as `nextId` for the next page, or `null` when there are no more results.","type":["string","null"]}}},"ParticipantPayout":{"type":"object","description":"**Affiliate programs only.** A payout generated for a participant in an affiliate program. All payouts start with a status of `UPCOMING`, which represents an aggregation of the participant's `ParticipantCommission` amounts, and is adjusted in real time as commissions change.","required":["id","participantId","commissionIds","amount","currencyISO","status","createdAt"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant payout."},"participantId":{"type":"string","readOnly":true,"description":"The participant ID of the referrer who earned the payout."},"commissionIds":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"The IDs of the associated `ParticipantCommission`s."},"amount":{"type":"integer","readOnly":true,"description":"Payout amount in the currency's smallest denomination (e.g., `100` cents equals $1.00 USD, and `100` equals ¥100 for a zero-decimal currency). Always non-negative."},"currencyISO":{"type":"string","readOnly":true,"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code of the payout (e.g., `USD`, `GBP`)."},"status":{"$ref":"#/components/schemas/PayoutStatus","readOnly":true,"description":"Current lifecycle state of the payout."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the payout record was created."},"queuedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the payout was queued for processing (status `QUEUED`). `null` until the payout is queued."},"issuedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the payout was issued. `null` if the payout was not successfully issued."},"failedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the payout failed to be issued. `null` if the payout did not fail to be issued."},"reversedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when an issued payout was returned or cancelled and transitioned to `REVERSED`. `null` unless the payout is reversed."},"provider":{"type":["string","null"],"readOnly":true,"description":"The payment provider used to issue the payout (e.g., `paypal`). `null` for payouts with a status of `UPCOMING`."},"amountInCampaignCurrency":{"type":["integer","null"],"readOnly":true,"description":"Payout amount converted to the program's currency, in the currency's smallest denomination. Always non-negative. `null` if the payout status is `UPCOMING` or if it is still pending FX conversion."},"campaignCurrencyISO":{"type":["string","null"],"readOnly":true,"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code of the program used in currency conversion (e.g., `USD`, `GBP`). `null` if the payout status is `UPCOMING` or if it is still pending FX conversion."},"exchangeRateAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the currency exchange rate was received. `null` if the payout status is `UPCOMING` or if it is still pending FX conversion."},"exchangeRate":{"type":["number","null"],"readOnly":true,"description":"The currency exchange rate used for conversion. `null` if the payout status is `UPCOMING` or if it is still pending FX conversion."},"fxError":{"type":["string","null"],"readOnly":true,"description":"If there was an error with FX conversion, the specific details. `null` if the payout status is `UPCOMING` or if the FX conversion was successful."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/payouts":{"get":{"tags":["Affiliate Programs"],"operationId":"listPayouts","summary":"List participant payouts","description":"**Affiliate programs only.** Retrieves a paged list of all participant payouts in an affiliate program.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/NextId"},{"$ref":"#/components/parameters/Limit100"},{"$ref":"#/components/parameters/PayoutStatus"}],"responses":{"200":{"description":"Participant payouts returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantPayoutListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List payouts for a participant

> \*\*Affiliate programs only.\*\* Retrieves a paged list of payouts that belong to a participant.

```json
{"openapi":"3.1.0","info":{"title":"GrowSurf REST API","version":"2.0.0"},"tags":[{"name":"Affiliate Programs","description":"Affiliate transaction, commission, and payout operations."}],"servers":[{"url":"https://api.growsurf.com/v2","description":"Production"}],"security":[{"apiKeyAuth":[]}],"components":{"securitySchemes":{"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"GrowSurf REST API key supplied as `Authorization: Bearer <api_key>`."}},"parameters":{"CampaignId":{"name":"id","in":"path","required":true,"description":"GrowSurf program ID.","schema":{"type":"string"}},"ParticipantIdOrEmail":{"name":"participantIdOrEmail","in":"path","required":true,"description":"GrowSurf participant ID or URL-encoded participant email address.","schema":{"type":"string"}},"NextId":{"name":"nextId","in":"query","required":false,"description":"ID to start the next paged result set with.","schema":{"type":"string"}},"Limit100":{"name":"limit","in":"query","required":false,"description":"Number of results to return. Maximum 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}},"PayoutStatus":{"name":"status","in":"query","required":false,"description":"Participant payout status.","schema":{"$ref":"#/components/schemas/PayoutStatus"}}},"schemas":{"PayoutStatus":{"type":"string","enum":["UPCOMING","QUEUED","ISSUED","FAILED","REVERSED"]},"ParticipantPayoutListResponse":{"type":"object","required":["payouts","limit","nextId"],"properties":{"payouts":{"description":"Participant payouts returned for this page.","type":"array","items":{"$ref":"#/components/schemas/ParticipantPayout"}},"limit":{"description":"Maximum number of payouts requested for this page.","type":"integer"},"nextId":{"description":"Payout ID to pass as `nextId` for the next page, or `null` when there are no more results.","type":["string","null"]}}},"ParticipantPayout":{"type":"object","description":"**Affiliate programs only.** A payout generated for a participant in an affiliate program. All payouts start with a status of `UPCOMING`, which represents an aggregation of the participant's `ParticipantCommission` amounts, and is adjusted in real time as commissions change.","required":["id","participantId","commissionIds","amount","currencyISO","status","createdAt"],"properties":{"id":{"type":"string","readOnly":true,"description":"The unique identifier of the participant payout."},"participantId":{"type":"string","readOnly":true,"description":"The participant ID of the referrer who earned the payout."},"commissionIds":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"The IDs of the associated `ParticipantCommission`s."},"amount":{"type":"integer","readOnly":true,"description":"Payout amount in the currency's smallest denomination (e.g., `100` cents equals $1.00 USD, and `100` equals ¥100 for a zero-decimal currency). Always non-negative."},"currencyISO":{"type":"string","readOnly":true,"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code of the payout (e.g., `USD`, `GBP`)."},"status":{"$ref":"#/components/schemas/PayoutStatus","readOnly":true,"description":"Current lifecycle state of the payout."},"createdAt":{"type":"integer","format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the payout record was created."},"queuedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the payout was queued for processing (status `QUEUED`). `null` until the payout is queued."},"issuedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the payout was issued. `null` if the payout was not successfully issued."},"failedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the payout failed to be issued. `null` if the payout did not fail to be issued."},"reversedAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when an issued payout was returned or cancelled and transitioned to `REVERSED`. `null` unless the payout is reversed."},"provider":{"type":["string","null"],"readOnly":true,"description":"The payment provider used to issue the payout (e.g., `paypal`). `null` for payouts with a status of `UPCOMING`."},"amountInCampaignCurrency":{"type":["integer","null"],"readOnly":true,"description":"Payout amount converted to the program's currency, in the currency's smallest denomination. Always non-negative. `null` if the payout status is `UPCOMING` or if it is still pending FX conversion."},"campaignCurrencyISO":{"type":["string","null"],"readOnly":true,"description":"The [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code of the program used in currency conversion (e.g., `USD`, `GBP`). `null` if the payout status is `UPCOMING` or if it is still pending FX conversion."},"exchangeRateAt":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"UTC timestamp (in milliseconds) when the currency exchange rate was received. `null` if the payout status is `UPCOMING` or if it is still pending FX conversion."},"exchangeRate":{"type":["number","null"],"readOnly":true,"description":"The currency exchange rate used for conversion. `null` if the payout status is `UPCOMING` or if it is still pending FX conversion."},"fxError":{"type":["string","null"],"readOnly":true,"description":"If there was an error with FX conversion, the specific details. `null` if the payout status is `UPCOMING` or if the FX conversion was successful."}}},"Error":{"type":"object","properties":{"name":{"description":"Error name returned for the failed request.","type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"message":{"description":"Human-readable error message.","type":"string"},"errors":{"description":"Optional validation details for specific request fields.","type":"array","items":{"type":"object","additionalProperties":true}},"status":{"description":"HTTP status code for the error.","type":"integer"},"supportUrl":{"description":"Support URL related to the error, when available.","type":"string","format":"uri"},"policyName":{"description":"Rate-limit policy name for `429` errors, when available.","type":"string"},"level":{"description":"Error severity level, when available.","type":"string"},"timestamp":{"description":"When the error response was created.","type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is missing, invalid, revoked, outside its scope, or not allowed to perform this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Request is not valid for the current program or participant state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests.","headers":{"RateLimit-Policy":{"description":"Named quota policies, quotas, and windows using the IETF RateLimit Structured Fields format.","schema":{"type":"string"}},"RateLimit":{"description":"Remaining quota and reset time in seconds for the active policy or policies.","schema":{"type":"string"}},"Retry-After":{"description":"Whole seconds to wait before retrying this request. This field takes precedence over RateLimit.","schema":{"type":"string","pattern":"^[0-9]+$"}},"GrowSurf-RateLimit-Second-Limit":{"description":"Number of API requests allowed per second policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Second-Remaining":{"description":"Number of API requests remaining in the second policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Second-Milliseconds":{"description":"Milliseconds until the second policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Limit":{"description":"Number of API requests allowed per minute policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Minute-Remaining":{"description":"Number of API requests remaining in the minute policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Minute-Milliseconds":{"description":"Milliseconds until the minute policy window resets.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Limit":{"description":"Number of API requests allowed per hour policy.","schema":{"type":"integer"}},"GrowSurf-RateLimit-Hour-Remaining":{"description":"Number of API requests remaining in the hour policy.","schema":{"type":"integer"}},"GrowSurf-Retry-After-Hour-Milliseconds":{"description":"Milliseconds until the hour policy window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/campaign/{id}/participant/{participantIdOrEmail}/payouts":{"get":{"tags":["Affiliate Programs"],"operationId":"listParticipantPayouts","summary":"List payouts for a participant","description":"**Affiliate programs only.** Retrieves a paged list of payouts that belong to a participant.","parameters":[{"$ref":"#/components/parameters/CampaignId"},{"$ref":"#/components/parameters/ParticipantIdOrEmail"},{"$ref":"#/components/parameters/NextId"},{"$ref":"#/components/parameters/Limit100"},{"$ref":"#/components/parameters/PayoutStatus"}],"responses":{"200":{"description":"Participant payouts returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantPayoutListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.growsurf.com/developer-tools/rest-api/api-reference.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
