API Keys
Generate, list, and revoke creator API keys for external integrations.
Base URLs:
- Production:
https://fangate.info/api
Overview
Current scope includes only:
- create key
- list keys
- revoke key
Not included for now:
- rotate key
- rename key
- expiry scheduler/cleanup job
Authentication uses bearer tokens:
Authorization: Bearer <api_key>
Generated keys are stored hashed by Sanctum and the plaintext token is shown only once at create time.
Where to create a key. Creators create, list and revoke keys in the Fangate dashboard under Settings → API keys (app.fangate.info): pick the permissions, the key is shown once with a copy button, and the list shows last_used_at and a revoke button. No password has to leave the app for that. The POST /api/user/api-keys route below does the same for tools that manage keys programmatically - it takes the creator's login token, never a generated key. Agency accounts do not have this page: they operate the creator accounts they are connected to and create keys there.
Scope matrix
Fixed behavior:
get_existing_urls-> always ONget_existing_collection_urls-> always ONview_account_settings-> always OFF (hard blocked)create_new_urls-> toggleview_transactions-> togglemanage_webhooks-> toggleupload_ai_content-> followscreate_new_urls, not a separate toggle: every key that can create content also has to classify every new file it uploads (ai_origin, see AI content). Keys created before this rule do not carry the ability until the announced cut-over.
Endpoint permission matrix
Generated API keys are whitelist-only. Abilities: urls.read and collection-urls.read are always on; urls.write = create_new_urls; transactions.read = view_transactions; webhooks.manage = manage_webhooks.
| Endpoint | Method | Required ability |
|---|---|---|
/api/products | GET | api-key:urls.read |
/api/products/collection | GET | api-key:collection-urls.read |
/api/products/{product} | GET | api-key:urls.read |
/api/products/{product}/consent-creator-tags | GET | api-key:urls.read |
/api/products/{product}/telegram-card | GET | api-key:urls.read |
/api/products | POST | api-key:urls.write (new file: ai_origin required for keys with upload_ai_content) |
/api/products/{product} | PATCH / DELETE | api-key:urls.write |
/api/products/{product}/price, /folder | PATCH | api-key:urls.write |
/api/products/{product}/price-links, /collection, /thumbnail, /trailer, /media | POST | api-key:urls.write |
/api/products/{product}/thumbnail, /trailer, /media/{media} | DELETE | api-key:urls.write |
/api/products/media/{media} | DELETE | api-key:urls.write |
/api/media | GET | api-key:urls.read |
/api/media/{media}, /api/media/{media}/consent-tags | GET | api-key:urls.read |
/api/media, /api/media/bulk, /api/media/{media}/consent-tags | POST | api-key:urls.write (POST /api/media: ai_origin required for keys with upload_ai_content) |
/api/media/{media}, /api/media/{media}/ai-origin | PATCH | api-key:urls.write |
/api/media/{media} | DELETE | api-key:urls.write |
/api/upload-sessions* | GET/POST/DELETE | api-key:urls.write |
/api/content-folders* | GET | api-key:urls.read |
/api/content-folders* | POST/PATCH/DELETE | api-key:urls.write |
/api/wallet, /api/wallet/affiliate, /api/dashboard/summary | GET | api-key:transactions.read |
/api/analytics/sales-by-country, /api/analytics/top-buyers | GET | api-key:transactions.read |
/api/consent-tags, /api/consent-tag-relationships, /api/consent-tag-permissions | GET | api-key:urls.read (the relationship lists withhold other creators' email unless ?q= names 4+ characters of their address, name or stage name, see Consents) |
/api/consent-tags/{consentCreatorTag}, /api/consent-tags/{consentCreatorTag}/rescind | PATCH | api-key:urls.write (accepting, declining or rescinding is a declaration in the creator's name) |
/api/consent-tag-permissions/{ownerUserId} | PATCH | api-key:urls.write |
/api/creators/search | GET | api-key:urls.write |
/api/consents/store-or-send | POST | api-key:urls.write |
/api/veriff/decision | POST | any key (api-key:urls.read; a read_only key cannot, it may not POST) - see Verification |
/api/veriff/create | GET | api-key:urls.write, and only after a final negative verdict - see Verification |
/api/moderation/check | POST | api-key:urls.write (word-filter pre-check of title and public_description - the filter every product create and update runs; no images or videos) |
/api/webhooks, /api/webhooks/{id}, /api/webhooks/{id}/secret | GET/POST/PATCH/DELETE | api-key:webhooks.manage |
Any other route returns 403 with errors_code: api_key_endpoint_not_allowed ("This API key cannot access this endpoint."); a whitelisted route without the ability returns 403 with errors_code: api_key_permission_missing ("This API key does not have permission for this action."). Branch on the code - see Error codes.
Deliberately closed for keys. Account settings answer 403 with "Account settings are not available for generated API keys.": everything under /api/user - profile, password, sessions, linked accounts, Telegram, personal-message settings, fan messages, push tokens and key management itself (/api/user/api-keys) - plus video chat. Not on the whitelist, 403 with "This API key cannot access this endpoint.": payouts (/api/wallet/cashout), the analytics CSV export and PDF report (they carry buyer email addresses), the affiliate endpoints, the collection profile routes (/api/collection/profile* - the collection list GET /api/products/collection is open). Keys are created and revoked with a creator login token only.
Rate limits
API key management endpoints use combined limits:
- Per token (fallback user) + IP
- Per IP guardrail
Current limits:
| Endpoint | Per token/user + IP | Per IP |
|---|---|---|
POST /api/user/api-keys | 10/min | 30/min |
GET /api/user/api-keys | 30/min | 60/min |
DELETE /api/user/api-keys/{tokenId} | 30/min | 60/min |
GET /api/user/api-keys
List generated API keys for the authenticated creator.
- Auth required: Yes
Example response
{
"success": true,
"errors_message": null,
"data": [
{
"id": 14,
"name": "fangate-api-key:zapier",
"last_used_at": "2026-04-25T08:30:00Z",
"expires_at": null,
"created_at": "2026-04-25T08:00:00Z",
"abilities": [
"api-key:urls.read",
"api-key:collection-urls.read",
"api-key:urls.write",
"api-key:ai.write"
],
"read_only": false,
"account_keys": null,
"created_by_agency": null
}
]
}read_only is true for a key created with read_only. account_keys is the list of accounts an agency key is held to, or null when it reaches every account (always null on a creator account).
Keys created by an agency
An agency that operates your account (agency accounts, since 2026-09) can create API keys on it. Such keys are labelled so you can tell them from your own:
| Field | Type | Notes |
|---|---|---|
created_by_agency | object or null | null for keys you created yourself |
created_by_agency.id | integer | Agency id |
created_by_agency.name | string | Agency trading name, or legal name if none |
created_by_agency.link_id | integer | The agency relationship the key belongs to |
created_by_agency.link_active | boolean | false once the relationship has ended |
When the agency relationship ends (by you, the agency, or Fangate), every key the agency created on your account is deleted immediately. Your own keys are not affected. The revocation appears in your agency activity log.
POST /api/user/api-keys
Create a generated API key.
- Auth required: Yes
- Content type:
application/json
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes | Label used for token name prefix fangate-api-key: |
can_create_new_urls | boolean | No | Enables api-key:urls.write and api-key:ai.write (the key must classify every new file, see AI content) |
can_view_transactions | boolean | No | Enables api-key:transactions.read |
can_manage_webhooks | boolean | No | Enables api-key:webhooks.manage |
can_manage_ai_models | boolean | No | Agency and creator accounts (ignored on an AI model's own account). Enables api-key:agency.ai-models: list, create, rename and deactivate the account's AI models - an agency's under /api/agencies/me/ai-models, plus its model accounts; a creator's under /api/user/ai-models (since 2026-10-12) |
can_access_connected_creators | boolean | No | Agency accounts only (ignored elsewhere). Enables api-key:agency.creators: work inside connected creators with X-Fangate-Account and invite creators. Without it the key reaches the agency's AI models and model accounts only |
read_only | boolean | No | A key that only reads: minted with api-key:read-only and without any write ability (can_create_new_urls and can_manage_webhooks are ignored). Every method but GET answers 403 api_key_read_only. See Account-scoped and read-only keys |
account_keys | array of strings or null | No | Agency accounts only (ignored elsewhere). Holds the key to these accounts: account_key values from GET /api/agencies/me/accounts (invited or active relationships, max 100). Absent or null: the key reaches every account of the agency, as before. A value that is not one of your relationships answers 422 validation_failed with errors_field: account_keys, and no key is created |
expires_in_days | integer | No | Optional explicit expiry |
A generated key cannot create, list or revoke keys - use the dashboard (Settings → API keys) or the creator's login token (POST /api/login) for this call.
An agency account holds at most 100 generated keys at once; the next one answers 422 api_key_limit_reached. Revoked and expired keys do not count - revoke one to free its place.
Agencies. An agency creates its key on the agency account and names the account it works in with the X-Fangate-Account header - see Agency accounts. The permissions above say what the key may do inside every account it reaches.
Creators with AI models (since 2026-10-12). A creator's key names one of the creator's own AI models the same way: X-Fangate-Account: aim_<id>, the account_key from GET /api/user/ai-models. The call then runs as the model, within the key's permissions.
E-mail addresses. A generated key is handed no e-mail address of a third person: email is null in GET /api/creators/search, in the tagged and tagging person of the consent reads (GET /api/media/{media}/consent-tags, GET /api/products/{product}/consent-creator-tags, GET /api/consent-tags) and for the Fangate staff named there, and no display name falls back to an address. Searching by a full address still finds the account. invited_email stays - the account entered it itself.
Example request
curl -X POST https://fangate.info/api/user/api-keys \
-H "Authorization: Bearer <creator-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "zapier",
"can_create_new_urls": true,
"can_view_transactions": false,
"expires_in_days": 90
}'Example response
{
"success": true,
"errors_message": null,
"data": {
"token": "1|plain-text-token",
"name": "fangate-api-key:zapier",
"expires_at": "2026-07-24T08:00:00Z",
"read_only": false,
"account_keys": null,
"permissions": {
"get_existing_urls": true,
"get_existing_collection_urls": true,
"create_new_urls": true,
"view_transactions": false,
"manage_webhooks": false,
"upload_ai_content": true,
"view_account_settings": false
}
}
}upload_ai_content mirrors create_new_urls. In the key list, such keys carry api-key:ai.write in abilities (as in the example above); keys created before the AI release do not, until a cut-over date that will be announced in the changelog.
Account-scoped and read-only keys
Since 2026-09-23 an agency can hand a partner a key that reaches some of its accounts instead of all of them, and any account can create a key that only reads. Both are decided when the key is created; the account list can be changed later.
Account list (account_keys). The key works only in the listed accounts:
X-Fangate-Accountnaming an account off the list answers404 agency_account_not_found- exactly like an account the agency does not run.GET /api/agencies/me/accountsandGET /api/agencies/me/ai-modelslist the key's accounts only;PATCH/DELETEon an AI model off the list answer404.- An AI model or model account the key creates, and a creator it invites, are added to its list automatically.
- The agency's own money views add up every account, listed or not:
GET /api/wallet,GET /api/wallet/affiliate,GET /api/dashboard/summaryandGET /api/analytics/*answer403 api_key_scope_requires_accountunless the key names one of its accounts withX-Fangate-Account- the account's own wallet is fine. - Agency webhooks: a scoped key registers one only with an
account_keyslist inside its own list (Agency-wide webhooks); it sees, changes, removes, redelivers and rotates only those. A webhook without a list hears every account of its scopes and is closed to the key (422on register,403 api_key_scope_requires_accountotherwise). Or register the webhook on the account itself, with the header. PATCHreplaces the list with the accounts you name. Invitations the key holds to an address that has not registered yet have noaccount_key, so they cannot be named - they stay on the list; withdrawing the invitation removes them. A declined invitation leaves every list.- When a relationship ends, the account drops out of every key's list. A key whose list ran empty reaches no account until the list is changed.
- Taking accounts off a key's list takes them off the webhooks that key registered: they drop out of its agency webhooks (a webhook left with no account is switched off; one without a list keeps to the key's new list), and webhooks it registered inside a removed account are switched off. Ended accounts stay on a webhook's list, so a refund of an earlier sale still arrives.
- A key created without
account_keysreaches every account of the agency, as every key did before this date.
Read-only (read_only: true). The key carries api-key:read-only and no write ability. Every method but GET answers 403 api_key_read_only, on every route - including inviting creators and registering webhooks. What it may read is still decided by the other permissions (can_view_transactions for wallet and analytics, can_access_connected_creators for reaching connected creators) and by its account list.
PATCH /api/user/api-keys/
Change the account list of an agency key. Agency accounts only; like every call on this page it takes a login token, never a generated key.
- Auth required: Yes
- Content type:
application/json
| Field | Type | Required | Notes |
|---|---|---|---|
account_keys | array of strings or null | Yes | The new list (1-100 account_key values of invited or active relationships), or null to let the key reach every account again |
{
"success": true,
"errors_message": null,
"data": {
"id": 14,
"account_keys": ["conn_418_90211", "conn_419_90212"]
}
}404 for a key that is not yours or not a generated key; 403 on a creator account; 422 validation_failed (errors_field: account_keys) for a value that is not one of your relationships - the list is then left unchanged.
DELETE /api/user/api-keys/
Revoke a generated API key by token id. For a key of an agency account, and for a key an agency created on your account, the webhooks the key registered are switched off with it (is_active: false), not deleted: they stay listed under GET /api/webhooks, where you can switch one back on or remove it. A creator's own key takes nothing with it - its webhooks keep running. A delivery that was queued before a webhook was switched off is not sent; it can be sent again once the webhook is back on.
- Auth required: Yes
Example response
{
"success": true,
"errors_message": null,
"data": "API key revoked"
}Security notes
- Keep the key on your server. A key placed in browser or app code can be read by every user of your application; only presigned upload URLs belong in a browser (see Upload sessions).
- API key plaintext is returned once on creation only.
- Hashed storage + one-time display is considered acceptable for current phase.
last_used_atfrom Sanctum is used as minimal audit trail for now.- Single auth format is used for API keys:
Authorization: Bearer <api_key>. - No key-expiry scheduler is configured for now.