curl --request POST \
--url https://api.usepatchwork.co/v1/voice/transcribe \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form audio=@example-fileconst form = new FormData();
form.append('audio', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.usepatchwork.co/v1/voice/transcribe', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.usepatchwork.co/v1/voice/transcribe"
files = { "audio": ("example-file", open("example-file", "rb")) }
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, files=files, headers=headers)
print(response.text){
"status": "success",
"data": {
"text": "move the meeting to Thursday",
"confidence": 0.98
}
}{
"status": "error",
"error": {
"message": "<string>",
"code": "<string>",
"details": "<unknown>"
}
}{
"status": "error",
"error": {
"message": "<string>",
"code": "<string>",
"details": "<unknown>"
}
}{
"status": "error",
"error": {
"message": "<string>",
"code": "<string>",
"details": "<unknown>"
}
}Transcribe an utterance
Audio in, text out. Subject-authed exactly like the text endpoints. Stateless — this does not start a run or touch a thread; send the transcript as a message yourself.
curl --request POST \
--url https://api.usepatchwork.co/v1/voice/transcribe \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form audio=@example-fileconst form = new FormData();
form.append('audio', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.usepatchwork.co/v1/voice/transcribe', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.usepatchwork.co/v1/voice/transcribe"
files = { "audio": ("example-file", open("example-file", "rb")) }
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, files=files, headers=headers)
print(response.text){
"status": "success",
"data": {
"text": "move the meeting to Thursday",
"confidence": 0.98
}
}{
"status": "error",
"error": {
"message": "<string>",
"code": "<string>",
"details": "<unknown>"
}
}{
"status": "error",
"error": {
"message": "<string>",
"code": "<string>",
"details": "<unknown>"
}
}{
"status": "error",
"error": {
"message": "<string>",
"code": "<string>",
"details": "<unknown>"
}
}Authorizations
Identify accepts a workspace API key (sk_…). Loom accepts either a short-lived session token (direct) or a workspace API key (relay).
Headers
Makes this POST safe to retry. The first call does the work and its response is stored; a retry with the same key returns that stored response with Idempotency-Replayed: true, without charging again. Same key with a different body is rejected. Keys expire after 24 hours.
255Relay only. Opaque subject id, chosen by you and never resolved by Patchwork. Partitions threads and memory within your workspace. Direct tokens carry sub instead — do not send this header on a session token.
Required on every subject-scoped resource: threads, messages, runs, outcomes, confirmations and realtime. Omitting it is subject_required, never a wider result. The value is used exactly as given — never trimmed or normalised — so a stray space creates a second partition and is refused as invalid_subject.
Connection id or name the agent may call. Required in relay when the agent has unpinned customer tools. Direct tokens may carry conn instead.