curl --request POST \
--url https://api.moda.app/v1/uploads/register \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"storage_key": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"folder_id": "fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
"purpose": "voice_source"
}
'import requests
url = "https://api.moda.app/v1/uploads/register"
payload = {
"storage_key": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"folder_id": "fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
"purpose": "voice_source"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
storage_key: '<string>',
filename: '<string>',
mime_type: '<string>',
folder_id: 'fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV',
purpose: 'voice_source'
})
};
fetch('https://api.moda.app/v1/uploads/register', 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.moda.app/v1/uploads/register",
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([
'storage_key' => '<string>',
'filename' => '<string>',
'mime_type' => '<string>',
'folder_id' => 'fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV',
'purpose' => 'voice_source'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.moda.app/v1/uploads/register"
payload := strings.NewReader("{\n \"storage_key\": \"<string>\",\n \"filename\": \"<string>\",\n \"mime_type\": \"<string>\",\n \"folder_id\": \"fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV\",\n \"purpose\": \"voice_source\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.moda.app/v1/uploads/register")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"storage_key\": \"<string>\",\n \"filename\": \"<string>\",\n \"mime_type\": \"<string>\",\n \"folder_id\": \"fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV\",\n \"purpose\": \"voice_source\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.moda.app/v1/uploads/register")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"storage_key\": \"<string>\",\n \"filename\": \"<string>\",\n \"mime_type\": \"<string>\",\n \"folder_id\": \"fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV\",\n \"purpose\": \"voice_source\"\n}"
response = http.request(request)
puts response.read_body{
"id": "file_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
"url": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"size_bytes": 123,
"was_duplicate": false,
"folder_id": "fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
"expires_at": "<string>"
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}Register Upload
Finalize a signed-URL upload into a Moda file row.
Streams the staged blob through download_with_hash (writes to a
temp file on disk, hashes in 8 KiB chunks) so a 250 MB upload never
fully materializes in process memory — loading even a couple of those
concurrently on Cloud Run would risk OOM and defeat the point of the
direct-to-storage flow.
Deduplicates against existing team files by content hash. On a dedup
miss the staged blob is server-side-copied to the canonical
teams/<team>/files/<id> key via storage.copy (zero data
transfer on GCS) and registered via create_file_from_storage. The
pending blob is best-effort deleted either way.
curl --request POST \
--url https://api.moda.app/v1/uploads/register \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"storage_key": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"folder_id": "fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
"purpose": "voice_source"
}
'import requests
url = "https://api.moda.app/v1/uploads/register"
payload = {
"storage_key": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"folder_id": "fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
"purpose": "voice_source"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
storage_key: '<string>',
filename: '<string>',
mime_type: '<string>',
folder_id: 'fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV',
purpose: 'voice_source'
})
};
fetch('https://api.moda.app/v1/uploads/register', 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.moda.app/v1/uploads/register",
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([
'storage_key' => '<string>',
'filename' => '<string>',
'mime_type' => '<string>',
'folder_id' => 'fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV',
'purpose' => 'voice_source'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.moda.app/v1/uploads/register"
payload := strings.NewReader("{\n \"storage_key\": \"<string>\",\n \"filename\": \"<string>\",\n \"mime_type\": \"<string>\",\n \"folder_id\": \"fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV\",\n \"purpose\": \"voice_source\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.moda.app/v1/uploads/register")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"storage_key\": \"<string>\",\n \"filename\": \"<string>\",\n \"mime_type\": \"<string>\",\n \"folder_id\": \"fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV\",\n \"purpose\": \"voice_source\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.moda.app/v1/uploads/register")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"storage_key\": \"<string>\",\n \"filename\": \"<string>\",\n \"mime_type\": \"<string>\",\n \"folder_id\": \"fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV\",\n \"purpose\": \"voice_source\"\n}"
response = http.request(request)
puts response.read_body{
"id": "file_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
"url": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"size_bytes": 123,
"was_duplicate": false,
"folder_id": "fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
"expires_at": "<string>"
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}{
"type": "invalid_request",
"code": "<string>",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"causes": [
"<unknown>"
],
"details": {},
"retry_after_ms": 123,
"retryable": true
}Authorizations
API key from Settings > Developer > REST API
Headers
Calendar-dated API version pin. New integrations should pin 2026-05-01 to opt into the newest response shapes. For back-compat the server also accepts requests with no header and resolves them to the current default (today: 2026-04-12); that default advances on each sunset date. Any unsupported value returns 400 unsupported_version.
2026-04-12, 2026-05-01 "2026-05-01"
Body
storage_key returned by a prior POST /v1/uploads/url after the PUT completed.
Filename to record on the File row. Defaults to the basename embedded in storage_key.
512MIME type to record on the File row. Defaults to the type the signed URL was originally bound to (inferred from the filename when not supplied).
Prefixed fld_ wire ID (Crockford base32 body) — the canonical, recommended form. For back-compat, a bare UUID string is also accepted in both path parameters and JSON request bodies (older integrations that stored raw UUIDs keep working), as is the prefix over a UUID body (fld_00000000-0000-4000-8000-000000000000). All three are permanent, supported inputs; only the canonical form is ever emitted.
^fld_[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$"fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV"
voice_source marks a transient recording: the seed of POST /v1/voices/similar or the source_audio of a speech-to-speech take. It always gets its own file (never a duplicate of one you already have, and never handed to another upload of the same bytes), stays out of My Media, and Moda deletes it 24 hours later (expires_at) unless a canvas, brand kit or other surface references it by then. Delete it yourself with DELETE /v1/drive/items/{id} once used. Omit for an ordinary, permanent upload.
"voice_source"Response
Successful Response
Unique file identifier (prefixed file_...).
^file_[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$"file_01HT9WK8N3M2J4A5Z6P7Q8R9TV"
Stable proxy URL for the uploaded file. Use this in attachment URLs.
Filename of the uploaded file.
MIME type of the file.
File size in bytes.
True if an identical file already existed (deduplicated).
Prefixed fld_ wire ID (Crockford base32 body) — the canonical, recommended form. For back-compat, a bare UUID string is also accepted in both path parameters and JSON request bodies (older integrations that stored raw UUIDs keep working), as is the prefix over a UUID body (fld_00000000-0000-4000-8000-000000000000). All three are permanent, supported inputs; only the canonical form is ever emitted.
^fld_[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$"fld_01HT9WK8N3M2J4A5Z6P7Q8R9TV"
ISO 8601 UTC time after which Moda deletes this transient upload (purpose voice_source), or null for a file that never expires.