Uploads
Uploads are a two‑step intent → finalize flow so large media never transits
the API. You ask for a signed PUT URL, push the bytes straight to storage, then
tell Core to finalize — which is also what creates the track and kicks off
processing. All routes are owner‑scoped and entitlement‑gated. See
Authentication and
Conventions → Idempotency & safety.
Base path: /api/swayzio/v1. Runtime: native→proxy.
This page covers uploading the original audio of a new track. Specialized intents — versions, artwork, documents — live on Track Resources; pack track/cover uploads live on Pack Sharing.
Endpoints
| Method | Path | Purpose | Auth | Request | Response |
|---|---|---|---|---|---|
POST | /v1/uploads | Create an upload intent for a new track’s original audio | Clerk actor + entitlement | createUploadRequestSchema | 201 clientCreateUploadResponseSchema |
POST | /v1/uploads/:uploadId/finalize | Finalize after the signed PUT; dispatches processing | Clerk actor | finalizeUploadRequestSchema | finalize union |
The upload flow
Create the intent
POST /v1/uploads with the file’s name, content type, and byte size. Core
allocates a track + asset, signs a one‑time PUT URL, and returns the headers
you must echo.
curl -s -X POST https://app.swayzio.com/api/swayzio/v1/uploads \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"fileName":"night-drive.wav","contentType":"audio/wav","byteSize":48211200}'{
"uploadId": "upl_…",
"trackId": "trk_…",
"assetId": "ast_…",
"signedUrl": "https://storage.googleapis.com/…",
"method": "PUT",
"expiresAt": "2026-06-10T…Z",
"requiredHeaders": { "content-type": "audio/wav" },
"finalizeUrl": "https://app.swayzio.com/api/swayzio/v1/uploads/upl_…/finalize"
}Upload the bytes
PUT the file directly to signedUrl, sending every header in
requiredHeaders exactly as given. The bytes never pass through the Core API.
curl -s -X PUT "$SIGNED_URL" \
-H "content-type: audio/wav" \
--data-binary @night-drive.wavFinalize
POST to the finalizeUrl (or /v1/uploads/:uploadId/finalize). The first
original_audio finalize for a track dispatches the media‑intelligence pipeline
(probe, waveform, tags, stems, lyrics, embeddings). Optionally include a decoded
waveform so a peaks visual renders immediately without waiting on processing.
curl -s -X POST "$FINALIZE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"durationMs":182000}'Poll GET /v1/tracks/:trackId/processing-status
to watch the lanes complete.
POST /v1/uploads
Creates an upload intent for a new track’s original audio.
Auth: Clerk actor + upload entitlement (a denied entitlement returns
403 entitlement_required).
Request: createUploadRequestSchema —
{ intent?, fileName, contentType, byteSize }. intent defaults to
track_original; both MIME type and file extension are validated, and audio is
capped at 500 MB. This route only accepts audio‑track intents — draft,
track_artwork, and track_document are rejected with 400 and a redirect to
the dedicated route:
Rejected intent | Error | Use instead |
|---|---|---|
draft | use_track_versions_upload_route | POST /v1/tracks/:trackId/versions/uploads |
track_artwork | use_track_artwork_upload_route | POST /v1/tracks/:trackId/artwork/uploads |
track_document | use_track_documents_upload_route | POST /v1/tracks/:trackId/documents/uploads |
Response: 201 clientCreateUploadResponseSchema. This is the
redacted client DTO — it omits the internal objectName/storageUri and adds a
ready‑to‑call finalizeUrl:
{ uploadId, trackId, assetId, signedUrl, method: "PUT", expiresAt, requiredHeaders, finalizeUrl }.
Send every header in requiredHeaders on the PUT, unchanged. A signature
mismatch (wrong or missing header) is rejected by storage, not the API, so the
finalize step will later report the asset as missing.
POST /v1/uploads/:uploadId/finalize
Finalizes an intent once the signed PUT has landed. For the first
original_audio asset on a track this also queues the processing jobs.
Auth: Clerk actor.
Request: finalizeUploadRequestSchema —
{ assetId?, durationMs?, waveform? }, where waveform is
{ peaks: number[] (16–2048, each 0..1), sampleRateHz, channels? }. The waveform
is advisory: Core persists it as the track’s peaks asset so the visual renders
instantly.
Response: a discriminated union on status:
status | HTTP | Meaning |
|---|---|---|
finalized | 200 | { assetId, assetKind, jobsQueued, trackId, uploadId, workflowStatus? } |
already_finalized | 200 | Idempotent re‑finalize of a completed intent |
upload_intent_not_found | 404 | No such intent for this owner |
asset_mismatch | 409 | assetId doesn’t match the intent (upload_asset_mismatch) |
curl -s -X POST https://app.swayzio.com/api/swayzio/v1/uploads/upl_123/finalize \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"durationMs": 182000,
"waveform": { "peaks": [0.1, 0.4, 0.9, 0.3], "sampleRateHz": 48000, "channels": 2 }
}'Related
- Tracks — the records uploads create.
- Track Resources — version, artwork, and document upload intents, plus processing status.
- Conventions · Data Contracts