Swayzio Core API v1 — base path /api/swayzio/v1
Core APIUploads

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

MethodPathPurposeAuthRequestResponse
POST/v1/uploadsCreate an upload intent for a new track’s original audioClerk actor + entitlementcreateUploadRequestSchema201 clientCreateUploadResponseSchema
POST/v1/uploads/:uploadId/finalizeFinalize after the signed PUT; dispatches processingClerk actorfinalizeUploadRequestSchemafinalize 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.wav

Finalize

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 intentErrorUse instead
draftuse_track_versions_upload_routePOST /v1/tracks/:trackId/versions/uploads
track_artworkuse_track_artwork_upload_routePOST /v1/tracks/:trackId/artwork/uploads
track_documentuse_track_documents_upload_routePOST /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:

statusHTTPMeaning
finalized200{ assetId, assetKind, jobsQueued, trackId, uploadId, workflowStatus? }
already_finalized200Idempotent re‑finalize of a completed intent
upload_intent_not_found404No such intent for this owner
asset_mismatch409assetId 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 }
      }'