# Affiliate

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /affiliate/programs

**List affiliate programs**

`operationId: AffiliateController_listPrograms`

The affiliate programs defined for the org, with their commission structures.

#### Signature

```http
GET /affiliate/programs (status?: string, type?: string) -> Programs
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/programs/{name}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `status` | query | string | — | Filter by status. |
| `type` | query | string | — | Filter by type. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Programs |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/programs

**Create an affiliate program**

`operationId: AffiliateController_createProgram`

Creates a program. The name is its identifier and must be unique — a duplicate is refused rather than silently overwriting the existing program and its commission terms.

#### Signature

```http
POST /affiliate/programs (body) -> The created program
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NAME_REQUIRED | Program name is required | `name` is missing. | The name is the program identifier. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/affiliates`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The program to create.

```json
{
  "name": "partner-2026",
  "commissionType": "percentage",
  "commissionValue": 10,
  "cookieDays": 30
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created program |
| `400` | Program name is required — `name` is missing. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/programs/{name}

**Get an affiliate program**

`operationId: AffiliateController_getProgram`

Fetches one program with its commission rules and terms.

#### Signature

```http
GET /affiliate/programs/{name} (name: string) -> The program
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | PROGRAM_NOT_FOUND | Program not found | No affiliate program has that name. | List programs with `GET /affiliate/programs`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PUT /affiliate/programs/{name}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `name` | path | string | yes | Program name. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The program |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Program not found — No affiliate program has that name. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /affiliate/programs/{name}

**Update an affiliate program**

`operationId: AffiliateController_updateProgram`

Updates a program. Changing commission terms affects **future** referrals — those already tracked keep the rate they were recorded at, which is what stops a rate change retroactively altering what affiliates are owed.

#### Signature

```http
PUT /affiliate/programs/{name} (name: string, body) -> The updated program
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Existing referrals keep their recorded commission.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | PROGRAM_NOT_FOUND | Program not found | No affiliate program has that name. | List programs with `GET /affiliate/programs`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PUT /affiliate/affiliates/{id}/commission-override`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `name` | path | string | yes | Program name. |

### Request body

Fields to change.

```json
{
  "commissionValue": 12
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated program |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Program not found — No affiliate program has that name. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/affiliates

**List affiliates**

`operationId: AffiliateController_listAffiliates`

Affiliates across programs, with their status and codes.

#### Signature

```http
GET /affiliate/affiliates (type?: string, page?: string, pageSize?: string, program?: string, status?: string) -> Affiliates
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/affiliates/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `status` | query | string | — |  |
| `program` | query | string | — |  |
| `type` | query | string | — | Filter by type. |
| `page` | query | string | — | Page number (1-based). |
| `pageSize` | query | string | — | Rows per page. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Affiliates |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/affiliates

**Create an affiliate**

`operationId: AffiliateController_registerAffiliate`

Enrols an affiliate in a program and issues their referral code. One affiliate per email per program — the same person can join different programs, but not the same one twice.

#### Signature

```http
POST /affiliate/affiliates (body) -> The created affiliate
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | PROGRAM_NOT_FOUND | Program not found | No affiliate program has that name. | List programs with `GET /affiliate/programs`. |
| `400` | AFFILIATE_EXISTS | An affiliate with this email already exists in this program | That email is already enrolled in the program. | Look up the existing affiliate rather than creating a second. |
| `500` | CODE_GENERATION_FAILED | Failed to generate unique code | A unique referral code could not be produced after repeated attempts. | Retry. Persistent failure suggests the code space is exhausted or a collision check is misbehaving. |

Plus the standard platform errors: `401`, `403`, `429`.

#### See also

- `POST /affiliate/affiliates/{id}/approve`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The affiliate to enrol.

```json
{
  "email": "partner@example.com",
  "name": "Grace Hopper",
  "program": "partner-2026"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created affiliate |
| `400` | An affiliate with this email already exists in this program — That email is already enrolled in the program. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Program not found — No affiliate program has that name. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | Failed to generate unique code — A unique referral code could not be produced after repeated attempts. |

## GET /affiliate/affiliates/{id}

**Get an affiliate**

`operationId: AffiliateController_getAffiliate`

Fetches one affiliate with their code, program and commission arrangement.

#### Signature

```http
GET /affiliate/affiliates/{id} (id: string) -> The affiliate
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/affiliates/{id}/approve`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The affiliate |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/affiliates/{id}/approve

**Approve an affiliate**

`operationId: AffiliateController_approveAffiliate`

Activates an affiliate so their referrals start earning. An already-active affiliate is refused rather than silently re-approved.

#### Signature

```http
POST /affiliate/affiliates/{id}/approve (id: string) -> The approved affiliate
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |
| `400` | ALREADY_ACTIVE | Affiliate is already active | The affiliate is already approved. | Not idempotent — read the status first. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/affiliates/{id}/suspend`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The approved affiliate |
| `400` | Affiliate is already active — The affiliate is already approved. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/affiliates/{id}/reject

**Decline an affiliate application**

`operationId: AffiliateController_rejectAffiliate`

Closes an application that never started (different from suspending a working account); the applicant is told in those words. The record and the `reason` are kept.

#### Signature

```http
POST /affiliate/affiliates/{id}/reject (id: string, body) -> The declined affiliate
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |
| `400` | ALREADY_ACTIVE | This affiliate is already active — suspend them instead of rejecting the application | The affiliate is active. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/affiliates/{id}/approve`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "reason": "Audience does not match our products"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The declined affiliate |
| `400` | This affiliate is already active — suspend them instead of rejecting the application — The affiliate is active. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/affiliates/{id}/suspend

**Suspend an affiliate**

`operationId: AffiliateController_suspendAffiliate`

Suspends an affiliate, stopping new referrals from being attributed to them. Referrals already tracked and approved are unaffected — suspension stops future earning, it does not claw back past commission.

#### Signature

```http
POST /affiliate/affiliates/{id}/suspend (id: string) -> The suspended affiliate
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |
| `400` | NOT_ACTIVE | Affiliate is not active (status: <status>) | The affiliate is not currently active. | The message names the current status. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/affiliates/{id}/reactivate`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The suspended affiliate |
| `400` | Affiliate is not active (status: <status>) — The affiliate is not currently active. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/affiliates/{id}/reactivate

**Reactivate an affiliate**

`operationId: AffiliateController_reactivateAffiliate`

Restores a suspended affiliate to active, allowing new referrals to be attributed again.

#### Signature

```http
POST /affiliate/affiliates/{id}/reactivate (id: string) -> The reactivated affiliate
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/affiliates/{id}/suspend`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The reactivated affiliate |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /affiliate/affiliates/{id}/commission-override

**Override an affiliate's commission**

`operationId: AffiliateController_setCommissionOverride`

Sets a commission rate for one affiliate that differs from their program's — a negotiated rate for a large partner. `type` chooses between a flat amount and a percentage.

#### Signature

```http
PUT /affiliate/affiliates/{id}/commission-override (id: string, body) -> The updated affiliate
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Applies to future referrals; existing ones keep their recorded rate.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PUT /affiliate/programs/{name}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

The override.

```json
{
  "type": "percentage",
  "value": 15
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated affiliate |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/affiliates/{id}/codes

**Add a referral code to an affiliate**

`operationId: AffiliateController_addAffiliateCode`

Gives the affiliate a code they chose. Additive — an affiliate can hold several live codes. By default the new code becomes the main one (shown to them and used in generated links) and the code it replaces is retired, not deleted, so links already out keep crediting them. `makePrimary: false` adds without changing the main code. Codes are matched case-insensitively and stored as typed.

#### Signature

```http
POST /affiliate/affiliates/{id}/codes (id: string, body) -> The affiliate, with the new code in `codes[]`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |
| `400` | CODE_REQUIRED | A referral code is required | `code` is empty. | — |
| `409` | CODE_TAKEN | "<code>" is already taken by <name> | Another affiliate holds it (a retired one: "was used by <name> and still points at them"). | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/codes/check`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "code": "sarah",
  "label": "Personal"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The affiliate, with the new code in `codes[]` |
| `400` | A referral code is required — `code` is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `409` | "<code>" is already taken by <name> — Another affiliate holds it (a retired one: "was used by <name> and still points at them"). |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/affiliates/{id}/codes/send

**Send an affiliate their code**

`operationId: AffiliateController_sendAffiliateCode`

Emails and texts the affiliate their current code and referral link; nothing changes. The link is built on the site the org publishes on, never from the caller's host; with no site the code is still sent and `linkUnavailable` says why. Optional `productSlug`, `page`, `url` or `siteName` aim the link; `code` sends one of their other codes instead of the main one.

#### Signature

```http
POST /affiliate/affiliates/{id}/codes/send (id: string, body) -> { affiliate, code, link, linkUnavailable? }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |
| `409` | NO_CODE | This affiliate has no code to send yet — issue one first. | The affiliate has no code. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "productSlug": "trail-shoe"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | { affiliate, code, link, linkUnavailable? } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `409` | This affiliate has no code to send yet — issue one first. — The affiliate has no code. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/affiliates/{id}/codes/reset

**Issue a new code**

`operationId: AffiliateController_resetAffiliateCode`

Generates a fresh code, makes it the main one and retires the one it replaces (links already shared keep crediting them). The affiliate is emailed and texted the new code and link.

#### Signature

```http
POST /affiliate/affiliates/{id}/codes/reset (id: string, body) -> { affiliate, code, replacedCode, link }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | { affiliate, code, replacedCode, link } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /affiliate/affiliates/{id}/codes/primary

**Choose the main code**

`operationId: AffiliateController_setPrimaryCode`

The main code is what the affiliate is shown and what generated links carry. Promoting a retired code brings it back into circulation.

#### Signature

```http
PUT /affiliate/affiliates/{id}/codes/primary (id: string, body) -> The affiliate
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |
| `400` | CODE_DISABLED | "<code>" is disabled — turn it back on before making it the main code | The code is disabled. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "code": "sarah"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The affiliate |
| `400` | "<code>" is disabled — turn it back on before making it the main code — The code is disabled. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /affiliate/affiliates/{id}/codes/status

**Retire, disable or reactivate a code**

`operationId: AffiliateController_setCodeStatus`

`retired` stops advertising a code but keeps crediting links already out; `disabled` stops it resolving at all (a leaked or abused code); `active` restores it. The main code cannot be stood down — promote another first.

#### Signature

```http
PUT /affiliate/affiliates/{id}/codes/status (id: string, body) -> The affiliate
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | AFFILIATE_NOT_FOUND | Affiliate not found | No affiliate has that id. | List affiliates with `GET /affiliate/affiliates`. |
| `400` | MAIN_CODE | "<code>" is this affiliate's main code. Make another one their main code first, otherwise they have nothing to hand out. | Retiring or disabling the main code. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "code": "spring-promo",
  "status": "retired"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The affiliate |
| `400` | "<code>" is this affiliate's main code. Make another one their main code first, otherwise they have nothing to hand out. — Retiring or disabling the main code. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Affiliate not found — No affiliate has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/codes/check

**Check a referral code**

`operationId: AffiliateController_checkAffiliateCode`

Answers before anything is written, so a form can say "taken" while someone types. Never an error: a code that cannot be used returns `available: false` with the reason. Pass `affiliateId` when adding to an existing affiliate so their own codes do not collide.

#### Signature

```http
GET /affiliate/codes/check (code?: string, affiliateId?: string) -> { available, code, reason? }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `code` | query | string | yes |  |
| `affiliateId` | query | string | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { available, code, reason? } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/links/generate

**Generate an affiliate link**

`operationId: AffiliateController_generateLink`

Builds a tracking link for an affiliate, optionally pointing at a specific product or page. Identify the affiliate by `code` or `affiliateId`; a destination comes from `url`, `productSlug` or `page`.

#### Signature

```http
POST /affiliate/links/generate (body) -> The tracking link
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NO_SITE | No site found. Provide a url or siteName. | No destination site could be resolved. | Supply a `url`, or configure a default site. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/public/resolve/{code}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

What link to build.

```json
{
  "code": "GRACE10",
  "productSlug": "cola-330ml"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The tracking link |
| `400` | No site found. Provide a url or siteName. — No destination site could be resolved. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/referrals

**List referrals**

`operationId: AffiliateController_listReferrals`

Referrals with filters and paging — the ledger of what affiliates have driven and what stage each is at.

#### Signature

```http
GET /affiliate/referrals (affiliateId?: string, affiliateCode?: string, status?: string, program?: string, page?: integer, pageSize?: integer) -> Referrals
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/referrals/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `affiliateId` | query | string | — |  |
| `affiliateCode` | query | string | — |  |
| `status` | query | string | — |  |
| `program` | query | string | — |  |
| `page` | query | integer | — |  |
| `pageSize` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Referrals |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/referrals/{id}

**Get a referral**

`operationId: AffiliateController_getReferral`

Fetches one referral with its program, affiliate and full transition history resolved inline — the read for investigating why a commission is what it is.

#### Signature

```http
GET /affiliate/referrals/{id} (id: string) -> The referral with context
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/referrals/{id}/transition`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The referral with context |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/referrals/{id}/transition

**Transition a referral**

`operationId: AffiliateController_transitionReferral`

The single endpoint for every admin action on a referral: `approve`, `reject`, `hold`, `reverse`, `restore`, `expire`, `recalculate`, `override-amount`.

This is the one to use. `approve` and `reject` also exist as separate legacy endpoints that delegate here, but only this form covers the full set — holding a suspicious referral, reversing an approved one after a refund, or recalculating after a rate correction.

**Approving credits the affiliate's wallet.** Reversing an already-approved referral debits it back.

#### Signature

```http
POST /affiliate/referrals/{id}/transition (id: string, body) -> The transitioned referral
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Approval moves money into an affiliate wallet. Reverse rather than delete when something needs undoing.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/referrals/bulk-transition`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

The action to take.

```json
{
  "action": "approve",
  "reason": "Order confirmed"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The transitioned referral |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/referrals/bulk-transition

**Transition referrals in bulk**

`operationId: AffiliateController_bulkTransitionReferrals`

Applies a transition to many referrals at once — by explicit ids, or by a **filter** on status, program, affiliate or date range.

The filter form is powerful and dangerous: a broad filter with `approve` credits every matching affiliate wallet in one call. Run the same filter through `GET /affiliate/referrals` first and check the count.

#### Signature

```http
POST /affiliate/referrals/bulk-transition (body) -> Per-referral results
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Verify the filter against the referral list before running an approve.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/referrals`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

What to transition, and how.

```json
{
  "action": "approve",
  "ids": [
    "REF-4821",
    "REF-4822"
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Per-referral results |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/referrals/audit/report

**Run a referral data-quality audit**

`operationId: AffiliateController_auditReferrals`

Surfaces referrals that are wrong in ways nobody notices: stuck in a stage far too long, carrying zero commission when they should not, or orphaned from their affiliate or program.

Worth running before a commission payout — each of these is either an affiliate not being paid what they earned, or the reverse.

#### Signature

```http
GET /affiliate/referrals/audit/report () -> The audit report
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/referrals/expire-stale`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The audit report |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/referrals/{id}/approve

**Approve a referral (legacy)**

`operationId: AffiliateController_approveReferral`

Approves a referral and credits the affiliate's wallet immediately. A legacy alias for `transition` with `approve` — prefer the transition endpoint, which supports the full action set.

#### Signature

```http
POST /affiliate/referrals/{id}/approve (id: string, body) -> The approved referral
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Credits a real wallet balance.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/referrals/{id}/transition`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

Optional detail.

```json
{
  "reason": "Order confirmed"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The approved referral |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/referrals/{id}/reject

**Reject a referral (legacy)**

`operationId: AffiliateController_rejectReferral`

Rejects a referral so no commission is paid. A legacy alias for `transition` with `reject`.

#### Signature

```http
POST /affiliate/referrals/{id}/reject (id: string, body) -> The rejected referral
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/referrals/{id}/transition`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

Why it was rejected.

```json
{
  "reason": "Self-referral"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The rejected referral |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/stats

**Get affiliate statistics**

`operationId: AffiliateController_getStats`

Overall affiliate figures — referral volume, conversion and commission owed.

#### Signature

```http
GET /affiliate/stats () -> Statistics
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/stats/top-affiliates`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Statistics |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/stats/program/{programName}

**Get program statistics**

`operationId: AffiliateController_getProgramStats`

Figures for one program — whether its commission structure is actually working.

#### Signature

```http
GET /affiliate/stats/program/{programName} (programName: string) -> Program statistics
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/stats`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `programName` | path | string | yes | Program name. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Program statistics |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/stats/affiliate/{affiliateId}

**Get affiliate statistics**

`operationId: AffiliateController_getAffiliateStats`

Figures for one affiliate — what they have driven and earned.

#### Signature

```http
GET /affiliate/stats/affiliate/{affiliateId} (affiliateId: string) -> Affiliate statistics
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/stats/top-affiliates`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `affiliateId` | path | string | yes | Affiliate id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Affiliate statistics |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /affiliate/stats/top-affiliates

**Get top affiliates**

`operationId: AffiliateController_getTopAffiliates`

The best-performing affiliates by referral value.

#### Signature

```http
GET /affiliate/stats/top-affiliates (limit?: string) -> Top affiliates
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/stats`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `limit` | query | string | — | Maximum rows. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Top affiliates |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/commissions/process-held

**Process held commissions**

`operationId: AffiliateController_processHeldCommissions`

Releases commissions that were held pending review, crediting the affiliate wallets. Money moves — confirm the holds were resolved rather than merely aged out.

#### Signature

```http
POST /affiliate/commissions/process-held (body) -> What was processed
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Credits real wallet balances across affiliates.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/referrals/{id}/transition`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

Optional options.

```json
{}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | What was processed |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/referrals/expire-stale

**Expire stale referrals**

`operationId: AffiliateController_expireStaleReferrals`

Expires referrals that have sat unconverted past their attribution window. Housekeeping — but it closes referrals permanently, so check the audit report first for anything stuck rather than genuinely stale.

#### Signature

```http
POST /affiliate/referrals/expire-stale (body) -> What was expired
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Distinguish "stale" from "stuck" before running it.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /affiliate/referrals/audit/report`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

Optional options.

```json
{}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | What was expired |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /affiliate/test/attribute-order

**Test order attribution**

`operationId: AffiliateController_testAttributeOrder`

Runs the attribution logic against a test order and reports which affiliate would be credited and why — the way to verify attribution rules without waiting for a real order.

#### Signature

```http
POST /affiliate/test/attribute-order (body) -> The attribution result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Diagnostic — check whether it writes a referral before running it against production data.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /affiliate/public/track`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The test order.

```json
{
  "code": "GRACE10",
  "orderNumber": "A7K2M9QX4",
  "amount": 129.99
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The attribution result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

