Skip to content

Quickstart

This walks through one project with a test key: everything behaves like a live project (statuses, webhooks, the delivery) except that nothing is processed and nothing is charged. Switch to a live key when you’re ready.

Sign in at app.podsworth.com, open your account page, and under API keys create a key in Test mode with these permissions: read projects, create and upload projects. Copy the secret: it’s shown once.

Terminal window
export PODSWORTH_KEY=pw_test_...
Terminal window
curl https://api.podsworth.com/v1/projects \
-H "Authorization: Bearer $PODSWORTH_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Episode 12", "files": [{"fileName": "episode-12.wav"}]}'

The project starts as a draft. Each file has an upload: a URL and the form fields to send with it.

{
"id": "prj_9f2c41d0b8e64a7c9a11",
"status": "draft",
"mode": "test",
"files": [
{
"position": 0,
"fileName": "episode-12.wav",
"uploadStatus": "pending",
"upload": { "url": "https://…s3.amazonaws.com/", "fields": { "key": "uploads/…", "policy": "…", "…": "…" } }
}
]
}

POST the file to upload.url as multipart/form-data: every field from upload.fields, then the file itself as file (it must come last).

Terminal window
curl https://…s3.amazonaws.com/ \
-F key=uploads/… -F policy=… -F x-amz-signature=… \
-F file=@episode-12.wav
Terminal window
curl -X POST https://api.podsworth.com/v1/projects/prj_9f2c41d0b8e64a7c9a11/submit \
-H "Authorization: Bearer $PODSWORTH_KEY"

The project goes to validating: we check every file, measure it, and (with a live key) charge its credits. Then it’s queued, or rejected with a reason you can fix before submitting again.

Poll GET /v1/projects/{id} (every 10 seconds or so), or add a webhook and wait for project.completed. A test project takes about a minute.

Terminal window
curl https://api.podsworth.com/v1/projects/prj_9f2c41d0b8e64a7c9a11 \
-H "Authorization: Bearer $PODSWORTH_KEY"

When it’s completed, downloadUrl is the whole delivery as a zip, outputs lists each file with its own link, and manifestUrl describes them. Links last an hour: fetch the project again for fresh ones.

import os
import time
import requests
API = "https://api.podsworth.com"
HEADERS = {"Authorization": f"Bearer {os.environ['PODSWORTH_KEY']}"}
project = requests.post(f"{API}/v1/projects", headers=HEADERS, json={
"name": "Episode 12", "files": [{"fileName": "episode-12.wav"}]}).json()
upload = project["files"][0]["upload"]
with open("episode-12.wav", "rb") as f:
requests.post(upload["url"], data=upload["fields"], files={"file": f}).raise_for_status()
requests.post(f"{API}/v1/projects/{project['id']}/submit", headers=HEADERS).raise_for_status()
while project["status"] not in ("completed", "failed", "rejected", "cancelled"):
time.sleep(10)
project = requests.get(f"{API}/v1/projects/{project['id']}", headers=HEADERS).json()
print(project["status"], project["downloadUrl"] or project["rejection"] or project["failure"])

Node 20 or later (built-in fetch, FormData and Blob):

import { readFile } from "node:fs/promises";
const API = "https://api.podsworth.com";
const headers = { Authorization: `Bearer ${process.env.PODSWORTH_KEY}`, "Content-Type": "application/json" };
let project = await (await fetch(`${API}/v1/projects`, {
method: "POST", headers, body: JSON.stringify({ name: "Episode 12", files: [{ fileName: "episode-12.wav" }] }),
})).json();
const { url, fields } = project.files[0].upload;
const form = new FormData();
for (const [name, value] of Object.entries(fields)) form.append(name, value as string);
form.append("file", new Blob([await readFile("episode-12.wav")]), "episode-12.wav");
if (!(await fetch(url, { method: "POST", body: form })).ok) throw new Error("upload failed");
await fetch(`${API}/v1/projects/${project.id}/submit`, { method: "POST", headers });
while (!["completed", "failed", "rejected", "cancelled"].includes(project.status)) {
await new Promise((r) => setTimeout(r, 10_000));
project = await (await fetch(`${API}/v1/projects/${project.id}`, { headers })).json();
}
console.log(project.status, project.downloadUrl ?? project.rejection ?? project.failure);
  • Projects: statuses, multi-track projects, settings, outputs.
  • Webhooks: stop polling.
  • Test mode: force a failure to test your error handling.