Payouts REST API — Documentation
Date: 2026-05-07 Status: Implemented Module: AffiliateSupport Scope: REST API endpoints for payout management, payout requests, and payout settings
1. Objective
Expose the full functionality of the Payouts management system as external REST API endpoints. These endpoints let third-party systems:
- List payouts with filtering, searching, and pagination
- Retrieve a single payout with full details
- View aggregated payout statistics
- Transition payout status (pending → processing → completed/failed)
- Bulk process multiple payouts at once
- Manage affiliate payout requests (approve/reject)
- Export payouts as CSV
- Download PDF payout statements
- Read and update payout settings (frequency, minimum, early requests)
2. Authentication & Scopes
All endpoints require a Bearer token issued via the Access Token system.
2.1 Scopes
| Scope | Access |
|---|---|
payouts:read |
List, show, stats, requests, export, statement |
payouts:write |
Status transitions, bulk actions, approve/reject requests |
settings:read |
Read payout settings |
settings:write |
Update payout settings |
2.2 Authentication Header
Authorization: Bearer <access_token>
2.3 Error Responses
HTTP 401 — Unauthorized (missing or invalid token)
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing access token."
}
}
HTTP 403 — Forbidden (token lacks required scope)
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Insufficient scope."
}
}
3. Endpoint Overview
3.1 Payout Management
| Method | Path | Scope | Throttle | Description |
|---|---|---|---|---|
GET |
/v1/payouts |
payouts:read |
120/min | List payouts (paginated, filterable) |
GET |
/v1/payouts/stats |
payouts:read |
120/min | Business payout statistics |
GET |
/v1/payouts/requests |
payouts:read |
120/min | List affiliate payout requests |
GET |
/v1/payouts/export |
payouts:read |
30/min | Download CSV export |
GET |
/v1/payouts/{id} |
payouts:read |
120/min | Single payout detail |
GET |
/v1/payouts/{id}/statement |
payouts:read |
30/min | Download PDF statement |
POST |
/v1/payouts/{id}/processing |
payouts:write |
60/min | Mark payout as processing |
POST |
/v1/payouts/{id}/complete |
payouts:write |
60/min | Mark payout as completed |
POST |
/v1/payouts/{id}/fail |
payouts:write |
60/min | Mark payout as failed |
POST |
/v1/payouts/bulk-processing |
payouts:write |
60/min | Bulk mark as processing |
POST |
/v1/payouts/bulk-complete |
payouts:write |
60/min | Bulk mark as completed |
POST |
/v1/payouts/requests/{id}/approve |
payouts:write |
60/min | Approve a payout request |
POST |
/v1/payouts/requests/{id}/reject |
payouts:write |
60/min | Reject a payout request |
3.2 Payout Settings
| Method | Path | Scope | Throttle | Description |
|---|---|---|---|---|
GET |
/v1/settings/payouts |
settings:read |
120/min | Read payout configuration |
PUT |
/v1/settings/payouts |
settings:write |
60/min | Update payout configuration |
4. Payout Status Lifecycle
┌──────────┐
│ Pending │
└────┬─────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌────────┐
│Processing│ │Completed│ │ Failed │
└────┬─────┘ └─────────┘ └────────┘
│
┌────┼────┐
▼ ▼
┌─────────┐ ┌────────┐
│Completed│ │ Failed │
└─────────┘ └────────┘
Valid transitions:
| From | To | Endpoint |
|---|---|---|
pending |
processing |
POST /v1/payouts/{id}/processing |
pending |
completed |
POST /v1/payouts/{id}/complete |
pending |
failed |
POST /v1/payouts/{id}/fail |
processing |
completed |
POST /v1/payouts/{id}/complete |
processing |
failed |
POST /v1/payouts/{id}/fail |
Invalid transitions return HTTP 409 with error code INVALID_STATUS.
5. Detailed Endpoint Specifications
5.1 List Payouts — GET /v1/payouts
Scope: payouts:read
Query Parameters
| Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
search |
string | No | — | max:255 | Search by affiliate name or email |
status |
string | No | — | pending, processing, completed, failed |
Filter by status |
affiliate_id |
integer | No | — | — | Filter by affiliate ID |
from |
date | No | — | YYYY-MM-DD, <= to |
Start of date range (created_at >=) |
to |
date | No | — | YYYY-MM-DD, >= from |
End of date range (created_at <=) |
per_page |
integer | No | 15 | min:1, max:100 | Items per page |
page |
integer | No | 1 | min:1 | Page number |
Success Response — HTTP 200
{
"success": true,
"message": "OK",
"data": {
"items": [
{
"id": 42,
"affiliate": {
"id": 7,
"name": "Jane Smith",
"referral_code": "JANE2026"
},
"amount": "250.00",
"currency": "USD",
"method": "bank_transfer",
"status": "pending",
"period_start": "2026-04-01",
"period_end": "2026-04-30",
"reference": null,
"paid_at": null,
"batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2026-05-01T00:00:00.000Z"
}
],
"meta": {
"current_page": 1,
"per_page": 15,
"total": 1,
"last_page": 1
}
}
}
Notes:
affiliateis loaded from the affiliate’s user record. Containsid,name, andreferral_code.methodis extracted from the affiliate’s payment details. May benullif not set.batch_idgroups payouts generated together by the automated scheduler. May benullfor manual payouts.- All monetary values are strings with 2 decimal places.
- Results are ordered by
created_at DESC.
5.2 Get Payout Stats — GET /v1/payouts/stats
Scope: payouts:read
Success Response — HTTP 200
{
"success": true,
"message": "OK",
"data": {
"total_pending_amount": 1250.00,
"total_pending_count": 5,
"completed_this_month": 3400.00,
"failed_count": 1,
"pending_requests_count": 2
}
}
Field descriptions:
| Field | Description |
|---|---|
total_pending_amount |
Sum of all pending payout amounts |
total_pending_count |
Number of pending payouts |
completed_this_month |
Sum of completed payouts in the current calendar month |
failed_count |
Number of failed payouts |
pending_requests_count |
Number of pending affiliate payout requests awaiting review |
5.3 Get Payout Detail — GET /v1/payouts/{id}
Scope: payouts:read
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Payout record ID |
Success Response — HTTP 200
{
"success": true,
"message": "OK",
"data": {
"id": 42,
"affiliate": {
"id": 7,
"name": "Jane Smith",
"referral_code": "JANE2026"
},
"amount": "250.00",
"currency": "USD",
"method": "bank_transfer",
"status": "completed",
"period_start": "2026-04-01",
"period_end": "2026-04-30",
"reference": "TXN-12345",
"paid_at": "2026-05-02T14:30:00.000Z",
"batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2026-05-01T00:00:00.000Z",
"notes": "Paid via bank transfer",
"updated_at": "2026-05-02T14:30:00.000Z",
"payout_request": {
"id": 10,
"requested_amount": "250.00",
"status": "approved",
"notes": "Need early payout for expenses",
"created_at": "2026-04-28T10:00:00.000Z"
}
}
}
Notes:
- Detail view returns additional fields not in the list:
notes,updated_at, and linkedpayout_request. payout_requestis present only if this payout was created from an affiliate’s early payout request. Otherwise omitted.
Error — HTTP 404
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Payout not found."
}
}
5.4 List Payout Requests — GET /v1/payouts/requests
Scope: payouts:read
Query Parameters
| Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
status |
string | No | — | pending, approved, rejected |
Filter by request status |
affiliate_id |
integer | No | — | — | Filter by affiliate ID |
per_page |
integer | No | 15 | min:1, max:100 | Items per page |
page |
integer | No | 1 | min:1 | Page number |
Success Response — HTTP 200
{
"success": true,
"message": "OK",
"data": {
"items": [
{
"id": 10,
"affiliate": {
"id": 7,
"name": "Jane Smith",
"referral_code": "JANE2026"
},
"requested_amount": "250.00",
"notes": "Need early payout for expenses",
"status": "pending",
"rejection_reason": null,
"payout_id": null,
"reviewed_at": null,
"created_at": "2026-04-28T10:00:00.000Z"
}
],
"meta": {
"current_page": 1,
"per_page": 15,
"total": 1,
"last_page": 1
}
}
}
Notes:
payout_idis populated after the request is approved and a payout is created.rejection_reasonis populated only for rejected requests.reviewed_atis the timestamp when the request was approved or rejected.
5.5 Export Payouts CSV — GET /v1/payouts/export
Scope: payouts:read Throttle: 30/min (heavy operation)
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
string | No | Filter by payout status |
affiliate_id |
integer | No | Filter by affiliate ID |
from |
date | No | Start of date range |
to |
date | No | End of date range |
Success Response — HTTP 200
Content-Type: text/csv Content-Disposition: attachment; filename="payouts-export-2026-05-07.csv"
Affiliate,Email,Amount,Currency,Method,Status,Period Start,Period End,Reference,Paid At,Created At
Jane Smith,jane@example.com,250.00,USD,bank_transfer,completed,2026-04-01,2026-04-30,TXN-12345,2026-05-02 14:30:00,2026-05-01 00:00:00
5.6 Download Payout Statement — GET /v1/payouts/{id}/statement
Scope: payouts:read Throttle: 30/min (PDF generation)
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Payout record ID |
Success Response — HTTP 200
Content-Type: application/pdf Content-Disposition: attachment; filename="payout-statement-42.pdf"
Returns a PDF document containing:
- Payout details (amount, currency, period, reference)
- Conversions included in the payout period
- Conversion count, total order value, total commission
Error — HTTP 404
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Payout not found."
}
}
5.7 Mark Payout as Processing — POST /v1/payouts/{id}/processing
Scope: payouts:write
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Payout record ID |
Success Response — HTTP 200
{
"success": true,
"message": "Payout marked as processing.",
"data": {
"id": 42,
"affiliate": { "id": 7, "name": "Jane Smith", "referral_code": "JANE2026" },
"amount": "250.00",
"currency": "USD",
"method": "bank_transfer",
"status": "processing",
"period_start": "2026-04-01",
"period_end": "2026-04-30",
"reference": null,
"paid_at": null,
"batch_id": null,
"created_at": "2026-05-01T00:00:00.000Z"
}
}
Error — HTTP 409
{
"success": false,
"error": {
"code": "INVALID_STATUS",
"message": "Cannot mark payout #42 as processing: current status is completed."
}
}
5.8 Mark Payout as Completed — POST /v1/payouts/{id}/complete
Scope: payouts:write
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Payout record ID |
Body Parameters
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
reference |
string | No | max:255 | Payment reference (e.g., bank transfer ID) |
notes |
string | No | max:1000 | Optional notes |
Success Response — HTTP 200
{
"success": true,
"message": "Payout marked as completed.",
"data": {
"id": 42,
"status": "completed",
"reference": "TXN-12345",
"paid_at": "2026-05-07T14:30:00.000Z",
"..."
}
}
Side effect: Sends a PayoutCompletedNotification email to the affiliate.
Error — HTTP 409
Same as 5.7 — returned when current status is completed or failed.
5.9 Mark Payout as Failed — POST /v1/payouts/{id}/fail
Scope: payouts:write
Body Parameters
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
notes |
string | No | max:1000 | Failure reason |
Success Response — HTTP 200
{
"success": true,
"message": "Payout marked as failed.",
"data": {
"id": 42,
"status": "failed",
"..."
}
}
Side effect: Sends a PayoutFailedNotification email to the affiliate.
5.10 Bulk Mark as Processing — POST /v1/payouts/bulk-processing
Scope: payouts:write
Body Parameters
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
ids |
integer[] | Yes | min:1, max:100, each distinct | Payout IDs to transition |
Success Response — HTTP 200
{
"success": true,
"message": "3 payout(s) marked as processing.",
"data": {
"processed_count": 3,
"requested_count": 5,
"skipped_count": 2
}
}
Notes:
- Only
pendingpayouts are transitioned. Non-pending IDs are silently skipped. skipped_count=requested_count-processed_count.
5.11 Bulk Mark as Completed — POST /v1/payouts/bulk-complete
Scope: payouts:write
Body Parameters
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
ids |
integer[] | Yes | min:1, max:100, each distinct | Payout IDs to complete |
reference |
string | No | max:255 | Payment reference (applied to all) |
Success Response — HTTP 200
{
"success": true,
"message": "3 payout(s) marked as completed.",
"data": {
"completed_count": 3,
"requested_count": 3,
"skipped_count": 0
}
}
Notes:
- Only
pendingandprocessingpayouts are transitioned. Others are skipped. - The
referenceis applied to all completed payouts in the batch.
5.12 Approve Payout Request — POST /v1/payouts/requests/{id}/approve
Scope: payouts:write
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Payout request ID |
Success Response — HTTP 200
{
"success": true,
"message": "Payout request approved.",
"data": {
"payout_request": {
"id": 10,
"affiliate": { "id": 7, "name": "Jane Smith", "referral_code": "JANE2026" },
"requested_amount": "250.00",
"status": "approved",
"payout_id": 42,
"reviewed_at": "2026-05-07T14:30:00.000Z",
"created_at": "2026-04-28T10:00:00.000Z"
},
"payout": {
"id": 42,
"amount": "250.00",
"currency": "USD",
"status": "pending",
"created_at": "2026-05-07T14:30:00.000Z"
}
}
}
Side effects:
- Creates a new
pendingpayout linked to the request. - Sends a
PayoutRequestApprovedNotificationemail to the affiliate.
Error — HTTP 409
{
"success": false,
"error": {
"code": "INVALID_STATUS",
"message": "Cannot approve request: current status is approved."
}
}
5.13 Reject Payout Request — POST /v1/payouts/requests/{id}/reject
Scope: payouts:write
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Payout request ID |
Body Parameters
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
reason |
string | Yes | max:1000 | Rejection reason (required) |
Success Response — HTTP 200
{
"success": true,
"message": "Payout request rejected.",
"data": {
"id": 10,
"affiliate": { "id": 7, "name": "Jane Smith", "referral_code": "JANE2026" },
"requested_amount": "250.00",
"status": "rejected",
"rejection_reason": "Insufficient documentation provided.",
"reviewed_at": "2026-05-07T14:30:00.000Z",
"created_at": "2026-04-28T10:00:00.000Z"
}
}
Side effect: Sends a PayoutRequestRejectedNotification email to the affiliate.
Validation Error — HTTP 422
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": {
"reason": ["The reason field is required."]
}
}
}
6. Payout Settings Endpoints
6.1 Get Payout Settings — GET /v1/settings/payouts
Scope: settings:read
Success Response — HTTP 200
{
"success": true,
"message": "OK",
"data": {
"payout_frequency": "monthly",
"payout_day": 15,
"min_payout": "100.00",
"currency": "USD",
"allow_early_requests": true,
"auto_create_payouts": true,
"next_payout_date": "2026-06-15"
}
}
Field descriptions:
| Field | Type | Description |
|---|---|---|
payout_frequency |
string | weekly or monthly |
payout_day |
integer | Day of week (1-7, ISO) for weekly; day of month (1-31) for monthly |
min_payout |
string | Minimum balance required to trigger a scheduled payout |
currency |
string | Currency code (ISO 4217) |
allow_early_requests |
boolean | Whether affiliates can request early payouts |
auto_create_payouts |
boolean | Whether the system automatically creates scheduled payouts |
next_payout_date |
string|null | Estimated next payout date. null if auto_create_payouts is disabled |
6.2 Update Payout Settings — PUT /v1/settings/payouts
Scope: settings:write
Body Parameters
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
payout_frequency |
string | Yes | weekly, monthly |
Payout schedule frequency |
payout_day |
integer | Yes | 1-7 (weekly) or 1-31 (monthly) | Day of payout |
min_payout |
number | Yes | min:0 | Minimum balance threshold |
allow_early_requests |
boolean | Yes | — | Allow affiliate early payout requests |
auto_create_payouts |
boolean | Yes | — | Enable automatic scheduled payouts |
Note: payout_day max depends on payout_frequency:
- Weekly: max is 7 (ISO day of week: 1=Monday, 7=Sunday)
- Monthly: max is 31 (capped to actual days in month during generation)
Success Response — HTTP 200
{
"success": true,
"message": "Payout settings updated.",
"data": {
"payout_frequency": "weekly",
"payout_day": 5,
"min_payout": "25.00",
"currency": "USD",
"allow_early_requests": true,
"auto_create_payouts": false,
"next_payout_date": null
}
}
7. Database Schema
7.1 payouts Table
| Column | Type | Notes |
|---|---|---|
id |
BIGINT PK | Auto-increment |
business_id |
FK → businesses | Multi-tenant scope, cascade delete |
affiliate_id |
FK → affiliates | Cascade delete |
amount |
DECIMAL(12,2) | Payout amount |
currency |
VARCHAR(3) | Default USD |
method |
VARCHAR(50), nullable | Payment method from affiliate profile |
status |
ENUM | pending, processing, completed, failed |
reference |
VARCHAR(255), nullable | Payment reference (e.g., bank transfer ID) |
notes |
TEXT, nullable | Optional notes |
paid_at |
TIMESTAMP, nullable | When the payout was completed |
period_start |
DATE, nullable | Start of commission period |
period_end |
DATE, nullable | End of commission period |
payout_request_id |
FK → payout_requests, nullable | Linked early request |
batch_id |
VARCHAR(36), nullable | UUID grouping scheduled payouts |
created_at, updated_at |
TIMESTAMPS | Standard |
Indexes: (business_id, affiliate_id), (business_id, paid_at), (business_id, status), BRIN (created_at).
7.2 payout_requests Table
| Column | Type | Notes |
|---|---|---|
id |
BIGINT PK | Auto-increment |
business_id |
FK → businesses | Multi-tenant scope, cascade delete |
affiliate_id |
FK → affiliates | Cascade delete |
requested_amount |
DECIMAL(12,2) | Amount requested |
notes |
TEXT, nullable | Affiliate’s reason for early request |
status |
ENUM | pending, approved, rejected |
rejection_reason |
TEXT, nullable | Reason for rejection |
payout_id |
FK → payouts, nullable | Created payout (after approval) |
reviewed_at |
TIMESTAMP, nullable | When approved/rejected |
created_at, updated_at |
TIMESTAMPS | Standard |
Indexes: (business_id, affiliate_id), (business_id, status).
7.3 business_payout_settings Table
| Column | Type | Notes |
|---|---|---|
id |
BIGINT PK | Auto-increment |
business_id |
FK → businesses | Unique, cascade delete |
payout_frequency |
VARCHAR(20) | weekly or monthly (default: monthly) |
payout_day |
SMALLINT | Default: 1 |
min_payout |
DECIMAL(12,2) | Default: 50.00 |
currency |
VARCHAR(3) | Default: USD |
allow_early_requests |
BOOLEAN | Default: false |
auto_create_payouts |
BOOLEAN | Default: true |
created_at, updated_at |
TIMESTAMPS | Standard |
8. Notification Side Effects
Write operations trigger email notifications to affiliates and business owners:
| Endpoint | Notification | Recipient |
|---|---|---|
POST /v1/payouts/{id}/complete |
PayoutCompletedNotification |
Affiliate |
POST /v1/payouts/{id}/fail |
PayoutFailedNotification |
Affiliate |
POST /v1/payouts/requests/{id}/approve |
PayoutRequestApprovedNotification |
Affiliate |
POST /v1/payouts/requests/{id}/reject |
PayoutRequestRejectedNotification |
Affiliate |
All notifications are queued (implement ShouldQueue) and sent via the mail channel.
9. Security Considerations
| Concern | Mitigation |
|---|---|
| Multi-tenant isolation | All queries scoped via forBusiness() — never expose cross-business data |
| Scope enforcement | Read operations require payouts:read, write operations require payouts:write |
| Rate limiting | Read at 120/min, write at 60/min, export/PDF at 30/min |
| Idempotency | Invalid status transitions return 409 (not 500) — safe to retry |
| Bulk action cap | Maximum 100 IDs per bulk request to prevent abuse |
| Audit trail | All status transitions logged via AuditService (Auditable trait on models) |
| Input validation | All inputs validated via dedicated FormRequest classes |
10. Data Flow
External Client
│
▼
┌─────────────────────────────────────────────────┐
│ API Domain (api.token middleware) │
│ ├── Validates Bearer token │
│ ├── Resolves Business from token │
│ ├── Enforces scope (payouts:read/write) │
│ └── Sets request->attributes->business │
└────────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ PayoutApiController (extends BaseApiController)│
│ ├── Validates input via FormRequest │
│ ├── Delegates to PayoutService │
│ ├── Formats response via formatPayout() │
│ └── Returns ApiResponse envelope │
└────────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ PayoutService (EXISTING) │
│ ├── businessPayouts() → paginated query │
│ ├── businessPayoutStats() → aggregated stats │
│ ├── markProcessing/Completed/Failed() │
│ ├── bulkMarkProcessing/Completed() │
│ ├── approveRequest()/rejectRequest() │
│ └── AuditService::log() → audit trail │
└────────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ PostgreSQL — payouts, payout_requests, │
│ business_payout_settings tables │
│ (scoped by business_id, indexed) │
└─────────────────────────────────────────────────┘
11. Implementation Files
Created
| File | Purpose |
|---|---|
app/Core/Enums/ApiKeyScope.php |
Added PayoutsRead, PayoutsWrite enum cases |
database/migrations/2026_05_07_000001_backfill_payout_scopes_to_api_keys.php |
Backfill scopes to existing API keys |
modules/AffiliateSupport/Http/Controllers/Api/PayoutApiController.php |
Controller with 13 endpoints |
modules/AffiliateSupport/Http/Requests/Api/ListPayoutsApiRequest.php |
List validation |
modules/AffiliateSupport/Http/Requests/Api/ListPayoutRequestsApiRequest.php |
Requests list validation |
modules/AffiliateSupport/Http/Requests/Api/MarkPayoutCompletedApiRequest.php |
Complete action validation |
modules/AffiliateSupport/Http/Requests/Api/MarkPayoutFailedApiRequest.php |
Fail action validation |
modules/AffiliateSupport/Http/Requests/Api/RejectPayoutRequestApiRequest.php |
Reject action validation |
modules/AffiliateSupport/Http/Requests/Api/BulkPayoutActionApiRequest.php |
Bulk action validation |
modules/AffiliateSupport/Http/Requests/Api/UpdatePayoutSettingsApiRequest.php |
Settings update validation |
tests/Feature/Api/PayoutApiTest.php |
29 feature tests |
Modified
| File | Change |
|---|---|
modules/AffiliateSupport/Routes/api.php |
Added payout route groups + settings routes |
modules/AffiliateSupport/Http/Controllers/Api/BusinessSettingsApiController.php |
Added showPayoutSettings(), updatePayoutSettings() |
12. Test Coverage
29 tests, 133 assertions — all passing.
| Category | Tests |
|---|---|
| List payouts (paginated, filtered, tenant-isolated) | 5 |
| Show payout (detail, 404 cases) | 3 |
| Stats | 1 |
| List payout requests | 1 |
| Export CSV | 1 |
| Status transitions (processing, complete, fail) | 5 |
| Bulk operations | 3 |
| Payout request approve/reject | 4 |
| Auth & scope enforcement | 3 |
| Payout settings (read, update, validation) | 3 |