curl --request POST \
--url https://api.heify.com/request-upload-url \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"configuration_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"evaluator_id": "2abb5563-dd64-47bb-bb17-94252e168b06",
"name": "sales-call-2026-03-21.mp3"
}
'{
"data": {
"transcription_id": "b147ee2b-41f8-43ac-8838-29b17229abf4",
"upload_url": "https://storage.heify.com/upload/client-id/b147ee2b-41f8-43ac-8838-29b17229abf4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=300&...",
"configuration_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"participant_id": null,
"evaluator_id": "2abb5563-dd64-47bb-bb17-94252e168b06",
"period_date": null,
"name": "sales-call-2026-03-21.mp3"
}
}Submit from local file
Upload a local audio or video file for transcription using a two-step process.
curl --request POST \
--url https://api.heify.com/request-upload-url \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"configuration_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"evaluator_id": "2abb5563-dd64-47bb-bb17-94252e168b06",
"name": "sales-call-2026-03-21.mp3"
}
'{
"data": {
"transcription_id": "b147ee2b-41f8-43ac-8838-29b17229abf4",
"upload_url": "https://storage.heify.com/upload/client-id/b147ee2b-41f8-43ac-8838-29b17229abf4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=300&...",
"configuration_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"participant_id": null,
"evaluator_id": "2abb5563-dd64-47bb-bb17-94252e168b06",
"period_date": null,
"name": "sales-call-2026-03-21.mp3"
}
}Step 1 — Request upload URL
curl -X POST https://api.heify.com/request-upload-url \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"configuration_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"evaluator_id": "2abb5563-dd64-47bb-bb17-94252e168b06",
"name": "sales-call-2026-03-21.mp3",
"period_date": "2026-03-21T14:30:00Z"
}'
import requests
response = requests.post(
"https://api.heify.com/request-upload-url",
headers={"x-api-key": "YOUR_API_KEY"},
json={
"configuration_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"evaluator_id": "2abb5563-dd64-47bb-bb17-94252e168b06",
"name": "sales-call-2026-03-21.mp3",
"period_date": "2026-03-21T14:30:00Z"
}
)
data = response.json()["data"]
upload_url = data["upload_url"]
transcription_id = data["transcription_id"]
upload_url expires in 5 minutes. Proceed to Step 2 immediately after receiving it.participant_id and evaluator_id are optional. If provided, they must exist and belong to your account.name field is sanitized to ASCII — accented or non-Latin characters are automatically stripped.period_date is optional — it sets when the recording was made and overwrites the transcription’s created_at. If omitted, created_at is the upload timestamp. See Recording date below for accepted formats.Step 2 — Upload the file
Send aPUT request to the upload_url from the Step 1 response. You must include the metadata fields as headers — they are embedded in the upload URL signature and the request will fail if they are missing or don’t match.
curl -X PUT \
-H "Content-Type: audio/mpeg" \
-H "x-amz-meta-configuration_id: CONFIGURATION_ID_FROM_STEP_1" \
-H "x-amz-meta-evaluator_id: EVALUATOR_ID_FROM_STEP_1" \
-H "x-amz-meta-period_date: PERIOD_DATE_FROM_STEP_1" \
--data-binary @recording.mp3 \
"UPLOAD_URL_FROM_STEP_1"
data = response.json()["data"] # Step 1 response
# Build metadata headers from Step 1 response
meta_headers = {
"Content-Type": "audio/mpeg",
"x-amz-meta-configuration_id": data["configuration_id"],
}
if data.get("participant_id"):
meta_headers["x-amz-meta-participant_id"] = data["participant_id"]
if data.get("evaluator_id"):
meta_headers["x-amz-meta-evaluator_id"] = data["evaluator_id"]
if data.get("period_date"):
meta_headers["x-amz-meta-period_date"] = data["period_date"]
if data.get("name"):
meta_headers["x-amz-meta-name"] = data["name"]
with open("recording.mp3", "rb") as f:
upload_response = requests.put(data["upload_url"], data=f, headers=meta_headers)
if upload_response.status_code == 200:
print(f"Upload successful. Transcription ID: {data['transcription_id']}")
200 OK with an empty body confirms the upload succeeded. Processing starts automatically in the background.
period_date in Step 1, replay the normalized value echoed in the Step 1 response (data.period_date) as the x-amz-meta-period_date header — not your original input. Like the other metadata headers, it is embedded in the upload URL signature.transcription_id from Step 1 to check results once processing completes, or configure a webhook to receive a notification automatically.Recording date (period_date)
Customers often upload calls in batches, after the fact — a batch recorded in April might be uploaded in June. Without period_date, every transcription is stamped with the upload time, so all of April’s calls would land in June and monthly or quarterly reporting would be impossible.
period_date states when the recording actually happened. Because it overwrites created_at, setting it correctly at upload time automatically fixes every time-based analytic at once: the timeline, the month-over-month comparison, per-criterion evolution, the evaluator heatmap, and the start_date/end_date analytics filters — all are derived from created_at.
It behaves identically here and on Submit from public URL: same format, same validation, same errors.
Accepted formats
The date is required; the time is optional. Everything is resolved to a UTC instant and stored increated_at.
| Input | Resolves to |
|---|---|
"2026-04-15" | 2026-04-15T00:00:00Z — date only lands at midnight UTC |
"2026-04-15T14:30" | 2026-04-15T14:30:00Z — seconds optional |
"2026-04-15T14:30:00" | 2026-04-15T14:30:00Z — no timezone is read as UTC |
"2026-04-15T14:30:00.000Z" | 2026-04-15T14:30:00Z — fractional seconds accepted and truncated |
"2026-04-15T14:30:00+02:00" | 2026-04-15T12:30:00Z — offset converted to UTC |
00:00:00 UTC (not “some time that day”), and no timezone means UTC (not your local time). A client that means local time must send the offset — and note an offset can move the instant to the previous day, and therefore into the previous reporting month (2026-04-01T00:30:00+02:00 resolves to March 31st).null, or empty-string values are treated as “not provided” — no error, created_at falls back to the upload timestamp. Anything else malformed returns 400: month-only values ("2026-04"), a space instead of the T separator, non-ISO formats ("15/04/2026"), impossible dates, or non-string types.
charts.heatmap is a weekday × hour grid built from created_at — date-only uploads all pile into the midnight column, flattening the hour axis. Everything else (month, timeline, evolution, comparison, date filters, weekday) is correct either way.period_date. If a recording was submitted with the wrong date, delete it and submit the audio again with the correct period_date.Authorizations
Body
The unique identifier of the configuration to use for this transcription.
Optional label or filename for the transcription. Only ASCII characters are kept — non-ASCII characters are automatically stripped.
Optional. Link this transcription to a participant. Must exist and belong to your account.
Optional. Link this transcription to an evaluator. Must exist and belong to your account.
Optional. When the recording was made — a date (YYYY-MM-DD) or an ISO 8601 datetime; the time is optional. Overwrites the transcription's created_at, which drives all time-based analytics. Without a time the date is taken at 00:00:00 UTC; without a timezone the value is interpreted as UTC. Month-only values (2026-04) and non-ISO formats are rejected with 400. If omitted, created_at is the upload timestamp.
^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$"2026-04-15T14:30:00Z"
Response
Pre-signed URL generated successfully. PUT the audio file to upload_url with the correct Content-Type header.
Show child attributes
Show child attributes