curl --request POST \
--url https://api.fanvue.com/v1/experiences \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'X-Fanvue-API-Version: <x-fanvue-api-version>' \
--data '
{
"appUuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"externalExperienceId": "course-photography-101",
"title": "Photography 101",
"description": "Six lessons, shot on film.",
"priceCents": 1500
}
'import requests
url = "https://api.fanvue.com/v1/experiences"
payload = {
"appUuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"externalExperienceId": "course-photography-101",
"title": "Photography 101",
"description": "Six lessons, shot on film.",
"priceCents": 1500
}
headers = {
"X-Fanvue-API-Version": "<x-fanvue-api-version>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Fanvue-API-Version': '<x-fanvue-api-version>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
appUuid: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
externalExperienceId: 'course-photography-101',
title: 'Photography 101',
description: 'Six lessons, shot on film.',
priceCents: 1500
})
};
fetch('https://api.fanvue.com/v1/experiences', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"experience": {
"uuid": "00000000-0000-4000-8000-000000000010",
"appUuid": "00000000-0000-4000-8000-000000000001",
"creatorUuid": "00000000-0000-4000-8000-000000000002",
"externalExperienceId": "course-photography-101",
"title": "Photography 101",
"description": "Six lessons, shot on film.",
"imageUrl": "https://cdn.fanvue.com/app-experiences/00000000-0000-4000-8000-000000000002/3f2a9c1d8e7b6a50.webp",
"accessMode": "PAID",
"experienceType": "VIDEO",
"deliveryMode": "EMBEDDED",
"externalUrl": null,
"hidden": false,
"priceCents": 1500,
"priceRecurring": false,
"publishedAt": "2026-10-07T10:00:00.000Z",
"suspendedAt": null,
"reviewStatus": "approved",
"reviewRejection": null,
"pendingReviewSubmittedAt": null,
"isNsfw": false,
"url": "https://www.fanvue.com/ava/experiences/photography-101"
}
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>"
}{
"error": "fan_experiences_not_enabled",
"message": "<string>"
}{
"message": "<string>"
}{
"error": "paid_impact_acknowledgement_required",
"message": "Fans have paid for access this would take away; acknowledge the paid impact to proceed.",
"impact": {
"affectedFanCount": 3,
"activeSubscriptionCount": 0,
"pendingSubscriptionInvoiceCount": 0,
"oneTimePurchaseCount": 3,
"pendingPurchaseCount": 0
}
}{
"error": "<string>",
"message": "<string>",
"nextVersion": "<string>"
}{
"error": "<string>"
}{
"message": "<string>"
}{
"message": "<string>"
}Publish an experience for this creator
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.
curl --request POST \
--url https://api.fanvue.com/v1/experiences \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'X-Fanvue-API-Version: <x-fanvue-api-version>' \
--data '
{
"appUuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"externalExperienceId": "course-photography-101",
"title": "Photography 101",
"description": "Six lessons, shot on film.",
"priceCents": 1500
}
'import requests
url = "https://api.fanvue.com/v1/experiences"
payload = {
"appUuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"externalExperienceId": "course-photography-101",
"title": "Photography 101",
"description": "Six lessons, shot on film.",
"priceCents": 1500
}
headers = {
"X-Fanvue-API-Version": "<x-fanvue-api-version>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Fanvue-API-Version': '<x-fanvue-api-version>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
appUuid: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
externalExperienceId: 'course-photography-101',
title: 'Photography 101',
description: 'Six lessons, shot on film.',
priceCents: 1500
})
};
fetch('https://api.fanvue.com/v1/experiences', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"experience": {
"uuid": "00000000-0000-4000-8000-000000000010",
"appUuid": "00000000-0000-4000-8000-000000000001",
"creatorUuid": "00000000-0000-4000-8000-000000000002",
"externalExperienceId": "course-photography-101",
"title": "Photography 101",
"description": "Six lessons, shot on film.",
"imageUrl": "https://cdn.fanvue.com/app-experiences/00000000-0000-4000-8000-000000000002/3f2a9c1d8e7b6a50.webp",
"accessMode": "PAID",
"experienceType": "VIDEO",
"deliveryMode": "EMBEDDED",
"externalUrl": null,
"hidden": false,
"priceCents": 1500,
"priceRecurring": false,
"publishedAt": "2026-10-07T10:00:00.000Z",
"suspendedAt": null,
"reviewStatus": "approved",
"reviewRejection": null,
"pendingReviewSubmittedAt": null,
"isNsfw": false,
"url": "https://www.fanvue.com/ava/experiences/photography-101"
}
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>"
}{
"error": "fan_experiences_not_enabled",
"message": "<string>"
}{
"message": "<string>"
}{
"error": "paid_impact_acknowledgement_required",
"message": "Fans have paid for access this would take away; acknowledge the paid impact to proceed.",
"impact": {
"affectedFanCount": 3,
"activeSubscriptionCount": 0,
"pendingSubscriptionInvoiceCount": 0,
"oneTimePurchaseCount": 3,
"pendingPurchaseCount": 0
}
}{
"error": "<string>",
"message": "<string>",
"nextVersion": "<string>"
}{
"error": "<string>"
}{
"message": "<string>"
}{
"message": "<string>"
}write:experience— Request that the creator publish or unpublish a fan-facing experience. The app mints a request token; the creator confirms and Fanvue performs the change.
Authorizations
OAuth 2.0 access token, presented as a JWT bearer token in the Authorization header. Obtain a token via the authorization-code flow; the scopes granted to the token determine which operations it may call.
Headers
API version to use for the request
"2025-06-26"
Body
Your app's UUID.
Your own stable id for the experience. Publishing the same id again for the same creator updates the existing experience.
1 - 255"course-photography-101"
1 - 100"Photography 101"
500"Six lessons, shot on film."
FREE, SUBSCRIPTION (the creator's subscribers), PAID (needs priceCents) or HIDDEN (share-link only).
FREE, SUBSCRIPTION, PAID, HIDDEN 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.
2048"https://example.com/covers/photography-101.jpg"
EMBEDDED (default) renders your fan_experience surface on Fanvue; EXTERNAL opens externalUrl.
EMBEDDED, EXTERNAL 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.
2048"https://example.com/experiences/photography-101"
Keep the experience off the creator's profile. Implied by HIDDEN.
Price in USD cents, between 300 and 50000.
300 <= x <= 500001500
With priceCents: bill monthly as an app subscription instead of a one-off unlock.
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.
LIVE_STREAM, LIVE_AUDIO, VIDEO, AUDIO, GAME, CHALLENGE, QUIZ, EVENT, OTHER "GAME"
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.
10Show child attributes
Show child attributes
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.
true
The impact the previous 409 reported, echoed back verbatim.
Show child attributes
Show child attributes
Response
The experience as published, with the type and price Fanvue recorded
Show child attributes
Show child attributes
Was this page helpful?