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.
1. Create a test key
Section titled “1. Create a test key”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.
export PODSWORTH_KEY=pw_test_...2. Create a project
Section titled “2. Create a project”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": "…", "…": "…" } } } ]}3. Upload the file
Section titled “3. Upload the file”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).
curl https://…s3.amazonaws.com/ \ -F key=uploads/… -F policy=… -F x-amz-signature=… \ -F file=@episode-12.wav4. Submit it
Section titled “4. Submit it”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.
5. Follow it and download the result
Section titled “5. Follow it and download the result”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.
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.
The same in Python
Section titled “The same in Python”import osimport 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"])The same in TypeScript
Section titled “The same in TypeScript”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);