Appearance
Uploads
Features that work on your own files take the storage key of a finished upload. Files go up in parts of 10 MB, so no single request is large and a failed part can be retried on its own.
The flow:
- Start:
POST /v1/uploadswith the file's name, type and size. You get akey, anuploadId, the part size and the number of parts. - Send parts:
PUT /v1/uploads/partonce per part, in any order. Keep each part'spartNumberandetag. - Finish:
POST /v1/uploads/completewith every part'spartNumberandetag. - Use the key in a feature request.
If something goes wrong, POST /v1/uploads/abort discards the upload.
All upload endpoints need a verified phone. Uploads are free.
Endpoints:
POST /v1/uploadsPUT /v1/uploads/partPOST /v1/uploads/completePOST /v1/uploads/abort
What each feature accepts
| Feature | Field | File types |
|---|---|---|
| Transcripts, alignments | key | Audio or video |
| Clean audio, voice changer | key | Audio or video |
| Voice cloning, singing recordings | key | Audio |
| Sing a song | key | Audio |
| Sound effects for a video | key | Video |
| Dubbing | key | MP4 or MOV video, or audio |
| Image edits | references[].key | PNG, JPEG or WebP |
| Video from an image | image.key, endImage.key | PNG, JPEG or WebP |
| Talking avatars | image.key; audio.key | PNG, JPEG or WebP; audio up to 20 MB |
| Drive | key | Audio, video or image |
A key only works in the workspace that uploaded it.
Start an upload
POST /v1/uploads
Request body
| Field | Type | Required | Description |
|---|---|---|---|
fileName | string | no | The file's name. Longer than 200 characters is cut. It's kept with the upload and used as a default display name. |
contentType | string | yes | audio/*, video/*, image/png, image/jpeg or image/webp, such as audio/mpeg or video/mp4. |
size | integer | yes | The file's size in bytes. Up to 2 GB (2,147,483,648 bytes). Images up to 20 MB. |
Response
201 Created
json
{
"key": "workspaces/3f6b2a1c-8d4e-4b7a-9c2d-5e1f0a9b8c7d/uploads/5f3a8c1e-2d7b-4e9a-a6c4-8b1e0d3f7c25/interview.mp3",
"uploadId": "AEKb2m9Qx7c…",
"partBytes": 10485760,
"parts": 2
}| Field | Description |
|---|---|
key | The storage key. The file name is cleaned: characters other than letters, digits, ., -, _ and spaces are removed, and spaces become -. |
uploadId | Identifies this upload in the part, complete and abort calls. |
partBytes | The part size: 10,485,760 bytes (10 MB). |
parts | How many parts to send: ceil(size ÷ partBytes). |
Errors
| Status | error | Message |
|---|---|---|
| 400 | invalid_request | Upload an audio, video or image file (PNG, JPEG or WebP). |
| 400 | invalid_request | This file is empty. |
| 413 | too_large | Files can be up to 2 GB. |
| 413 | too_large | Images can be up to 20 MB. |
| 403 | phone_unverified | Verify your phone number to start creating. |
Example
bash
curl -X POST https://api.cinara.ai/v1/uploads \
-H "Authorization: Bearer $CINARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fileName": "interview.mp3", "contentType": "audio/mpeg", "size": 12001280}'Upload a part
PUT /v1/uploads/part
Sends one part's raw bytes.
Query parameters
| Name | Required | Description |
|---|---|---|
key | yes | The upload's key, URL-encoded. |
uploadId | yes | The upload's uploadId, URL-encoded. |
part | yes | The part number, starting at 1 (up to 205). |
Request body
The part's bytes, with Content-Type: application/octet-stream. Part n is bytes (n − 1) × partBytes up to n × partBytes of the file. Every part except the last must be exactly partBytes long. A part can't be empty or larger than 10 MB.
Response
200 OK
json
{ "partNumber": 1, "etag": "7b5e9d2c4a1f8e3b6d0c9a2e5f1b4d7a" }Errors
| Status | error | Message |
|---|---|---|
| 400 | invalid_request | Unknown upload (bad key, uploadId or part number) |
| 400 | invalid_request | Upload parts are up to 10 MB (empty or too large) |
| 409 | upload_expired | This upload has expired. Choose the file again. |
| 403 | phone_unverified | Verify your phone number to start creating. |
A part can be sent again if it fails.
Example
bash
curl -X PUT "https://api.cinara.ai/v1/uploads/part?key=$(jq -rn --arg k "$KEY" '$k|@uri')&uploadId=$(jq -rn --arg u "$UPLOAD_ID" '$u|@uri')&part=1" \
-H "Authorization: Bearer $CINARA_API_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @part-aaaaComplete an upload
POST /v1/uploads/complete
Joins the parts into the finished file.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
key | string | yes | The upload's key. |
uploadId | string | yes | The upload's uploadId. |
parts | object[] | yes | Every part, 1–205 entries, each { "partNumber": <integer>, "etag": "<string>" } as returned by the part call. |
Response
200 OK
json
{
"key": "workspaces/3f6b2a1c-8d4e-4b7a-9c2d-5e1f0a9b8c7d/uploads/5f3a8c1e-2d7b-4e9a-a6c4-8b1e0d3f7c25/interview.mp3",
"size": 12001280
}size is the finished file's size in bytes. It's checked against the size sent when starting, and against the limit for the file's type. If either check fails, the upload is deleted and you get an error: start the upload again.
Errors
| Status | error | Message |
|---|---|---|
| 400 | invalid_request | Unknown upload |
| 409 | upload_expired | This upload couldn't be finished. Choose the file again. |
| 400 | invalid_request | The uploaded file isn't the size it was declared as. Upload it again. |
| 413 | too_large | Files can be up to 2 GB. |
| 413 | too_large | Images can be up to 20 MB. |
| 403 | phone_unverified | Verify your phone number to start creating. |
Example
bash
curl -X POST https://api.cinara.ai/v1/uploads/complete \
-H "Authorization: Bearer $CINARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"key": "workspaces/3f6b2a1c-8d4e-4b7a-9c2d-5e1f0a9b8c7d/uploads/5f3a8c1e-2d7b-4e9a-a6c4-8b1e0d3f7c25/interview.mp3", "uploadId": "AEKb2m9Qx7c…", "parts": [{"partNumber": 1, "etag": "7b5e9d2c4a1f8e3b6d0c9a2e5f1b4d7a"}, {"partNumber": 2, "etag": "c3a8f1d6e9b2a5c4d7e0f3b6a9c2d5e8"}]}'Abort an upload
POST /v1/uploads/abort
Discards an unfinished upload and its parts. It succeeds even if the upload is already gone.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
key | string | yes | The upload's key. |
uploadId | string | yes | The upload's uploadId. |
Response
200 OK
json
{ "aborted": true }Errors
| Status | error | Message |
|---|---|---|
| 400 | invalid_request | Unknown upload |
| 403 | phone_unverified | Verify your phone number to start creating. |
Example
bash
curl -X POST https://api.cinara.ai/v1/uploads/abort \
-H "Authorization: Bearer $CINARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"key": "workspaces/3f6b2a1c-8d4e-4b7a-9c2d-5e1f0a9b8c7d/uploads/5f3a8c1e-2d7b-4e9a-a6c4-8b1e0d3f7c25/interview.mp3", "uploadId": "AEKb2m9Qx7c…"}'Full upload script
Uploads a file and prints its key. Needs curl and jq.
bash
#!/usr/bin/env bash
set -euo pipefail
FILE="$1" # e.g. interview.mp3
TYPE="$2" # e.g. audio/mpeg
API="https://api.cinara.ai"
AUTH="Authorization: Bearer $CINARA_API_KEY"
SIZE=$(wc -c < "$FILE" | tr -d ' ')
uri() { jq -rn --arg v "$1" '$v|@uri'; }
START=$(curl -sf -X POST "$API/v1/uploads" -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg n "$(basename "$FILE")" --arg t "$TYPE" --argjson s "$SIZE" '{fileName: $n, contentType: $t, size: $s}')")
KEY=$(echo "$START" | jq -r .key)
UPLOAD_ID=$(echo "$START" | jq -r .uploadId)
PART_BYTES=$(echo "$START" | jq -r .partBytes)
WORK=$(mktemp -d)
split -b "$PART_BYTES" -a 4 "$FILE" "$WORK/part-"
PARTS='[]'
N=1
for CHUNK in "$WORK"/part-*; do
RES=$(curl -sf --retry 3 -X PUT "$API/v1/uploads/part?key=$(uri "$KEY")&uploadId=$(uri "$UPLOAD_ID")&part=$N" \
-H "$AUTH" -H "Content-Type: application/octet-stream" --data-binary @"$CHUNK") || {
curl -s -X POST "$API/v1/uploads/abort" -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg k "$KEY" --arg u "$UPLOAD_ID" '{key: $k, uploadId: $u}')" > /dev/null
exit 1
}
PARTS=$(echo "$PARTS" | jq --argjson p "$RES" '. + [$p]')
N=$((N + 1))
done
rm -rf "$WORK"
curl -sf -X POST "$API/v1/uploads/complete" -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg k "$KEY" --arg u "$UPLOAD_ID" --argjson parts "$PARTS" '{key: $k, uploadId: $u, parts: $parts}')" > /dev/null
echo "$KEY"bash
KEY=$(./cinara-upload.sh interview.mp3 audio/mpeg)