curl --request POST \
--url https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'x-api-key: <api-key>' \
--data '
{
"kind": "voice",
"content_type": "audio/mp4",
"bytes": 1843200
}
'import requests
url = "https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads"
payload = {
"kind": "voice",
"content_type": "audio/mp4",
"bytes": 1843200
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"x-api-key": "<api-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
'x-api-key': '<api-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({kind: 'voice', content_type: 'audio/mp4', bytes: 1843200})
};
fetch('https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'kind' => 'voice',
'content_type' => 'audio/mp4',
'bytes' => 1843200
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads"
payload := strings.NewReader("{\n \"kind\": \"voice\",\n \"content_type\": \"audio/mp4\",\n \"bytes\": 1843200\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads")
.header("Idempotency-Key", "<idempotency-key>")
.header("x-api-key", "<api-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"kind\": \"voice\",\n \"content_type\": \"audio/mp4\",\n \"bytes\": 1843200\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["x-api-key"] = '<api-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"kind\": \"voice\",\n \"content_type\": \"audio/mp4\",\n \"bytes\": 1843200\n}"
response = http.request(request)
puts response.read_body{
"upload_id": "0192fa8a-9346-78b7-9664-3cb0e5caf78d",
"url": "https://uploads.example.com/0192fa8a-9346-78b7-9664-3cb0e5caf78d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=EXAMPLEKEY%2F20261121%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20261121T204140Z&X-Amz-Expires=300&X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost&X-Amz-Security-Token=EXAMPLE-SESSION-TOKEN&X-Amz-Signature=3f1c0d2e9b8a7f6e5d4c3b2a19087f6e5d4c3b2a19087f6e5d4c3b2a19087f6e",
"expires_at": "2026-11-21T20:46:40Z"
}Create an upload
Creates an upload for a fan’s voice note or photo, with a link to send the file to. It’s the first step in sending one, before Send a message. Send a new Idempotency-Key with each upload, so a retry never creates a second one.
curl --request POST \
--url https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'x-api-key: <api-key>' \
--data '
{
"kind": "voice",
"content_type": "audio/mp4",
"bytes": 1843200
}
'import requests
url = "https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads"
payload = {
"kind": "voice",
"content_type": "audio/mp4",
"bytes": 1843200
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"x-api-key": "<api-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
'x-api-key': '<api-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({kind: 'voice', content_type: 'audio/mp4', bytes: 1843200})
};
fetch('https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'kind' => 'voice',
'content_type' => 'audio/mp4',
'bytes' => 1843200
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads"
payload := strings.NewReader("{\n \"kind\": \"voice\",\n \"content_type\": \"audio/mp4\",\n \"bytes\": 1843200\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads")
.header("Idempotency-Key", "<idempotency-key>")
.header("x-api-key", "<api-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"kind\": \"voice\",\n \"content_type\": \"audio/mp4\",\n \"bytes\": 1843200\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dizplai.com/v1/fan-voices/{fan_voice_id}/uploads")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["x-api-key"] = '<api-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"kind\": \"voice\",\n \"content_type\": \"audio/mp4\",\n \"bytes\": 1843200\n}"
response = http.request(request)
puts response.read_body{
"upload_id": "0192fa8a-9346-78b7-9664-3cb0e5caf78d",
"url": "https://uploads.example.com/0192fa8a-9346-78b7-9664-3cb0e5caf78d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=EXAMPLEKEY%2F20261121%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20261121T204140Z&X-Amz-Expires=300&X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost&X-Amz-Security-Token=EXAMPLE-SESSION-TOKEN&X-Amz-Signature=3f1c0d2e9b8a7f6e5d4c3b2a19087f6e5d4c3b2a19087f6e5d4c3b2a19087f6e",
"expires_at": "2026-11-21T20:46:40Z"
}Authorizations
The fan's token from your own login, sent as Authorization: Bearer <token>: a signed-in fan's or a guest's. Dizplai checks it against your login's details, agreed when you're set up, and a guest's token carries the guest claim agreed at the same time. Needed to vote, upload, send a message, and read the fan's own vote or messages.
Headers
Required. A new UUID for each upload. Keys are kept for each fan for 5 minutes, as long as the link: the same key and body from the same fan within that time answers as the first time, with the same upload and link, so a retry never creates a second upload; with a different body, 409 (idempotency_conflict). After 5 minutes, the same key creates a new upload.
Path Parameters
Body
- Voice note
- Photo
What the fan wants to upload for a Fan Voice message: its kind, file type and size. Fixed limits: a voice note is audio/mp4 (AAC, what phones record) or audio/webm (Opus), up to 5 MB and 60 seconds; a photo is image/jpeg, image/png, image/heic or image/webp, up to 10 MB.
Response
Upload the file to url, then send the message with this upload_id within an hour, or the upload is deleted. If the connection drops, ask again with the same Idempotency-Key and body within 5 minutes, while the link works: you get the same upload and link. After that the key is forgotten, and asking again creates a new upload. An upload that's never used is deleted, and only a message the fan sends counts towards their limit.
Where to upload the file, and the upload_id to send with the message. PUT the file to url before expires_at, with Content-Type set to the content_type and Content-Length to the bytes you asked for; anything else is rejected. Then send the message with this upload_id within an hour, or the upload is deleted. Nothing serves the file until a moderator approves it.

