---
title: "Publish an experience for this creator"
method: POST
path: "/experiences"
---

# Publish an experience for this creator

`POST /experiences`

## Request

- Base URL: `https://api.fanvue.com`
- URL: `https://api.fanvue.com/experiences`
- Auth: OAuth 2
  - Authorization code: `https://auth.fanvue.com/oauth2/auth`
  - Token URL: `https://auth.fanvue.com/oauth2/token` scopes: `write:experience`

Publishes an experience for the creator whose token is calling, without the confirmation dialog: your app declares the whole experience — type, access mode, price and priced actions included — and Fanvue writes it. The creator consented to this when they granted your app `write:experience`.

    Publishing the same `externalExperienceId` again for the same creator updates the existing experience. A republish that would take paid access away from fans (switching to `HIDDEN`, or hiding a published experience) answers 409 with the impact; retry with `acknowledgePaidImpact` and the impact echoed as `acknowledgedImpact` to proceed, unless fans hold live recurring access, which blocks it outright.

    Prefer `POST /request-token` when you want the creator to confirm or price the experience themselves in Fanvue's dialog.

## Headers

- `X-Fanvue-API-Version` string, required

## Request body

- object
  - `appUuid` string, uuid, required: Your app's UUID.
  - `externalExperienceId` string, required: Your own stable id for the experience. Publishing the same id again for the same creator updates the existing experience.
  - `title` string, required
  - `description` string, required
  - `imageUrl` string, uri, nullable: Public https URL of the cover image. Fanvue downloads and re-hosts it; one that cannot be fetched is dropped and the experience keeps no cover.
  - `accessMode` 'FREE' | 'SUBSCRIPTION' | 'PAID' | 'HIDDEN', required: `FREE`, `SUBSCRIPTION` (the creator's subscribers), `PAID` (needs `priceCents`) or `HIDDEN` (share-link only).
  - `deliveryMode` 'EMBEDDED' | 'EXTERNAL': `EMBEDDED` (default) renders your `fan_experience` surface on Fanvue; `EXTERNAL` opens `externalUrl`.
  - `externalUrl` string, uri, nullable: Where an `EXTERNAL` experience opens. Must be https (checked here). The platform additionally refuses Fanvue, localhost, IP and dev-tunnel hosts; such a URL answers 400 with a `message` naming the field.
  - `hidden` boolean: Keep the experience off the creator's profile. Implied by `HIDDEN`.
  - `priceCents` integer: Price in USD cents, between 300 and 50000.
  - `priceRecurring` boolean: With `priceCents`: bill monthly as an app subscription instead of a one-off unlock.
  - `experienceType` 'LIVE_STREAM' | 'LIVE_AUDIO' | 'VIDEO' | 'AUDIO' | 'GAME' | 'CHALLENGE' | 'QUIZ' | 'EVENT' | 'OTHER': What kind of experience this is. Decides how it presents itself where it is embedded (for example the category tag on a chat card). Omit for the generic `OTHER`.
  - `actions` object[]: Paid actions to sell inside the experience, each with its price. At most 10. Omit to leave the experience's actions untouched; an empty array withdraws every action you declared.
    - `externalActionId` string, required: Your own stable key for the action.
    - `title` string, required: Label the fan sees on the payment dialog.
    - `description` string, nullable
    - `priceCents` integer, required: Price in USD cents, between 300 and 50000.
  - `acknowledgePaidImpact` boolean: Set to `true` on a retry to accept the paid impact a previous attempt answered 409 with. Send the 409's `impact` alongside as `acknowledgedImpact`.
  - `acknowledgedImpact` object: The `impact` the previous 409 reported, echoed back verbatim.
    - `affectedFanCount` integer, required: Distinct fans holding paid access right now, across subscriptions and purchases.
    - `activeSubscriptionCount` integer, required: Subscriptions that still grant access. Any at all blocks the change outright.
    - `pendingSubscriptionInvoiceCount` integer, required: In-flight initial subscription charges. Block, like a settled subscription.
    - `oneTimePurchaseCount` integer, required: Distinct fans holding a settled one-off purchase. Needs acknowledging.
    - `pendingPurchaseCount` integer, required: In-flight one-off purchase attempts. Need acknowledging, like a settled purchase.

## Response `200`

The experience as published, with the type and price Fanvue recorded

- object
  - `experience` AppExperience, required
    - `uuid` string, uuid, required
    - `appUuid` string, uuid, required
    - `creatorUuid` string, uuid, required
    - `externalExperienceId` string, required: Your own stable id for the experience, as you published it.
    - `title` string, required
    - `description` string, required
    - `imageUrl` string, nullable, required: Public URL of the cover Fanvue stored, or `null` when none was kept (none sent, or the one sent could not be fetched).
    - `accessMode` 'FREE' | 'SUBSCRIPTION' | 'PAID' | 'HIDDEN', required
    - `experienceType` 'LIVE_STREAM' | 'LIVE_AUDIO' | 'VIDEO' | 'AUDIO' | 'GAME' | 'CHALLENGE' | 'QUIZ' | 'EVENT' | 'OTHER', required: What kind of experience this is; `OTHER` when nothing declared one.
    - `deliveryMode` 'EMBEDDED' | 'EXTERNAL', required
    - `externalUrl` string, nullable, required: Where an `EXTERNAL` experience opens; `null` for `EMBEDDED`.
    - `hidden` boolean, required: Share-link only, never listed on the creator's profile. Always `true` for `HIDDEN`.
    - `priceCents` integer, nullable, required: Price in USD cents behind a `PAID` experience; `null` for every other mode.
    - `priceRecurring` boolean, required: Whether that price bills monthly as an app subscription rather than once.
    - `publishedAt` string, nullable, required: When it was published (ISO 8601); `null` while unpublished.
    - `suspendedAt` string, date-time, nullable, required: When Fanvue removed it for a policy reason (ISO 8601); `null` for a live or merely unpublished experience. A suspended experience is not listed, cannot be launched and refuses a publish or update with `experience_suspended` until Fanvue reinstates it; keep your record of it rather than deleting it.
    - `reviewStatus` 'pending_review' | 'approved' | 'rejected', required: Where the experience stands in Fanvue's review. Publishing submits it for review (`pending_review`); fans see it once `approved`, while it is also published and not suspended. Editing the title, description, cover, delivery mode or external link of an approved experience keeps the approved version live for fans and submits the edit for review, shown by `pendingReviewSubmittedAt`; price, access and visibility changes are not reviewed. A `rejected` experience is offline and goes back into review when you publish it again. Its creator, and Fanvue staff testing it, can open it in every state.
    - `reviewRejection` ExperienceReviewRejection, nullable, required: Why Fanvue rejected the experience; `null` unless `reviewStatus` is `rejected`.
      - `reasonCodes` string[], required: The listing requirement sections Fanvue cited, such as `7.3`; see the App Store listing requirements.
      - `message` string, required: Fanvue's explanation, written for the creator.
    - `pendingReviewSubmittedAt` string, date-time, nullable, required: When an edit to an approved experience was submitted for review (ISO 8601); `null` when no edit is waiting. Fans keep seeing the approved version until Fanvue decides; the fields returned here are the edited ones.
    - `isNsfw` boolean, required: Whether the experience is NSFW, because its creator's profile is not marked safe for work or the app is rated NSFW. Fans must pass age verification to open an NSFW experience, and a fan in safe mode is warned first.
    - `url` string, required: The share link fans land on: `/{handle}/experiences/{slug}` for a listed experience, keyed by the uuid instead when `hidden` so the link stays unguessable. Chat cards and link previews recognise it.

## Other responses

- `400`: Bad Request - API version not supported OR validation failed OR a field was refused by the platform (its message names the field)
- `401`: Unauthorized Response
- `403`: Three refusals, told apart by their shape. A named refusal the app can act on carries `error` and `message`: the creator is outside the fan-experiences rollout, has not installed the app, the app cannot carry experiences, or Fanvue has removed this experience (`experience_suspended`, lifted on reinstatement). The token lacking `write:experience` carries `error` alone (`Insufficient scopes`). The app named in `appUuid` not being the calling app carries `message` alone (`Forbidden`).
- `404`: App not found
- `409`: Fans have paid for access this change takes away. `paid_impact_acknowledgement_required` is retried with the acknowledgement fields; `blocked_active_subscriptions` cannot proceed from the API
- `410`: API version no longer supported (sunset)
- `429`: Too many requests - rate limit exceeded
- `502`: The write could not be completed against the upstream
- `503`: Developer API upstream is not configured

## Changes

- **2026-10-21** `966260af2770`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/fanvue/apis/fanvue-api/changes/experiences/post.md)

---

[API](https://skmtc.dev/fanvue/apis/fanvue-api.md) - [All operations](https://skmtc.dev/fanvue/apis/fanvue-api/llms.txt) - [OpenAPI document](https://skmtc.dev/fanvue/apis/fanvue-api/revisions/966260af2770?raw)
