curl --request POST \
--url https://api.fanvue.com/v1/experiences/token/exchange \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'X-Fanvue-API-Version: <x-fanvue-api-version>' \
--data '
{
"token": "exp_2f1c…"
}
'import requests
url = "https://api.fanvue.com/v1/experiences/token/exchange"
payload = { "token": "exp_2f1c…" }
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({token: 'exp_2f1c…'})
};
fetch('https://api.fanvue.com/v1/experiences/token/exchange', 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": "ext-experience-123",
"title": "Behind the scenes",
"description": "An exclusive behind-the-scenes experience.",
"accessMode": "SUBSCRIPTION",
"experienceType": "VIDEO",
"hidden": false
},
"fanUuid": "00000000-0000-4000-8000-000000000003",
"entitlement": {
"isEntitled": true,
"mode": "SUBSCRIPTION",
"reason": "subscribed"
},
"walletBalance": 2500
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>"
}{
"message": "<string>",
"reason": "not_purchased"
}{
"message": "<string>"
}{
"error": "<string>",
"message": "<string>",
"nextVersion": "<string>"
}{
"error": "<string>"
}{
"message": "<string>"
}{
"message": "<string>"
}Exchange a fan experience launch token
Exchanges the opaque experience token an app’s fan-experience surface received in its iframe URL for the resolved experience, the fan’s identity, and the fan’s entitlement to the experience.
The token is bound to the app that owns the experience: a token minted for one app cannot be exchanged with another app’s credentials (403).
Returns 400 if the token is invalid or expired, 403 with error: "experience_suspended" if Fanvue has removed the experience (it comes back when reinstated), 403 with error: "experience_not_approved" if Fanvue has not approved the experience (it opens once approved), and 404 if the experience no longer exists.
curl --request POST \
--url https://api.fanvue.com/v1/experiences/token/exchange \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'X-Fanvue-API-Version: <x-fanvue-api-version>' \
--data '
{
"token": "exp_2f1c…"
}
'import requests
url = "https://api.fanvue.com/v1/experiences/token/exchange"
payload = { "token": "exp_2f1c…" }
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({token: 'exp_2f1c…'})
};
fetch('https://api.fanvue.com/v1/experiences/token/exchange', 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": "ext-experience-123",
"title": "Behind the scenes",
"description": "An exclusive behind-the-scenes experience.",
"accessMode": "SUBSCRIPTION",
"experienceType": "VIDEO",
"hidden": false
},
"fanUuid": "00000000-0000-4000-8000-000000000003",
"entitlement": {
"isEntitled": true,
"mode": "SUBSCRIPTION",
"reason": "subscribed"
},
"walletBalance": 2500
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>"
}{
"message": "<string>",
"reason": "not_purchased"
}{
"message": "<string>"
}{
"error": "<string>",
"message": "<string>",
"nextVersion": "<string>"
}{
"error": "<string>"
}{
"message": "<string>"
}{
"message": "<string>"
}read:experience— Exchange a fan’s experience token for the resolved experience and the fan’s identity, so an embedded fan-facing experience can render the right content.
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
The opaque experience launch token appended to the surface iframe URL.
1"exp_2f1c…"
Response
The resolved experience, fan identity, and entitlement
Show child attributes
Show child attributes
Show child attributes
Show child attributes
The fan's Fanvue wallet balance in minor units (cents, USD), so you can show their spending power as the surface loads. null when the wallet is not available to this fan at all, which is not the same as an empty balance — do not offer a wallet charge then. A snapshot, never an authorisation: charge with POST /experiences/{experienceUuid}/action-purchases and read the fresh balance off that response.
2500
Was this page helpful?