On this page
Alfie REST API reference
JSON over HTTPS at https://alfienotes.com/api/v1, authenticated with an API key (how). This page is generated from the OpenAPI spec (JSON).
Endpoints#
To retry a POST safely, send an Idempotency-Key header: the same key and body return the first response with Idempotent-Replayed: true and are never charged twice.
GET/me#
Read your plan and remaining credits
Required scope: notes:read
curl "https://alfienotes.com/api/v1/me" \
-H "Authorization: Bearer $ALFIE_API_KEY"{
"plan": "pro",
"remaining_usage_seconds": 36000
}Status codes
- 200 Plan and remaining credits.
- 401
unauthorized: the API key is missing, malformed, unknown or revoked. - 403
insufficient_scope: the key lacks the required scope; orplan_limit: the media is longer than your plan allows. - 405
method_not_allowed. - 429
rate_limited: too many requests for this key. - 500
internal_error.
POST/uploads#
Get a pre-signed URL to upload a file
Required scope: notes:write
PUT the file bytes to upload_url with the required_headers before expires_at (10 minutes), then submit a job with type: "upload" and the upload_id. Files over 800 MB are rejected at job submission with validation_error. The job transcribes the bytes present when it is submitted; later PUTs to the same URL do not affect it.
Accepted formats. Uploads are charged by their real audio length, so only uncompressed WAV (PCM or IEEE float, RIFF/WAVE) is accepted: content_type must be audio/wav, audio/x-wav or audio/wave (other types get 400 validation_error with details.allowed), and a job whose file is not such a WAV fails with 422 unsupported_media. Convert other media to WAV first, for example ffmpeg -i in.m4a -ac 1 -ar 16000 out.wav. Compressed formats may be accepted again in a later version.
| Name | In | Type | Description |
|---|---|---|---|
| Idempotency-Key | header | string (1–255 chars) | 1–255 printable ASCII characters, kept for 24 hours. Replaying the same request returns the original response with Idempotent-Replayed: true; reusing the key with a different body returns 409 idempotency_conflict. |
| filenamerequired | body | string (1–500 chars) | |
| content_typerequired | body | "audio/wav" | "audio/x-wav" | "audio/wave" | The file's MIME type. Only WAV is accepted; see the operation description. |
curl -X POST "https://alfienotes.com/api/v1/uploads" \
-H "Authorization: Bearer $ALFIE_API_KEY" \
-H "Idempotency-Key: lecture-week-1-upload" \
-H "Content-Type: application/json" \
-d '{"filename":"lecture-week-1.wav","content_type":"audio/wav"}'{
"upload_id": "YOUR_UPLOAD_ID",
"upload_url": "https://storage.example.com/YOUR_UPLOAD_ID.wav?X-Amz-Signature=EXAMPLE",
"expires_at": "2026-11-04T09:40:00.000Z",
"required_headers": {
"Content-Type": "audio/wav"
}
}Status codes
- 201 Upload URL created.
- 400
validation_error: the request is invalid. - 401
unauthorized: the API key is missing, malformed, unknown or revoked. - 403
insufficient_scope: the key lacks the required scope; orplan_limit: the media is longer than your plan allows. - 405
method_not_allowed. - 409
idempotency_conflict: the key was used with a different request; oridempotency_in_progress: the first request is still running (see Retry-After). - 429
rate_limited: too many requests for this key. - 500
internal_error.
POST/jobs#
Submit an upload or a YouTube link for transcription
Required scope: notes:write
Credits for the media's duration are reserved atomically; a WAV is charged from its size, not its header. There is no refund if processing later fails (same as the web app). One item per request. Poll GET /jobs/{id} until status is succeeded or failed.
| Name | In | Type | Description |
|---|---|---|---|
| Idempotency-Key | header | string (1–255 chars) | 1–255 printable ASCII characters, kept for 24 hours. Replaying the same request returns the original response with Idempotent-Replayed: true; reusing the key with a different body returns 409 idempotency_conflict. |
| typerequired | body | "upload" | When type is "upload". |
| upload_idrequired | body | string | When type is "upload". |
| filename | body | string (≤500 chars) | When type is "upload". |
| speaker_count | body | integer (1–20) | When type is "upload". |
| language | body | string (≤32 chars) | When type is "upload". |
| prompt | body | string (≤4000 chars) | When type is "upload". |
| region | body | "us" | "eu" | When type is "upload". |
| typerequired | body | "youtube" | When type is "youtube". |
| urlrequired | body | string (≤2048 chars) | When type is "youtube". |
| language | body | string (≤32 chars) | When type is "youtube". |
Transcribe an uploaded WAV
curl -X POST "https://alfienotes.com/api/v1/jobs" \
-H "Authorization: Bearer $ALFIE_API_KEY" \
-H "Idempotency-Key: lecture-week-1-job" \
-H "Content-Type: application/json" \
-d '{"type":"upload","upload_id":"YOUR_UPLOAD_ID","speaker_count":2,"language":"en"}'{
"job": {
"id": "YOUR_NOTE_ID",
"note_id": "YOUR_NOTE_ID",
"status": "processing",
"created_at": "2026-11-04T09:30:00.000Z"
}
}Transcribe a YouTube video
curl -X POST "https://alfienotes.com/api/v1/jobs" \
-H "Authorization: Bearer $ALFIE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"youtube","url":"https://www.youtube.com/watch?v=YOUR_VIDEO_ID"}'{
"job": {
"id": "YOUR_NOTE_ID",
"note_id": "YOUR_NOTE_ID",
"status": "processing",
"created_at": "2026-11-04T09:30:00.000Z"
}
}Status codes
- 202 Job accepted.
- 400
validation_error: the request is invalid. - 401
unauthorized: the API key is missing, malformed, unknown or revoked. - 402
insufficient_credits: not enough remaining credits for this media. - 403
insufficient_scope: the key lacks the required scope; orplan_limit: the media is longer than your plan allows. - 404
not_found: no such resource for this key's user (another user's id also returns 404). - 405
method_not_allowed. - 409
idempotency_conflict: the key was used with a different request; oridempotency_in_progress: the first request is still running (see Retry-After). - 422
unsupported_media: the uploaded file is not in a format accepted right now (see POST /uploads). No credits were reserved. - 429
rate_limited: too many requests for this key. - 500
internal_error.
GET/jobs/{id}#
Poll a job
Required scope: notes:read
The job id is also the note id.
| Name | In | Type | Description |
|---|---|---|---|
| idrequired | path | string |
curl "https://alfienotes.com/api/v1/jobs/YOUR_NOTE_ID" \
-H "Authorization: Bearer $ALFIE_API_KEY"{
"job": {
"id": "YOUR_NOTE_ID",
"note_id": "YOUR_NOTE_ID",
"status": "succeeded",
"created_at": "2026-11-04T09:30:00.000Z"
}
}Status codes
- 200 Job status.
- 401
unauthorized: the API key is missing, malformed, unknown or revoked. - 403
insufficient_scope: the key lacks the required scope; orplan_limit: the media is longer than your plan allows. - 404
not_found: no such resource for this key's user (another user's id also returns 404). - 405
method_not_allowed. - 429
rate_limited: too many requests for this key. - 500
internal_error.
GET/notes#
List your notes, newest first
Required scope: notes:read
Auto-deleted notes are excluded. Pass next_cursor back as cursor to get the next page.
| Name | In | Type | Description |
|---|---|---|---|
| limit | query | integer (1–100) | Default 20. |
| cursor | query | string | Opaque cursor from a previous response's next_cursor. |
curl "https://alfienotes.com/api/v1/notes?limit=1" \
-H "Authorization: Bearer $ALFIE_API_KEY"{
"data": [
{
"id": "YOUR_NOTE_ID",
"title": "lecture-week-1.wav",
"status": "succeeded",
"duration_seconds": 3120,
"source": "file",
"created_at": "2026-11-04T09:30:00.000Z",
"is_public": false
}
],
"next_cursor": "eyJjIjoiMjAyNi0xMS0wNFQwOTozMDowMFoiLCJpIjoiWU9VUl9OT1RFX0lEIn0"
}Status codes
- 200 A page of notes.
- 400
validation_error: the request is invalid. - 401
unauthorized: the API key is missing, malformed, unknown or revoked. - 403
insufficient_scope: the key lacks the required scope; orplan_limit: the media is longer than your plan allows. - 405
method_not_allowed. - 429
rate_limited: too many requests for this key. - 500
internal_error.
GET/notes/{id}#
Fetch a note with its transcript and latest summary
Required scope: notes:read
Another user's note returns 404.
| Name | In | Type | Description |
|---|---|---|---|
| idrequired | path | string |
curl "https://alfienotes.com/api/v1/notes/YOUR_NOTE_ID" \
-H "Authorization: Bearer $ALFIE_API_KEY"{
"note": {
"id": "YOUR_NOTE_ID",
"title": "lecture-week-1.wav",
"status": "succeeded",
"duration_seconds": 3120,
"source": "file",
"created_at": "2026-11-04T09:30:00.000Z",
"is_public": false,
"region": "us",
"transcribed_at": "2026-11-04T09:41:12.000Z",
"transcript": [
{
"start": 0,
"end": 4.2,
"text": "Welcome back. Today is about eigenvalues.",
"speaker": "Speaker 1"
},
{
"start": 4.2,
"end": 7.9,
"text": "Will this be on the exam?",
"speaker": "Speaker 2"
}
],
"artifact": null
}
}Status codes
- 200 The note.
- 401
unauthorized: the API key is missing, malformed, unknown or revoked. - 403
insufficient_scope: the key lacks the required scope; orplan_limit: the media is longer than your plan allows. - 404
not_found: no such resource for this key's user (another user's id also returns 404). - 405
method_not_allowed. - 429
rate_limited: too many requests for this key. - 500
internal_error.
PUT/notes/{id}/sharing#
Share or unshare a note
Required scope: notes:write
Idempotent: setting the current value again succeeds.
| Name | In | Type | Description |
|---|---|---|---|
| idrequired | path | string | |
| publicrequired | body | boolean |
curl -X PUT "https://alfienotes.com/api/v1/notes/YOUR_NOTE_ID/sharing" \
-H "Authorization: Bearer $ALFIE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"public":true}'{
"id": "YOUR_NOTE_ID",
"is_public": true
}Status codes
- 200 The new sharing state.
- 400
validation_error: the request is invalid. - 401
unauthorized: the API key is missing, malformed, unknown or revoked. - 403
insufficient_scope: the key lacks the required scope; orplan_limit: the media is longer than your plan allows. - 404
not_found: no such resource for this key's user (another user's id also returns 404). - 405
method_not_allowed. - 429
rate_limited: too many requests for this key. - 500
internal_error.
GET/notes/{id}/export#
Export a transcript as plain text
Required scope: notes:read
One [hh:mm:ss] Speaker: text line per segment. Returns 400 validation_error when the note has no transcript yet.
| Name | In | Type | Description |
|---|---|---|---|
| idrequired | path | string | |
| format | query | "txt" | Default "txt". |
curl "https://alfienotes.com/api/v1/notes/YOUR_NOTE_ID/export?format=txt" \
-H "Authorization: Bearer $ALFIE_API_KEY"[00:00:00] Speaker 1: Welcome back. Today is about eigenvalues.
[00:00:04] Speaker 2: Will this be on the exam?Status codes
- 200 The transcript.
- 400
validation_error: the request is invalid. - 401
unauthorized: the API key is missing, malformed, unknown or revoked. - 403
insufficient_scope: the key lacks the required scope; orplan_limit: the media is longer than your plan allows. - 404
not_found: no such resource for this key's user (another user's id also returns 404). - 405
method_not_allowed. - 429
rate_limited: too many requests for this key. - 500
internal_error.
Errors#
Every non-2xx response has the body {"error":{"code","message","details"?}}. Branch on code, not on message.
| Code | HTTP | Meaning |
|---|---|---|
| validation_error | 400 | The request is invalid. details.field names the field when there is one. |
| unauthorized | 401 | The API key is missing, malformed, unknown or revoked. |
| insufficient_credits | 402 | Not enough credit for this media. details.required_seconds says how much is needed. |
| insufficient_scope | 403 | The key lacks the scope. details.required_scope names it. |
| plan_limit | 403 | The media is longer than your plan allows. |
| not_found | 404 | No such resource for this key’s user. Another user’s id also returns 404. |
| method_not_allowed | 405 | Wrong HTTP method. The Allow header lists the right one. |
| idempotency_conflict | 409 | This Idempotency-Key was already used with a different request. |
| idempotency_in_progress | 409 | The first request with this Idempotency-Key is still running. Retry after Retry-After. |
| unsupported_media | 422 | The uploaded file is not an uncompressed WAV. No credits were reserved. |
| rate_limited | 429 | Too many requests for this key. Wait Retry-After seconds. |
| internal_error | 500 | Something went wrong on our side. Retry, with the same Idempotency-Key for writes. |
Rate limits#
- 120 requests per minute per key, across the REST API and the MCP server.
- On top of that, 10 job submissions per minute per key (
POST /jobs, and the MCPsubmit_uploadandsubmit_youtubetools). - Windows are fixed 60-second windows. Authenticated responses carry
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset(seconds) for the tightest limit. - Over the limit you get
429 rate_limitedwith aRetry-Afterheader in seconds. If the limit cannot be checked, job submissions are refused withRetry-After: 60and other requests continue.
Credits#
- Credit is counted in seconds of media.
GET /mereturns your plan andremaining_usage_seconds. Credits and plan limits are the same as in the web app. - Submitting a job reserves credit equal to the media length, in one atomic step: the audio length of an uploaded WAV (worked out from its size) or the length of a YouTube video.
- Creating upload URLs, polling jobs, reading notes and exporting transcripts are free.
- Not enough credit returns
402 insufficient_creditswithdetails.required_seconds. Media longer than your plan allows returns403 plan_limit. Neither is charged. - Credits are not refunded if processing fails after the job was accepted, as in the web app.