Skip to content
alfiealfie
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
curl "https://alfienotes.com/api/v1/me" \
  -H "Authorization: Bearer $ALFIE_API_KEY"
JSON response · 200
{
  "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; or plan_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.

Parameters
NameInTypeDescription
Idempotency-Keyheaderstring (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.
filenamerequiredbodystring (1–500 chars)
content_typerequiredbody"audio/wav" | "audio/x-wav" | "audio/wave"The file's MIME type. Only WAV is accepted; see the operation description.
curl
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"}'
JSON response · 201
{
  "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; or plan_limit: the media is longer than your plan allows.
  • 405 method_not_allowed.
  • 409 idempotency_conflict: the key was used with a different request; or idempotency_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.

Parameters
NameInTypeDescription
Idempotency-Keyheaderstring (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.
typerequiredbody"upload"When type is "upload".
upload_idrequiredbodystringWhen type is "upload".
filenamebodystring (≤500 chars)When type is "upload".
speaker_countbodyinteger (1–20)When type is "upload".
languagebodystring (≤32 chars)When type is "upload".
promptbodystring (≤4000 chars)When type is "upload".
regionbody"us" | "eu"When type is "upload".
typerequiredbody"youtube"When type is "youtube".
urlrequiredbodystring (≤2048 chars)When type is "youtube".
languagebodystring (≤32 chars)When type is "youtube".

Transcribe an uploaded WAV

curl
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"}'
JSON response · 202
{
  "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
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"}'
JSON response · 202
{
  "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; or plan_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; or idempotency_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.

Parameters
NameInTypeDescription
idrequiredpathstring
curl
curl "https://alfienotes.com/api/v1/jobs/YOUR_NOTE_ID" \
  -H "Authorization: Bearer $ALFIE_API_KEY"
JSON response · 200
{
  "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; or plan_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.

Parameters
NameInTypeDescription
limitqueryinteger (1–100)Default 20.
cursorquerystringOpaque cursor from a previous response's next_cursor.
curl
curl "https://alfienotes.com/api/v1/notes?limit=1" \
  -H "Authorization: Bearer $ALFIE_API_KEY"
JSON response · 200
{
  "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; or plan_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.

Parameters
NameInTypeDescription
idrequiredpathstring
curl
curl "https://alfienotes.com/api/v1/notes/YOUR_NOTE_ID" \
  -H "Authorization: Bearer $ALFIE_API_KEY"
JSON response · 200
{
  "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; or plan_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.

Parameters
NameInTypeDescription
idrequiredpathstring
publicrequiredbodyboolean
curl
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}'
JSON response · 200
{
  "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; or plan_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.

Parameters
NameInTypeDescription
idrequiredpathstring
formatquery"txt"Default "txt".
curl
curl "https://alfienotes.com/api/v1/notes/YOUR_NOTE_ID/export?format=txt" \
  -H "Authorization: Bearer $ALFIE_API_KEY"
Text response · 200
[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; or plan_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.

Error codes
CodeHTTPMeaning
validation_error400The request is invalid. details.field names the field when there is one.
unauthorized401The API key is missing, malformed, unknown or revoked.
insufficient_credits402Not enough credit for this media. details.required_seconds says how much is needed.
insufficient_scope403The key lacks the scope. details.required_scope names it.
plan_limit403The media is longer than your plan allows.
not_found404No such resource for this key’s user. Another user’s id also returns 404.
method_not_allowed405Wrong HTTP method. The Allow header lists the right one.
idempotency_conflict409This Idempotency-Key was already used with a different request.
idempotency_in_progress409The first request with this Idempotency-Key is still running. Retry after Retry-After.
unsupported_media422The uploaded file is not an uncompressed WAV. No credits were reserved.
rate_limited429Too many requests for this key. Wait Retry-After seconds.
internal_error500Something 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 MCP submit_upload and submit_youtube tools).
  • Windows are fixed 60-second windows. Authenticated responses carry RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds) for the tightest limit.
  • Over the limit you get 429 rate_limited with a Retry-After header in seconds. If the limit cannot be checked, job submissions are refused with Retry-After: 60 and other requests continue.

Credits#

  • Credit is counted in seconds of media. GET /me returns your plan and remaining_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_credits with details.required_seconds. Media longer than your plan allows returns 403 plan_limit. Neither is charged.
  • Credits are not refunded if processing fails after the job was accepted, as in the web app.