---
title: "Set this affiliate's commission rate, in whole percent (spec 3.7)."
method: PUT
path: "/api/v1/admin/affiliates/{affiliate_id}/rate"
tags: ["Affiliate Admin"]
---

# Set this affiliate's commission rate, in whole percent (spec 3.7).

`PUT /api/v1/admin/affiliates/{affiliate_id}/rate`

## Request

- Base URL: `https://api.avatarfactory.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.avatarfactory.io/api/v1/admin/affiliates/{affiliate_id}/rate`
- Auth: one of:
  - API key in cookie `session`
  - HTTP bearer

<br/>The program default is 50 percent and a negotiated partner can carry any<br/>whole percent from 1 to 100. Anything else is 422 `invalid_rate` and<br/>writes nothing, and that is the ONLY refusal shape this route has: a<br/>fractional percent and a string both come back as `{code, error}` rather<br/>than as the validator's raw array (round 14 walkthrough, finding 7). The change is NEVER retroactive: every commission row<br/>stores the rate it was booked at, so this only reaches invoices that<br/>arrive from now on.<br/><br/>Every change appends an `audit_log` row naming this admin, the affiliate,<br/>and the old and new rate, the same treatment the payout-details reveal<br/>gets. The response is the updated queue row, so the drawer re-renders from<br/>the answer rather than re-reading.

## Path parameters

- `affiliate_id` integer, required

## Request body

- AdminRateRequest: The per-affiliate commission rate, in whole percent (spec 3.7). Deliberately UNTYPED here. The rule (a whole number from 1 to 100) lives in the route and the service, so every refusal comes back as this module's `{code, error}` shape. Declared as `int` the endpoint had TWO error contracts: 0 and 101 answered `invalid_rate` while 50.5 and "abc" answered a raw Pydantic array, and only one of the two was the product's (round 14 walkthrough, finding 7). `json_schema_extra` still documents the real shape, so Swagger and a generated client know it is an integer from 1 to 100. Documentation, not validation: that is what keeps the refusal in the handler. The `default=None` is what keeps a MISSING key out of Pydantic's array too, so it has to stay, but a default publishes the field as OPTIONAL and it is not (Task 15 review, finding 7). The `description` says so, since that is the only half of "required" this shape can carry: omitting `percent` answers `invalid_rate`, the same refusal every other bad value gets.
  - `percent` integer: REQUIRED. A whole percent from 1 to 100. Omitting it, or sending a fraction, a string or a boolean, answers 422 invalid_rate.

## Response `200`

OK

- AdminAffiliateItem: One affiliate as the admin queue sees it. Wider than the affiliate's own `AffiliateView`: it carries `user_id` and `decided_by_user_id` so the queue can link to the account and show who decided. `payout_details` is masked here exactly as it is everywhere else.
  - `applied_at` string, date-time, nullable
  - `channels` string[]
  - `code` string, required
  - `code_renamed` boolean, required
  - `commission_rate_bps` integer, required
  - `decided_at` string, date-time, nullable
  - `decided_by_user_id` integer, nullable
  - `decline_reason` string, nullable
  - `email` string, required
  - `full_name` string, required
  - `id` integer, required
  - `payout_details` object
  - `payout_method` string, required
  - `phone` string, nullable
  - `status` string, required
  - `terms_accepted_at` string, date-time, nullable
  - `terms_version` string, nullable
  - `user_id` integer, required

## Other responses

- `404`: Not Found
- `422`: Unprocessable Entity

---

[API](https://skmtc.dev/avatarfactory/apis/avatarfactory-api.md) · [All operations](https://skmtc.dev/avatarfactory/apis/avatarfactory-api/llms.txt) · [OpenAPI document](https://skmtc.dev/avatarfactory/apis/avatarfactory-api/revisions/66fc7aa3f296?raw)
