Skip to content

Products

Creator product and media management.

This surface covers:

  • paginated content library reads
  • product creation from upload sessions
  • legacy direct uploads
  • sales-link metadata updates
  • folder assignment
  • collection membership
  • media deletion
  • product deletion

Product resource

Important fields returned by product endpoints:

FieldTypeNotes
idintegerProduct id.
typestringProduct media type inferred from primary media.
titlestring or nullProduct title.
previewstring or nullPreview URL generated by backend storage resolution.
preview_blurredstring or nullBlurred preview URL.
priceintegerPrice in minor units, for example cents.
in_collectionbooleanCurrent collection membership flag.
linkstring or nullPublic sales link.
link_clicksinteger or nullSales-link view count.
unlocksintegerSuccessful purchase count.
total_earningsintegerTotal earnings in minor units.
folder_idstring or nullAssigned content folder id.
folderobject or null{ id, name }.
mediaarrayArray of media resources.
is_adult_contentbooleanAVS flag.
is_verif_agebooleanBuyer age-check flag.
is_epoch_enabledbooleanCreator/provider state.
is_should_consentbooleanThird-party consent required flag.
is_downloadablebooleanWhether buyers can download.
private_descriptionstring or nullReturned only to the product owner.
public_descriptionstring or nullBuyer-visible description.

Media resource

Each media[] item currently includes:

FieldTypeNotes
idintegerMedia id.
typestringimage, video, etc.
previewstringPreview URL.
preview_blurredstringBlurred preview URL.
veriff_statusstringHuman-readable moderation label such as Unchecked, Approved, AI Removal, or Manual Approval.
removal_descriptionstring or nullAdmin/moderation reason if present.
sourcestring or nullOriginal media URL, only exposed when the current user can update the product.

GET /api/products

Paginated content library endpoint for the authenticated creator.

  • Auth required: Yes
  • Roles: Authenticated creator

Query parameters

ParameterTypeRequiredNotes
pageintegerNoPage number.
limitintegerNoItems per page.
sort_bystringNonewest, most_earnings, most_views, collection.
folder_idintegerNoFilter to one folder.
folder_statestringNoCurrently documented and implemented special value: unassigned.
filterFolderstringNoLegacy compatibility filter. Accepts folder id or unassigned.

Success response example

json
{
  "success": true,
  "errors_message": null,
  "data": {
    "pages_total": 3,
    "collection_link": "https://fangate.info/AbCdEf1234",
    "data": [
      {
        "id": 5155,
        "type": "image",
        "title": "Campaign set",
        "preview": "https://fangate.info/storage/users/7/products/preview.png",
        "preview_blurred": "https://fangate.info/storage/users/7/products/preview-blurred.png",
        "price": 4400,
        "in_collection": false,
        "link": "https://fangate.info/94w24rWk3v",
        "link_clicks": 0,
        "unlocks": 0,
        "total_earnings": 0,
        "folder_id": "12",
        "folder": {
          "id": "12",
          "name": "Campaigns"
        },
        "media": [],
        "is_adult_content": true,
        "is_verif_age": false,
        "is_epoch_enabled": true,
        "is_should_consent": false,
        "is_downloadable": true,
        "private_description": null,
        "public_description": ""
      }
    ]
  }
}

Pagination and count behavior

Current backend returns:

  • pages_total
  • collection_link
  • current page results in data.data

Current backend does not expose separate totalCount, allItemsCount, or folderCounts fields on this API response.

If frontend clients need those additional totals, they must compute them elsewhere or wait for a dedicated backend extension.


GET /api/products/collection

Return the creator's products that are currently part of the shareable collection.

  • Auth required: Yes
  • Roles: Authenticated creator
  • Query params: page, limit

Response shape matches GET /api/products.


GET /api/products/

Return a single product visible to the authenticated creator.

  • Auth required: Yes
  • Roles: Product owner / authorized creator

Path parameters

ParameterTypeRequired
productintegerYes

Success response example

json
{
  "success": true,
  "errors_message": null,
  "data": {
    "id": 5155,
    "type": "video",
    "title": "My Product",
    "preview": "https://...",
    "preview_blurred": "https://...",
    "price": 4400,
    "in_collection": false,
    "link": "https://fangate.info/94w24rWk3v",
    "link_clicks": 0,
    "unlocks": 0,
    "total_earnings": 0,
    "folder_id": null,
    "folder": null,
    "media": [
      {
        "id": 7792,
        "type": "video",
        "preview": "https://...",
        "preview_blurred": "https://...",
        "veriff_status": "Manual Approval",
        "removal_description": null,
        "source": "https://..."
      }
    ],
    "is_adult_content": true,
    "is_verif_age": false,
    "is_epoch_enabled": true,
    "is_should_consent": false,
    "is_downloadable": true,
    "private_description": null,
    "public_description": ""
  }
}

POST /api/products

Create a new product or attach media to an existing draft.

  • Auth required: Yes
  • Roles: Authenticated creator
  • Content type: multipart/form-data

There are two active creation flows.

Preferred flow: upload sessions

Send a previously finalized upload session:

FieldTypeRequiredNotes
upload_session_idstringYes for this flowSession must belong to current user and be ready to attach.
priceintegerYes when creating a new productPrice in minor units.
product_idintegerNoAttach to an existing product.
titlestringNoOptional.
is_adult_contentbooleanNoDefaults from user settings if omitted.
is_verif_agebooleanNoDefaults from user settings if omitted.
is_should_consentbooleanNoDefaults to false.
private_descriptionstringNoCreator-only notes.
public_descriptionstringNoBuyer-visible description.

Legacy flow: direct multipart upload

Send a new file directly:

FieldTypeRequiredNotes
mediafileYes for this flowLegacy direct upload.
priceintegerYes when creating a new productMinimum current rule is 500 minor units.
product_idintegerNoAttach to an existing draft product.
titlestringNoOptional.
extensionstringNoLegacy helper.
is_adult_contentbooleanNoOptional.
is_verif_agebooleanNoOptional.
is_should_consentbooleanNoOptional.
private_descriptionstringNoOptional.
public_descriptionstringNoOptional.

Important validation and side effects

  • If product_id is present, product ownership is enforced.
  • Large direct uploads are accepted and queued asynchronously via CreateProductJob.
  • If upload sessions are used, attachment is handled by UploadAttachmentService.
  • After product creation, moderation dispatch runs when either:
    • creator has skip_moderation, or
    • SightEngine moderation is enabled

Success / accepted responses

Immediate success:

json
{
  "success": true,
  "errors_message": null,
  "data": {
    "id": 5155,
    "link": "https://fangate.info/94w24rWk3v"
  }
}

Async accepted for larger direct uploads:

json
{
  "success": true,
  "errors_message": null,
  "data": {
    "id": 5155
  }
}

Error examples

Chunk still uploading:

json
{
  "success": true,
  "errors_message": null,
  "data": {
    "upload_percentage": 64
  }
}

Media processing failure:

json
{
  "success": false,
  "errors_message": "Media service error",
  "data": null
}

File too large for legacy path:

json
{
  "success": false,
  "errors_message": "File size too large",
  "data": null
}

PATCH /api/products/

Update product metadata.

  • Auth required: Yes
  • Roles: Product owner
  • Content type: application/json

Request body

FieldTypeRequired
titlestringNo
is_adult_contentbooleanNo
is_verif_agebooleanNo
is_should_consentbooleanNo
is_downloadablebooleanNo
private_descriptionstringNo
public_descriptionstringNo

Important notes

  • This endpoint updates metadata only.
  • It does not replace media.
  • It does not update the price.

Price Editing

Creators can change the price of an existing sales link in two explicit ways:

  1. update the current link's price
  2. create a new link with a different price while keeping the original link unchanged

This split is intentional and avoids ambiguous behavior when a link already has traffic or active checkout sessions.

Data model and scope

  • A product remains the media/content entity.
  • URL links are stored separately in short_links, but current checkout price is still sourced from products.price.
  • "Create new link with different price" is implemented by duplicating the product row (same media references, new product id, new short link).
  • Title and descriptions can be overridden in the duplicate flow; if omitted, source values are kept.

Checkout and validation behavior

  • Price must be revalidated server-side at checkout start.
  • Sessions that already started checkout before a price update keep their originally locked price.
  • A later price update does not force-invalidate those already pending checkout sessions.

Analytics behavior

  • Analytics are tracked per link.
  • Future events use the link/price active at event time.
  • Historical statistics remain based on prior prices and are not retroactively recomputed.

Concurrency and compatibility

  • Standard optimistic concurrency (last successful write wins) is acceptable for this ticket.
  • Existing clients keep current behavior until they adopt the dedicated price-edit endpoints below.

PATCH /api/products/{product}/price

Update the price on the existing active sales link for a product.

  • Auth required: Yes
  • Roles: Product owner
  • Content type: application/json

Request body

FieldTypeRequiredNotes
priceintegerYesNew price in minor units.

Example request

json
{
  "price": 1000
}

Success response example

json
{
  "success": true,
  "errors_message": null,
  "data": {
    "id": 5155,
    "link": "https://fangate.info/94w24rWk3v",
    "price": 1000
  }
}

Notes

  • Applies only to the existing link associated with the product.
  • Does not create a new link id/slug.
  • New checkout sessions use the updated price after server-side revalidation.

POST /api/products/{product}/price-links

Create a new priced link by duplicating the current product and assigning a new short link.

  • Auth required: Yes
  • Roles: Product owner
  • Content type: application/json
  • Success status: 201 Created

Request body

FieldTypeRequiredNotes
priceintegerYesPrice in minor units for the new link.
titlestringNoOptional new link display title override.
private_descriptionstringNoOptional creator-facing note override.
public_descriptionstringNoOptional buyer-facing description override.

Example request

json
{
  "price": 1200,
  "title": "Campaign set - promo",
  "public_description": "Limited drop price"
}

Success response example

json
{
  "success": true,
  "errors_message": null,
  "data": {
    "id": 6201,
    "title": "Campaign set - promo",
    "link": "https://fangate.info/AbCdEf1234",
    "price": 1200
  }
}

Notes

  • Creates a new product record with a new short link and new price.
  • Existing source product and link continue to work at their original price.
  • Media rows are duplicated without re-upload; storage files are shared until one side is no longer referenced.
  • Existing in-flight checkouts on older links continue with their original locked price.

PATCH /api/products/{product}/folder

Assign or unassign one product to a content folder.

  • Auth required: Yes
  • Roles: Product owner
  • Content type: application/json

Request body

Assign:

json
{
  "folder_id": 12
}

Unassign:

json
{
  "folder_id": null
}

Validation

  • if a folder id is provided, it must belong to the authenticated creator
  • null is valid and removes folder assignment

POST /api/products/{product}/collection

Toggle collection membership for a product.

  • Auth required: Yes
  • Roles: Product owner

Side effects

  • if the creator does not yet have a collection, backend creates one
  • if the collection has no short link, backend creates that too
  • calling again removes the product from the collection

DELETE /api/products/media/

Delete one media row and remove its stored files.

  • Auth required: Yes
  • Roles: Media owner / authorized product owner

Side effects

  • deletes media DB row
  • deletes media, preview, preview_blurred, and thumbnail paths only when not referenced by any other media rows
  • returns the parent product resource

DELETE /api/products/

Delete a product.

  • Auth required: Yes
  • Roles: Product owner

Side effects

  • deletes related media rows; storage objects are deleted only when no other media rows reference the same paths
  • deletes related consent records and consent requests
  • deletes the short link
  • deletes the product

Success response example

json
{
  "success": true,
  "errors_message": null,
  "data": "deleted"
}

  1. upload media through Upload Sessions or legacy direct upload
  2. POST /api/products
  3. optional PATCH /api/products/{product} to update descriptions or toggles
  4. optional POST /api/products/{product}/collection
  5. frontend reads link from the product resource and exposes the sales link

Price rules

  • price is stored in minor units
  • minimum current validation is 500
  • backend validation currently enforces integer pricing rather than decimal strings

Fangate backend developer documentation