Wallet
Creator wallet balances, transaction history, affiliate earnings view, payout requests, and dashboard KPI summaries used for creator analytics.
Wallet resource
Current wallet responses include:
| Field | Type | Notes |
|---|---|---|
available | integer | Funds ready for payout. |
hold | integer | Funds currently on hold. |
pending | integer | Funds currently in payout processing. |
total | integer | Aggregate wallet total. |
referral_revenue | integer | Affiliate revenue amount. |
cashout_available | boolean | Whether a payout can be requested now: available is at least payout_minimum and no payout block applies (since 2026-10-14 the minimum counts). |
payout_minimum | integer | The smallest payout the account can request, in cents of the wallet's currency (30.00 today). Also on every entry of balances[]. |
crypto_estimate | object or null | Crypto payouts (2026-10-08): what available is expected to buy in USDT after fees, at the rate of the day - usdt (string, 2 decimals) and rate, plus the deducted fees in cents. An estimate, not a promise; null unless the account pays out by crypto and a rate is known. Also on every entry of balances[]. |
hold_period_days | integer | Days a sale stays on hold for this account before it moves to available: 14 by default, set per account by Fangate. |
converted_earnings | boolean | true once a share earned in another currency was converted into this account's currency (see below). |
conversion_buffer_percentage | number | The conversion buffer in force, in percent. |
transactions | array | One page of wallet rows (see Transaction rows). |
transactions_meta | object | Paging of transactions: current_page, last_page, per_page, total. |
What a price becomes: GET /api/fees/preview
GET /api/fees/preview?price=1000 answers, for the account called (name it with X-Fangate-Account), what a sale at this price brings: net is exactly what the seller receives; the buyer pays gross, or with_age_check.gross where the buyer's country requires the age check. fees is every fee in one sum. Minor units of the account's currency; price at least 500. Any API key that reads products may call it. The account's current fees apply - a sale keeps the numbers it was booked with. The values below only show the shape; your account's fees decide the numbers.
{ "price": 1000, "currency": "EUR", "net": 825, "gross": 1100, "fees": 275, "with_age_check": { "gross": 1150, "fees": 325 } }Refund requests
Fangate does not refund through the API, but you can ask for it: POST /api/transactions/{transaction_id}/refund-request with transaction_id as payment webhooks send it (txn_123) and an optional reason (up to 1000 characters). It works for a paid sale of the account that is called (name it with X-Fangate-Account), up to 30 days after the sale, once per sale; an API key needs can_create_new_urls (Manage content). The answer is 201 with status: requested - nothing is refunded yet. Fangate reviews the request and passes it to the payment provider. When the refund goes through, the ordinary payment.cancelled follows; when it does not, refund_request.rejected with the reason. An agency account always names the account with X-Fangate-Account; without it the call answers 403, because the agency account itself has no sales. Errors: 404 for a sale that is not in the account, 422 with transaction_not_refundable (not paid or already reversed), refund_request_window_closed (older than 30 days) or refund_request_exists.
Hold period
Every earning is held before it can be paid out: a sale, an agency's share of a sale and an affiliate commission alike. The hold starts when the earning is booked - the moment the payment is confirmed - and lasts hold_period_days of the account that receives it: 14 days unless Fangate set another period for that account. It is the same for every payment method. Once the period is over, the amount moves from hold to available within a few minutes.
Shares earned in another currency
A sale is always booked in the seller's currency. An agency's share of a creator's sale and an affiliate commission go to an account that may hold another currency: such a share is converted into the recipient's currency when it is booked - at the reference exchange rate, minus a conversion buffer of up to 5 % (the value in force is conversion_buffer_percentage), rounded down to the cent. Exchange rates move between the moment a share is booked and the moment it is paid out, and banks charge for converting currencies. To cover both, Fangate deducts a conversion fee of up to 5 % from the reference rate when the share is booked. The seller's own earnings are never converted. A refund or chargeback takes back exactly the converted amount that was credited. If no reference rate is available at the moment of the sale, the share is booked unconverted in the sale's currency and shows up as its own balance.
GET /api/dashboard/summary
Pre-aggregated dashboard KPIs for the authenticated creator (content counts, link clicks, unlocks, product revenue totals, top-performing products, wallet buckets).
- Auth required: Yes
- Roles: Authenticated creator with a wallet
- Content type: none
Response shape notes
The payload extends the historical single-account shape with:
| Field | Type | Notes |
|---|---|---|
currency_unified | boolean | For one account, always true. |
accounts | array | One object per creator in the summary (single-account: one element) with user_id, email, display_name, currency_id, currency_code, and wallet_balance. |
wallet_balance | object or null | Top-level aggregate wallet buckets when every account shares the same currency_id; otherwise null (use per-account accounts[].wallet_balance). |
total_content_items | integer | Count of qualifying products with complete media. |
total_link_clicks | integer | Sum of short-link views. |
total_unlocks | integer | Sum of product unlocks. |
total_product_revenue | integer | Sum of total_earnings (minor units). |
overall_conversion_rate | number | Derived from totals. |
top_performing | array | Up to five products ranked by conversion with revenue and thumbnail metadata. |
Clients that need full wallet transaction history for charts should still call GET /api/wallet with the relevant bearer token per account.
POST /api/dashboard/summary/aggregate
Same KPI aggregation as GET /api/dashboard/summary, but merged across multiple creator accounts.
- Auth required: Yes (primary account’s
apitoken inAuthorization) - Roles: Authenticated creator with a wallet on every resolved account
- Content type:
application/json
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
additional_tokens | string[] | No | Deprecated when the bearer has linked sub-accounts (master / hybrid). Ignored in that case; aggregation uses persisted links from account_group_members instead. For standalone and pure sub_account (no own links), only the bearer is aggregated. Pure sub_account accounts must not send this field → 403. |
The primary user is always included from the bearer token. Accounts with linked_accounts automatically include every sub-account they linked (from login / GET /api/user/linked-accounts).
Important semantics
- Trust model: Combined metrics for multiple creators are allowed only when the backend recognizes a master → sub-account link created by the bearer (or the caller is aggregating only themselves). Holding another user’s token in
additional_tokensdoes not grant access. - Currency: If combined accounts use different
currency_idvalues,currency_unifiedisfalseand rootwallet_balanceisnull; use eachaccounts[].wallet_balanceand interprettotal_product_revenuewith care across currencies.
Error examples
Sub-account without own links sent additional_tokens:
{
"success": false,
"errors_message": "Sub-accounts without their own linked accounts cannot aggregate other accounts.",
"data": null
}GET /api/wallet
Return wallet balances and creator wallet transactions.
- Auth required: Yes
- Roles: Authenticated creator with wallet
Query parameters
| Parameter | Type | Required |
|---|---|---|
page | integer | No |
limit | integer | No |
from | string | No |
to | string | No |
currency | string | No |
bookings | boolean | No |
activity | string | No |
sort | string | No |
from / to narrow the transaction list to rows created in that window (UTC). Both take a date (2026-09-17) or an ISO 8601 date-time; a plain to date covers that whole day, so from=2026-09-17&to=2026-09-17 is one day. A date-time with an offset is taken as that instant (2026-09-17T16:30:00+02:00 is 14:30 UTC) - URL-encode the plus sign as %2B, a bare + in a query string is a space. Without them the list is complete. An unparsable value or a to before from answers 422 (errors_code: validation_failed).
The other parameters only shape the transaction list; the balances in the response stay those of the account currency:
currency- ISO code (usd,EUR, …): list the rows of the account's balance in that currency. An account that holds no balance in it gets an empty list.bookings=1- one row per booking: the hold and release rows of an earning are left out (its state is on the earning itself, seehold_status), and so are empty rows other than sales.activity-earnings,refunds,payoutsoraffiliate: only rows of that kind. Refunds and chargebacks of affiliate commissions count asaffiliate.sort-newest(default),oldestorlargest(by amount).
An unknown value of any of them answers 422 (errors_code: validation_failed).
Success response example
{
"success": true,
"errors_message": null,
"data": {
"available": 15000,
"hold": 3000,
"pending": 5000,
"total": 23000,
"referral_revenue": 2500,
"cashout_available": true,
"transactions": [
{
"id": 1,
"amount": 550,
"type": "product_earning",
"booking_kind": "earning",
"hold_status": "on_hold",
"hold_until": "2026-05-04T10:15:00+00:00",
"counterparty": null,
"created_at": "2026-04-20 10:15:00",
"available_at": null,
"hold_period_days": null,
"days_until_available": null,
"title": "Money for a photo",
"product_id": 42,
"product_title": "Beach set",
"transaction_id": "txn_123"
}
],
"transactions_meta": { "current_page": 1, "last_page": 1, "per_page": 15, "total": 1 }
}
}Transaction rows
transactions is one page of the wallet rows, sized by limit and moved with page (newest first unless sort says otherwise); transactions_meta tells how many pages there are. Each row carries:
| Field | Type | Notes |
|---|---|---|
id | integer | Wallet row id - not the txn_{id} of the payment webhook; that is transaction_id below. |
transaction_id | string or null | The sale's id exactly as payment webhooks send it in data.transaction_id (txn_123); null for rows that are not a sale. One sale can show up in more than one row (the hold and its release). |
amount | integer | Minor units of the wallet currency (550 = 5.50). Always positive - whether money came in or went out follows from booking_kind. |
type | string | Ledger row kind, e.g. product_earning, agency_share, affiliate_earning, refund, fee, hold, pending, withdrawal. |
booking_kind | string | What the row means: earning and affiliate (money in), refund and fee (money out), payout_requested (moved from available to processing), payout_paid (money out), payout_declined (the requested amount was taken out of processing), hold and release (money moved between on hold and available), other. |
usdt_estimate | string or null | payout_requested rows of a crypto payout (2026-10-08): what the requested amount buys in USDT at today's rate after fees, null otherwise. The payout confirmation names what was actually sent. |
hold_status | string or null | Earnings only (product_earning, agency_share, affiliate_earning): on_hold, available, or reversed once the sale was refunded or charged back. null for every other row. |
hold_until | string or null | Earnings only: while on_hold the moment it becomes available, once available the moment it was released; ISO 8601. |
counterparty | string or null | Agency share rows (agency_share): the public name of the creator whose sale it was. null otherwise. |
product_id | integer or null | The product a sale row belongs to; null for rows that are not a sale (payouts, affiliate earnings, holds). |
product_title | string or null | Title of that product. |
sale | object or null | The sale behind the row in minor units of the sale's currency: gross (what the buyer paid), fees (every fee in one sum, age check included) and net (the seller's earning), plus currency. gross = fees + net. Also status - paid, or reversed once the sale was refunded or charged back, on every row of that sale - with reversed_at and reversal_reason (refund, chargeback or other, as payment.cancelled names it). null for rows that are not a sale and for affiliate commissions - those belong to another creator's sale. |
client_reference | string or null | Your own reference of the product sold (set on POST/PATCH /api/products), so a row matches your order without a lookup. null for rows without a sale and for affiliate commissions. |
available_at, hold_period_days, days_until_available | Hold information; the last two only on hold rows. |
Reconciling a lost webhook: list the period (from / to), collect the transaction_id values and compare them with the data.transaction_id of the webhooks you received. For a sale you never heard of, the net amount is amount and the product product_id - or have the webhook sent again with POST /api/webhooks/{id}/redeliveries.
GET /api/wallet/affiliate
Return affiliate-only revenue and affiliate wallet transactions.
- Auth required: Yes
- Roles: Authenticated creator with wallet
Query parameters
| Parameter | Type | Required |
|---|---|---|
page | integer | No |
limit | integer | No |
Success response example
{
"success": true,
"errors_message": null,
"data": {
"referral_revenue": 2500,
"transactions": {
"data": [
{
"id": 33,
"amount": 220,
"type": "affiliate_earning",
"created_at": "2026-04-20 10:15:00",
"title": "Referral revenue"
}
]
}
}
}POST /api/wallet/cashout
Create a payout request for the creator's full currently available balance.
The available balance must be at least 30.00 in the wallet's currency (since 2026-10-14). Below that the request is refused with payout_below_minimum and nothing moves; the minimum itself goes through. GET /api/wallet says so beforehand: cashout_available is false below payout_minimum. Refusals come in this order: payout_account_required, the agency block (payout_blocked_reason), then payout_below_minimum; an empty wallet answers 400 "No available funds".
- Auth required: Yes
- Roles: Authenticated creator with wallet
- Content type: no body required
Request body
No request body is currently required.
Success response example
{
"success": true,
"errors_message": null,
"data": "Cashout created successfully"
}Error examples
{
"success": false,
"errors_message": "No available funds",
"data": null
}Without a saved payout account (since 2026-09-27; see Users):
{
"success": false,
"errors_message": "Add your payout account under Settings before requesting a payout.",
"errors_code": "payout_account_required",
"data": null
}Below the minimum payout (since 2026-10-14):
{
"success": false,
"errors_message": "The minimum payout is 30.00 USD.",
"errors_code": "payout_below_minimum",
"data": null
}Side effects
- backend creates payout state from current wallet
- current implementation uses the wallet service and does not ask the API client for a payout amount
Transaction behavior
Current public wallet responses expose transaction items with:
| Field | Type |
|---|---|
id | integer |
amount | integer |
type | string |
created_at | string |
title | string or null |
Do not assume every internal payout/admin transaction type is exposed publicly.
Related
- Auth (batch login and app sessions)
- Affiliate
- Wallet & Payouts