Error Codes
Every JSON error under /api carries a machine-readable errors_code. Branch on the code, never on errors_message - messages are written for people and get reworded; a code that was published here is not renamed or reused.
{
"success": false,
"errors_message": "The price must be at least 500.",
"errors_code": "price_below_minimum",
"errors_field": "price",
"data": null
}How a code is chosen:
- A named code where the API knows the exact reason (tables below).
- Otherwise the fallback for the HTTP status (
forbidden,not_found, ...), so there is always something to branch on.
errors_field accompanies validation failures: the first field that failed, with list positions written as * (media_consents.*.tagged_user_id).
The field is additive. Message, status code and every other field of an error are unchanged, and a client that ignores errors_code keeps working.
Fallback codes
errors_code | HTTP | Meaning |
|---|---|---|
validation_failed | 422 | A request field failed validation and no named code applies. errors_field names it, errors_message says why. |
unprocessable | 422 | The request was well-formed but refused by a business rule that has no named code yet. |
bad_request | 400 | Malformed request. |
unauthenticated | 401 | Missing, expired or revoked token / API key. |
forbidden | 403 | Authenticated, but the resource belongs to another account or the action is not allowed. |
not_found | 404 | No such route or record. |
method_not_allowed | 405 | The route exists, not for this HTTP method. |
conflict | 409 | The resource is in a state that does not allow the call (for example still processing). |
gone | 410 | The resource existed and was removed. |
payload_too_large | 413 | Body or file above the accepted size. |
locked | 423 | The resource is locked. |
rate_limited | 429 | Too many requests - wait for Retry-After seconds (see Error handling). The body is Laravel's plain { "message": ..., "errors_code": "rate_limited" }. |
server_error | 5xx | Fault on Fangate's side. Safe to retry with backoff. |
request_failed | other 4xx | Any other client error. |
API keys
errors_code | HTTP | Meaning |
|---|---|---|
api_key_endpoint_not_allowed | 403 | Generated API keys cannot call this endpoint at all (account settings, payouts, key management ...). Use the creator's login token. See API Keys. |
api_key_permission_missing | 403 | The endpoint is open to keys, but this key was created without the permission it needs (can_create_new_urls, can_view_transactions, can_manage_webhooks). Create a key with that permission. |
webhook_limit_reached | 422 | The account already holds 15 webhooks. Remove one before registering another. |
api_key_limit_reached | 422 | The agency account already holds 100 generated keys that are neither revoked nor expired. Revoke one before creating another. |
api_key_read_only | 403 | The key was created with read_only: true and the call is not a GET. Use a key without read_only for writes. |
api_key_scope_requires_account | 403 | The key is held to a list of accounts (account_keys) and called an agency-wide money view (/api/wallet, /api/dashboard/summary, /api/analytics/*) without X-Fangate-Account. Name one of the key's accounts. |
Products and prices
errors_code | HTTP | Where |
|---|---|---|
price_below_minimum | 422 | POST /api/products, PATCH /api/products/{product}/price, POST /api/products/{product}/price-links - a price under the floor of 500 minor units. Read the floor from GET /api/app-data → products.min_price_cents. |
product_text_too_short | 422 | POST /api/products, PATCH /api/products/{product}, POST /api/products/{product}/price-links - from 27 September 2026: neither title nor public_description has 15 characters (one of the two is enough; spaces at the ends do not count). Reported on public_description. A call that only attaches a file (product_id) or changes neither text is never refused for this. See POST /api/products. |
card_picture_unavailable | 409 | GET /api/products/{product}/telegram-card - the card picture cannot be made yet, usually right after the upload, before the product's blurred preview exists. Try again later. See Products. |
agency_account_key_invalid | 422 | X-Fangate-Account carries something that is not an account_key - a bare id, for one. Read the keys from GET /api/agencies/me/accounts. See Agency accounts. |
agency_account_not_found | 404 | X-Fangate-Account names no active account of this agency (unknown, invitation still open, relationship ended, AI model deactivated), or the two numbers in the key do not belong together. |
agency_api_access_revoked | 403 | The creator switched off API access for the agency. The account cannot be worked in with the agency's key - or with a key the agency created on it - until the creator switches it back on. Also answered when such a delivery is asked to be sent again. |
agency_upload_not_allowed | 403 | The creator switched off uploads by the agency. New files are refused; everything else in the account goes on. |
transaction_not_refundable | 422 | POST /api/transactions/{transaction_id}/refund-request - the sale is not paid or was already reversed. |
refund_request_window_closed | 422 | Same call - the sale is more than 30 days old. |
refund_request_exists | 422 | Same call - a refund was already requested for this sale (one per sale). |
agency_invitation_limit_reached | 403 | An API key invited a creator, but the agency has sent 125 invitations in total. Contact support to have the limit raised; the dashboard is not held to it. |
model_email_taken | 422 | POST /api/agencies/me/models named an address that already belongs to a Fangate account. Invite that person as a creator, or tag them in your content. See Model accounts. |
model_release_pending | 403 | An upload into a model account whose release file Fangate has not approved yet. Nothing can be uploaded before the approval. |
model_link_expired | 410 | The model's one-time link is unknown, used or older than 7 days. The model asks for a new one on her page. |
model_login_failed | 401 | The model's e-mail and password did not match, no password is set yet, or the model account is closed. One answer for every case. |
model_session_scope | 403 | A model-page session called an endpoint outside the model page. That session reaches the model's own page only. |
content_display_consent_required | 422 | POST /api/register without content_display_consent: true while the admin switch requires it, or with false at any time (see Auth). |
email_not_confirmed | 422 | POST /api/register and POST /api/agencies/register - the confirmation mail for this address was never opened. Send it with POST /api/user/email/verify, wait for the click (GET /api/registration/email-status), then register. See Onboarding. |
origin_not_allowed | 403 | POST /api/login, POST /api/login/batch, POST /api/register, POST /api/agencies/register - the call came from a web page or a server that is not registered with Fangate. Register your pages and servers: What we need from you. |
current_password_required | 422 | PATCH /api/user/password with a login token, and PATCH /api/user/{id} when the payout details (bank_data, bank_data_by_currency, payout_method, tron_usdt_wallet) change: send the account's current password as current_password. |
current_password_invalid | 422 | current_password was sent but is not the account's password. |
agency_document_not_needed | 422 | POST /api/agencies/me/documents with an API key: a key files only a required document that is still missing (missing_documents in GET /api/agencies/me/verification) or the answer to an open document request, never other. |
agency_document_key_limit_reached | 422 | POST /api/agencies/me/documents with an API key: this key has uploaded 10 documents, the most it may; ask Fangate if more are needed. |
payout_account_required | 422 | POST /api/wallet/cashout for an account without a saved payout account (see Users). |
payout_below_minimum | 422 | POST /api/wallet/cashout with an available balance under 30.00 in the wallet's currency (since 2026-10-14). An empty wallet still answers 400 "No available funds". See Wallet. |
ai_model_owner_verification_required | 422 | Uploading to, or selling through, a creator's own AI model while the creator behind it is not verified, or the model is deactivated. requires_verification is false: the creator verifies in the creator's own account, never in the model. See AI models of a creator. |
creator_verification_required | 422 | Creating a product or uploading a file before the creator's identity verification is complete. data carries requires_verification and the Veriff session (see Error handling); data.error_code holds the same value for older clients. |
AI content
errors_code | HTTP | Where |
|---|---|---|
ai_origin_required | 422 | POST /api/media, POST /api/products - a new file without its AI classification (see AI content). |
ai_attestation_required | 422 | Same calls and PATCH /api/media/{media}/ai-origin - an AI value without ai_attested: true. |
Upload sessions
errors_code | HTTP | Meaning |
|---|---|---|
upload_session_forbidden | 403 | The upload session belongs to another account. |
upload_session_not_ready | 409 | The session is not finalized yet - poll GET /api/upload-sessions/{id} until status is ready, then attach it. |
uploaded_asset_not_ready | 409 | The file is still being processed. Same remedy. |
uploaded_asset_already_attached | 409 | The uploaded file is already attached elsewhere. |
uploaded_asset_already_bound_to_media | 409 | The file is product media and cannot be reused as a public preview asset. |
uploaded_asset_kind_mismatch | 422 | The file type does not fit the slot (for example a video where an image is expected). |
upload_already_completed | 409 | The session was already completed. |
upload_aborted | 409 | The session was aborted - start a new one. |
upload_expired | 410 | The session expired (24 hours) - start a new one. |
upload_size_limit_exceeded | 422 | The file is above the upload size limit. |
uploads_staging_bucket_missing | 500 | Storage misconfiguration on Fangate's side. |
Why a product cannot be bought: purchase_block_code
Not an error of your call, but the state of a product: GET /api/products/{product} carries purchase_block_code (and is_accessible: false) while the product cannot be sold. Subscribe to the product.sellable webhook event to learn when the last block is gone, and to product.blocked to be told when a product cannot be sold because its content was rejected or a required consent is not approved - its reason (automatically_rejected_manual_review, manually_rejected, missing_consent, sales_stopped) comes with the purchase_block_code of this table (removed_by_fangate arrives as manually_rejected).
purchase_block_code | Meaning | What ends it |
|---|---|---|
moderation_not_approved | The content check of a file is still running or the file was rejected. | Automated moderation, usually minutes. |
manual_review_pending | A file waits for manual review by the Fangate team (see Content moderation). | Fangate's review; at most 24 hours for videos above 2.5 GB. |
price_review_pending | The price is above the compliance threshold (GET /api/app-data → products.manual_price_review_threshold_cents) and waits for approval. | Fangate's approval. |
consent_recipient_missing | Consent is required and the person who has to consent is not selected yet. | The creator tags the person. |
consent_pending | A tagged creator has not answered the consent request yet. | The tagged creator accepts. |
consent_declined | A tagged creator declined. | Remove the file or the tag. |
consent_rescinded | A consent that was given has been withdrawn. | Remove the file or the tag. |
ai_rail_unavailable | The product holds AI content and the AI storefront is not available for it. | Fangate's configuration. |
media_unavailable | No deliverable file is attached. | Attach a file. |
sales_stopped | The seller took the product off sale (sales_stopped or a passed sales_end_at). | PATCH /api/products/{product_id} with sales_stopped: false and sales_end_at: null. |
removed_by_fangate | Fangate took the product off sale. The files are untouched; buyers who already bought keep their access. | Fangate restores it - the seller's switches do not. |