API reference

Fjalim API reference

Version 1.0.0. Generated from apps/api/openapi.yaml at build time.

Fjalim turns Albanian speech into text. Upload a file, start a transcription, and read the transcript as JSON, plain text, SRT or WebVTT.

Prices. €0.012 per minute, billed per started second (200 µ€ per second). All amounts in this API are integer micro-euros (µ€, 1 € = 1,000,000 µ€).

Errors. Every error has the same shape: {"error": {"code": "insufficient_credits", "message": "..."}}.

Webhooks. Set webhook_url when you create a transcription. Fjalim sends a POST with {"id", "event", "created_at", "data"} where data is the transcription object. The Fjalim-Signature header is t=<unix seconds>,v1=<hex HMAC-SHA256>; the HMAC key is your webhook secret and the signed text is "<t>.<raw body>". A 2xx response confirms delivery. Otherwise Fjalim retries after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h, then gives up. Each request times out after 10 s.

Servers

  • https://api.fjalim.com : Production
  • http://localhost:8787 : Local development

Authentication

  • bearerKey: An API key: Authorization: Bearer fj_live_.... Create keys in the web app.
  • sessionCookie: Web app session. State-changing requests must come from an allowed Origin.
  • trialToken:

Uploads

POST /v1/uploads

Start an upload

Auth: bearerKey, sessionCookie

Returns a presigned URL (valid for one hour). PUT exactly size_bytes bytes to it with the returned headers; the URL is signed for that length, so a different size is refused by storage. Then call the complete route.

Request body application/json

NameTypeDescription
filenamerequired string

content_typerequired string

size_bytesrequired integer

{
  "filename": "intervista-kryeministri.mp3",
  "content_type": "audio/mpeg",
  "size_bytes": 18874368
}

Responses

201Upload created.

application/json

NameTypeDescription
idrequired string (uuid)

upload_urlrequired string (uri)

methodrequired "PUT"

headersrequired object

Send these headers with the PUT.

expires_atrequired string (date-time)

400The request is not valid (invalid_request).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "invalid_request",
    "message": "size_bytes: The file is larger than the 2147483648 byte limit."
  }
}
401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
429More than 60 requests in a minute (rate_limited). See Retry-After.
  • Header Retry-After integer

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

POST /v1/uploads/{id}/complete

Confirm that the file was uploaded

Auth: bearerKey, sessionCookie

Fjalim checks that the object exists and that its size matches size_bytes.

Parameters

NameInTypeDescription
idrequired path string (uuid)

Responses

200The upload is ready to transcribe.

application/json

NameTypeDescription
idrequired string (uuid)

statusrequired "uploaded"

size_bytesrequired integer

400The file is not there yet (upload_incomplete) or has a different size (size_mismatch).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

404Not found (not_found).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "not_found",
    "message": "Transcription not found."
  }
}

Transcriptions

GET /v1/transcriptions

List transcriptions, newest first

Auth: bearerKey, sessionCookie

Parameters

NameInTypeDescription
limit query integer

cursor query string (uuid)

The next_cursor of the previous page.

Responses

200One page.

application/json

NameTypeDescription
datarequired Transcription[]

data[].idrequired string (uuid)

data[].statusrequired "queued" | "processing" | "completed" | "failed" | "canceled"

data[].created_atrequired string (date-time)

data[].completed_atrequired string (date-time) | null

data[].duration_secondsrequired number | null

data[].billed_secondsrequired integer | null

data[].cost_micro_eurrequired integer | null

data[].errorrequired object | null

data[].error.code string

data[].error.message string

data[].metadatarequired object

data[].versionrequired integer

Latest transcript version. 0 until the transcript exists.

data[].filenamerequired string | null

data[].content_typerequired string | null

next_cursorrequired string (uuid) | null

401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
POST /v1/transcriptions

Start a transcription

Auth: bearerKey, sessionCookie

Send exactly one of upload_id and audio_url (https only). The balance must be at least 200 µ€, otherwise the answer is 402 insufficient_credits. Send an Idempotency-Key header to make retries safe: the same key with the same body returns the same transcription. The same key with a different body is refused with 422 idempotency_conflict. Billing happens when the worker has measured the audio duration. The uploaded object is checked again here: 400 upload_incomplete if it is missing, 400 size_mismatch if its size changed.

Parameters

NameInTypeDescription
Idempotency-Key header string

Request body application/json

NameTypeDescription
upload_id string (uuid)

audio_url string (uri)

https only; must not point to a private address.

webhook_url string (uri)

metadata object

Your own data, at most 4 KB as JSON.

options object

Reserved. Send nothing or an empty object.

{
  "upload_id": "0b6f1c2e-7b0a-4a52-9c1f-3c5d1a8e2f10",
  "webhook_url": "https://shembull.al/webhooks/fjalim",
  "metadata": {
    "emisioni": "Studio Live",
    "episodi": 42
  }
}
{
  "audio_url": "https://shembull.al/podcast/episodi-42.mp3"
}

Responses

202The transcription is queued.

application/json

NameTypeDescription
idrequired string (uuid)

statusrequired "queued" | "processing" | "completed" | "failed" | "canceled"

created_atrequired string (date-time)

completed_atrequired string (date-time) | null

duration_secondsrequired number | null

billed_secondsrequired integer | null

cost_micro_eurrequired integer | null

errorrequired object | null

error.code string

error.message string

metadatarequired object

versionrequired integer

Latest transcript version. 0 until the transcript exists.

filenamerequired string | null

content_typerequired string | null

{
  "id": "5f4a3c2b-1d0e-4f9a-8b7c-6d5e4f3a2b1c",
  "status": "queued",
  "created_at": "2026-11-03T09:12:40.000Z",
  "completed_at": null,
  "duration_seconds": null,
  "billed_seconds": null,
  "cost_micro_eur": null,
  "error": null,
  "metadata": {
    "emisioni": "Studio Live",
    "episodi": 42
  },
  "version": 0,
  "filename": "intervista-kryeministri.mp3",
  "content_type": "audio/mpeg"
}
400The request is not valid (invalid_request).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "invalid_request",
    "message": "size_bytes: The file is larger than the 2147483648 byte limit."
  }
}
401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
402The balance is below 200 µ€ (insufficient_credits).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "insufficient_credits",
    "message": "Your balance is too low. Add credit to start a transcription."
  }
}
404Not found (not_found).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "not_found",
    "message": "Transcription not found."
  }
}
422The Idempotency-Key was already used with a different request body (idempotency_conflict).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "idempotency_conflict",
    "message": "This Idempotency-Key was already used with a different request body."
  }
}
429More than 60 requests in a minute (rate_limited). See Retry-After.
  • Header Retry-After integer

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

GET /v1/transcriptions/{id}

Get a transcription

Auth: bearerKey, sessionCookie

Parameters

NameInTypeDescription
idrequired path string (uuid)

Responses

200The transcription.

application/json

NameTypeDescription
idrequired string (uuid)

statusrequired "queued" | "processing" | "completed" | "failed" | "canceled"

created_atrequired string (date-time)

completed_atrequired string (date-time) | null

duration_secondsrequired number | null

billed_secondsrequired integer | null

cost_micro_eurrequired integer | null

errorrequired object | null

error.code string

error.message string

metadatarequired object

versionrequired integer

Latest transcript version. 0 until the transcript exists.

filenamerequired string | null

content_typerequired string | null

401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
404Not found (not_found).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "not_found",
    "message": "Transcription not found."
  }
}
DELETE /v1/transcriptions/{id}

Delete the audio and every transcript version

Auth: bearerKey, sessionCookie

The billing fields stay on the record. This cannot be undone.

Parameters

NameInTypeDescription
idrequired path string (uuid)

Responses

204Deleted.
401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
404Not found (not_found).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "not_found",
    "message": "Transcription not found."
  }
}
GET /v1/transcriptions/{id}/transcript

Read the transcript

Auth: bearerKey, sessionCookie

The response carries ETag and X-Transcript-Version. Caption options apply only when the transcript has no edited captions.

Parameters

NameInTypeDescription
idrequired path string (uuid)

format query "json" | "txt" | "srt" | "vtt"

version query integer

Defaults to the latest version.

timestamps query boolean

txt only. Put [hh:mm:ss] before each segment.

speakers query boolean

txt only. Put the speaker name before each segment.

max_chars_per_line query integer

max_lines query integer

max_cps query number

min_duration query number

max_duration query number

min_gap query number

Responses

200The transcript in the requested format.
  • Header ETag string The version, for example `"2"`.
  • Header X-Transcript-Version integer
  • Header Content-Disposition string `attachment` with a file name made from the upload's name and the format, for example `attachment; filename="intervista.srt"; filename*=UTF-8''intervist%C3%AB.srt`. The plain `filename` is ASCII; `filename*` (RFC 6266 / RFC 5987) keeps characters such as ë and ç. Without an upload name the file is called `transcript.<format>`.

application/json

NameTypeDescription
versionrequired 1

languagerequired "sq"

modelrequired string

durationrequired number

textrequired string

segmentsrequired Segment[]

segments[].idrequired integer

segments[].startrequired number

segments[].endrequired number

segments[].speakerrequired string | null

segments[].textrequired string

segments[].wordsrequired Word[]

segments[].words[].textrequired string

segments[].words[].startrequired number

segments[].words[].endrequired number

segments[].words[].confidencerequired number | null

captionsrequired Cue[] | null

Edited caption cues. null until someone edits captions; exports then derive cues from the words.

text/plain · string

Mirëdita. Sot flasim për zgjedhjet.

application/x-subrip · string

1
00:00:00,320 --> 00:00:02,900
Mirëdita.
Sot flasim për zgjedhjet.

text/vtt · string

WEBVTT

1
00:00:00.320 --> 00:00:02.900
Mirëdita.
Sot flasim për zgjedhjet.
401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
404Not found (not_found).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "not_found",
    "message": "Transcription not found."
  }
}
409The transcript is not ready (not_ready) or the job failed (transcript_unavailable).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

PUT /v1/transcriptions/{id}/transcript

Save an edited transcript as a new version

Auth: bearerKey, sessionCookie

Send the version you edited in If-Match. If someone saved in between, the answer is 409 version_conflict.

Parameters

NameInTypeDescription
idrequired path string (uuid)

If-Matchrequired header string

Request body application/json

NameTypeDescription
versionrequired 1

languagerequired "sq"

modelrequired string

durationrequired number

textrequired string

segmentsrequired Segment[]

segments[].idrequired integer

segments[].startrequired number

segments[].endrequired number

segments[].speakerrequired string | null

segments[].textrequired string

segments[].wordsrequired Word[]

segments[].words[].textrequired string

segments[].words[].startrequired number

segments[].words[].endrequired number

segments[].words[].confidencerequired number | null

captionsrequired Cue[] | null

Edited caption cues. null until someone edits captions; exports then derive cues from the words.

Responses

200Saved. version is the new version.

application/json

NameTypeDescription
idrequired string (uuid)

statusrequired "queued" | "processing" | "completed" | "failed" | "canceled"

created_atrequired string (date-time)

completed_atrequired string (date-time) | null

duration_secondsrequired number | null

billed_secondsrequired integer | null

cost_micro_eurrequired integer | null

errorrequired object | null

error.code string

error.message string

metadatarequired object

versionrequired integer

Latest transcript version. 0 until the transcript exists.

filenamerequired string | null

content_typerequired string | null

400The request is not valid (invalid_request).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "invalid_request",
    "message": "size_bytes: The file is larger than the 2147483648 byte limit."
  }
}
401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
404Not found (not_found).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "not_found",
    "message": "Transcription not found."
  }
}
409version_conflict (stale If-Match) or not_ready.

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "version_conflict",
    "message": "The transcript changed. Reload it and apply your edit again."
  }
}
428If-Match is missing (precondition_required).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

GET /v1/transcriptions/{id}/audio

Play the original audio

Auth: bearerKey, sessionCookie

Redirects to a presigned URL that works for one hour.

Parameters

NameInTypeDescription
idrequired path string (uuid)

Responses

302Redirect to the audio file.
  • Header Location string (uri)
401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
404Not found (not_found).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "not_found",
    "message": "Transcription not found."
  }
}

Account

GET /v1/balance

Current balance

Auth: bearerKey, sessionCookie

Responses

200The balance in micro-euros. It can be negative after a refund.

application/json

NameTypeDescription
balance_micro_eurrequired integer

currencyrequired "EUR"

401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
GET /v1/usage

Usage per day

Auth: bearerKey, sessionCookie

Parameters

NameInTypeDescription
from query string (date)

First day (UTC), default 30 days before to.

to query string (date)

Last day (UTC), default today.

Responses

200Billed seconds and cost for each day that had usage.

application/json

NameTypeDescription
fromrequired string (date)

torequired string (date)

daysrequired object[]

days[].daterequired string (date)

days[].secondsrequired integer

days[].cost_micro_eurrequired integer

days[].transcriptionsrequired integer

400The request is not valid (invalid_request).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "invalid_request",
    "message": "size_bytes: The file is larger than the 2147483648 byte limit."
  }
}
401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
POST /auth/signup

Create an account (web app)

Auth: none

Sets the fj_session cookie. The email must be verified to receive the 15 free minutes. The answer is the same whether or not the email is already registered, so it cannot be used to find out who has an account. If the account exists, its owner gets an email instead, and the cookie you receive is not a valid session.

Request body application/json

NameTypeDescription
emailrequired string (email)

passwordrequired string

name string

locale "sq" | "en"

{
  "email": "arta@shembull.al",
  "password": "një-fjalëkalim-i-gjatë",
  "name": "Arta Krasniqi",
  "locale": "sq"
}

Responses

201Account created.

application/json

NameTypeDescription
account Account

account.id string (uuid)

account.email string (email)

account.name string | null

account.locale "sq" | "en"

account.email_verified boolean

account.balance_micro_eur integer

account.created_at string (date-time)

400The request is not valid (invalid_request).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "invalid_request",
    "message": "size_bytes: The file is larger than the 2147483648 byte limit."
  }
}
POST /auth/login

Sign in (web app)

Auth: none

Request body application/json

NameTypeDescription
emailrequired string (email)

passwordrequired string

Responses

200Signed in. Sets the fj_session cookie (30 days).

application/json

NameTypeDescription
account Account

account.id string (uuid)

account.email string (email)

account.name string | null

account.locale "sq" | "en"

account.email_verified boolean

account.balance_micro_eur integer

account.created_at string (date-time)

401Wrong email or password (invalid_credentials).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

POST /auth/logout

Sign out (web app)

Auth: bearerKey, sessionCookie

Responses

204Signed out.
GET /auth/me

The signed-in account (web app)

Auth: sessionCookie

Responses

200The account.

application/json

NameTypeDescription
account Account

account.id string (uuid)

account.email string (email)

account.name string | null

account.locale "sq" | "en"

account.email_verified boolean

account.balance_micro_eur integer

account.created_at string (date-time)

401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
POST /auth/verify

Confirm an email address

Auth: none

Credits the 900 s signup grant the first time only.

Request body application/json

NameTypeDescription
tokenrequired string

Responses

200Verified.

application/json

NameTypeDescription
verified boolean

signup_grant boolean

True when this call credited the free minutes.

400The link is invalid or expired (invalid_token).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

POST /auth/resend

Send the verification email again

Auth: sessionCookie

Responses

200The email was already verified.
202Sent.
POST /auth/password/forgot

Ask for a password reset email

Auth: none

Always answers 202, whether or not the account exists.

Request body application/json

NameTypeDescription
emailrequired string (email)

Responses

202If the account exists
POST /auth/password/reset

Choose a new password

Auth: none

Ends every session of the account.

Request body application/json

NameTypeDescription
tokenrequired string

passwordrequired string

Responses

200Password changed.
400The link is invalid or expired (invalid_token).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

GET /account/keys

List API keys (web app)

Auth: sessionCookie

Responses

200Keys without their secret part.

application/json

NameTypeDescription
data ApiKey[]

data[].idrequired string (uuid)

data[].namerequired string

data[].prefixrequired string

The first 12 characters of the key.

data[].created_atrequired string (date-time)

data[].last_used_atrequired string (date-time) | null

data[].revoked_atrequired string (date-time) | null

POST /account/keys

Create an API key (web app)

Auth: sessionCookie

The full key is returned once. Store it safely.

Request body application/json

NameTypeDescription
namerequired string

{
  "name": "Serveri i prodhimit"
}

Responses

201Created.

application/json

NameTypeDescription
idrequired string (uuid)

namerequired string

prefixrequired string

The first 12 characters of the key.

created_atrequired string (date-time)

last_used_atrequired string (date-time) | null

revoked_atrequired string (date-time) | null

keyrequired string

DELETE /account/keys/{id}

Revoke an API key (web app)

Auth: sessionCookie

Parameters

NameInTypeDescription
idrequired path string (uuid)

Responses

204Revoked.
404Not found (not_found).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "not_found",
    "message": "Transcription not found."
  }
}
GET /account/webhook-secret

Webhook signing secret (web app)

Auth: sessionCookie

Responses

200The secret.

application/json

NameTypeDescription
webhook_secret string

POST /account/webhook-secret/rotate

Create a new webhook signing secret (web app)

Auth: sessionCookie

Responses

200The new secret.

application/json

NameTypeDescription
webhook_secret string

POST /billing/checkout

Add credit with Stripe Checkout (web app)

Auth: sessionCookie

Request body application/json

NameTypeDescription
amount_eurrequired integer

Whole euros.

{
  "amount_eur": 25
}

Responses

200Send the browser to this URL.

application/json

NameTypeDescription
url string (uri)

400The request is not valid (invalid_request).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "invalid_request",
    "message": "size_bytes: The file is larger than the 2147483648 byte limit."
  }
}
GET /billing/ledger

Credit history (web app)

Auth: sessionCookie

Parameters

NameInTypeDescription
limit query integer

cursor query string

Responses

200Newest first.

application/json

NameTypeDescription
data LedgerEntry[]

data[].id integer

data[].amount_micro_eur integer

Positive for credit

data[].kind "topup" | "signup_grant" | "usage" | "usage_refund" | "refund" | "adjustment"

data[].job_id string (uuid) | null

data[].note string | null

data[].created_at string (date-time)

next_cursor string | null

Trial

POST /trial/uploads

Start a free trial upload

Auth: none

No account needed. The trial transcribes the first 60 seconds of one file. Limits: 3 per IP address per rolling 24 hours and 500 per UTC day overall (429 trial_limit or 429 trial_capacity). The Retry-After header says how many seconds until the limit frees up: until the oldest trial of that IP turns 24 hours old, or until the next UTC midnight for the overall cap. Files can be up to 100 MB.

Request body application/json

NameTypeDescription
filenamerequired string

content_typerequired string

size_bytesrequired integer

turnstile_token string

{
  "filename": "mesazh-zanor.m4a",
  "content_type": "audio/mp4",
  "size_bytes": 1048576
}

Responses

201PUT the file to upload_url, then start the transcription with trial_token.

application/json

NameTypeDescription
trial_tokenrequired string

upload_idrequired string (uuid)

upload_urlrequired string (uri)

methodrequired "PUT"

headersrequired object

expires_atrequired string (date-time)

400The request is not valid (invalid_request).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "invalid_request",
    "message": "size_bytes: The file is larger than the 2147483648 byte limit."
  }
}
403The human check failed (turnstile_failed).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

429trial_limit or trial_capacity. See Retry-After.
  • Header Retry-After integer Seconds until the limit frees up.

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

POST /trial/transcriptions

Start the free trial transcription

Auth: trialToken

Request body application/json

NameTypeDescription
upload_idrequired string (uuid)

Responses

202Queued.

application/json

NameTypeDescription
idrequired string (uuid)

statusrequired "queued" | "processing" | "completed" | "failed" | "canceled"

created_atrequired string (date-time)

completed_atrequired string (date-time) | null

duration_secondsrequired number | null

billed_secondsrequired integer | null

cost_micro_eurrequired integer | null

errorrequired object | null

error.code string

error.message string

metadatarequired object

versionrequired integer

Latest transcript version. 0 until the transcript exists.

filenamerequired string | null

content_typerequired string | null

400upload_incomplete or size_mismatch.

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
409trial_used: the free trial covers one file.

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

GET /trial/transcriptions/{id}

Poll the free trial transcription

Auth: trialToken

Parameters

NameInTypeDescription
idrequired path string (uuid)

Responses

200The transcription, plus transcript once it is completed.

application/json

NameTypeDescription
idrequired string (uuid)

statusrequired "queued" | "processing" | "completed" | "failed" | "canceled"

created_atrequired string (date-time)

completed_atrequired string (date-time) | null

duration_secondsrequired number | null

billed_secondsrequired integer | null

cost_micro_eurrequired integer | null

errorrequired object | null

error.code string

error.message string

metadatarequired object

versionrequired integer

Latest transcript version. 0 until the transcript exists.

filenamerequired string | null

content_typerequired string | null

transcript Transcript

transcript.versionrequired 1

transcript.languagerequired "sq"

transcript.modelrequired string

transcript.durationrequired number

transcript.textrequired string

transcript.segmentsrequired Segment[]

transcript.segments[].idrequired integer

transcript.segments[].startrequired number

transcript.segments[].endrequired number

transcript.segments[].speakerrequired string | null

transcript.segments[].textrequired string

transcript.segments[].wordsrequired Word[]

transcript.captionsrequired Cue[] | null

Edited caption cues. null until someone edits captions; exports then derive cues from the words.

401Missing or invalid credentials (unauthorized).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
404Not found (not_found).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

{
  "error": {
    "code": "not_found",
    "message": "Transcription not found."
  }
}

Operations

POST /billing/stripe/webhook

Stripe events (called by Stripe)

Auth: none

Verifies Stripe-Signature on the raw body. checkout.session.completed (paid) adds credit from amount_total; charge.refunded removes the refunded amount. Repeated events are ignored.

Request body application/json

Responses

200Received (a repeated event answers duplicate true).
400The signature is not valid (invalid_signature).

application/json

NameTypeDescription
errorrequired object

error.coderequired string

Stable machine-readable code.

error.messagerequired string

GET /healthz

Process is up

Auth: none

Responses

200OK.
GET /readyz

Database and storage are reachable

Auth: none

Responses

200Ready.
503A dependency is down.
GET /openapi.json

This document as JSON

Auth: none

Responses

200The OpenAPI document.