Changelog
All notable changes to the Lecturia SaaS API are documented here, following Keep a Changelog conventions. Subscribe to the Atom feed to track releases.
The API is currently in invite-only beta. Version 1.0.0 marks general
availability; until then, entries ship under 1.0.0-beta. Breaking changes
are always prefixed BREAKING: and link to a migration guide.
[Unreleased]
Nothing yet.
[1.0.0-beta.3] — 2026-09-30
Contract tag: v0.19.0
Added
-
FB2 and DOCX uploads.
formatonPOST /materials/upload-urlacceptsfb2anddocxnext toepub,pdfandtxt, and thefileofPOST /materialsand of a multipartPOST /conversionsmay be an.fb2or.docxfile. The format is derived from the filename extension. -
extraction_warning_detailsonGET /materials/{id}/text-preview: the non-fatal issues recorded while extracting the material, as{ code, message }objects in the order they were recorded, plus an optionalparamsobject with interpolation values for two codes:params.section(1-based section number) onempty_or_image_only_chapterandparams.line_length(line-length threshold) onlong_text_lines. Always present,[]for a clean extraction. Branch oncodeand useparamsto localize a known code; showmessagewhenparamsis missing or the code is unknown, and do not parse numbers frommessage. Known codes:- Format:
no_heading_styles_detected(DOCX),no_headings_or_toc_detected(EPUB),unknown_fb2_tag(FB2),empty_or_image_only_chapter(EPUB, one per affected section),scanned_pdf_ocr(PDF),low_encoding_confidenceandlong_text_lines(TXT). - Text cleanup:
text_cleanup_skipped,text_cleanup_partial,text_cleanup_failed. - Language detection:
language_undetermined,language_text_too_short,language_low_confidence,language_mixed.
The set is open: a new code can appear without a version bump, so handle an unknown code by showing its
message. Each code appears at most once per material, exceptempty_or_image_only_chapter. The existingextraction_warningsstring array carries the same messages in the same order. The API Reference describes every code. - Format:
-
403 consent_required: when Lecturia publishes new terms or a new privacy policy, every call made with a key of an account that has not accepted them is refused with this code until the account owner signs in to app.lecturia.ai and accepts.detailscarriesowed(terms,privacyor both),terms_versionandprivacy_version. The same keys work again once the owner has accepted.DELETE /keys/{id}andDELETE /webhooks/{id}are never refused, so a leaked key can be revoked and a webhook endpoint removed first. See Authentication. -
alignment_statusandalignment_erroron every chunk object inGET /materials/{id},GET /materials/{id}/chunksandGET /conversions/{id}.alignment_statusis the alignment lifecycle of the chunk's current audio:pending,readyorfailed, ornullwhen the chunk has never been aligned.alignment_errorsays why alignment failed whenalignment_statusisfailedand isnullotherwise. Both fields are always present. -
localeonPOST /auth/register: an optional app locale for the account (en,ru,uk,de,es,it). Input is trimmed, lowercased and reduced to its primary language subtag, so regional tags such asde-ATare accepted and stored asde; any other value is422. When omitted, theAccept-Languageheader decides, falling back to English. It sets the language of the emails sent to the account. -
languageonPOST /materialsandPOST /materials/upload-url: an optional ISO 639-1 code (en,es,fr,de,it,ru,uk) naming the language of the file you are uploading. It seeds the OCR recogniser for scanned PDFs, so a Cyrillic scan is read with the Cyrillic model on the first pass instead of falling back to a slower, costlier page-by-page model. Omit it and detection runs as before. A value outside the list is422 validation_failedwithdetails.field = "language". -
Rate limiting is now documented and enforced per API key: 600 requests per minute, metered per key rather than per account. Every response the limiter handles carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset(UTC epoch seconds of the window end). Over budget is429 rate_limitedwithRetry-Afterin seconds. Responses produced before the limiter (a 401, abearer_not_allowed_on_saas403, a CORS preflight) carry no budget headers, and the two/auth/*endpoints are limited per source IP instead.GET /usagereports the calling key's live budget in its body for clients that cannot read headers. -
GET /ereaders/{slug}/sample: sample audio for one e-reader. Returns a presignedaudio_url(valid for 15 minutes) pluslang,available_langs,duration_secondsandformat. The optionallangquery parameter uses a fallback chain (exact match, base subtag, the e-reader's primary language, first available), so a well-formed but unavailable language falls back instead of failing; every miss (unknown slug, inactive e-reader, or no generated sample at all) is a single404 sample_not_available. A malformedlangis422 validation_failedwithdetails.field = "lang". EachGET /ereadersentry already advertises the route via itssample_url. -
sample_langson everyGET /ereadersentry: the BCP 47 language codes the e-reader has a generated sample for. Additive, always present, nevernull; an e-reader without samples reports[]. Use it to tell in advance whetherGET /ereaders/{slug}/samplecan serve audio: an empty array predicts a404 sample_not_availablefor everylang. Note it is not necessarily a subset oflanguages. -
archive_limit_exceededas anerror_codeon afailedmaterial, inGET /materials/{id}andGET /conversions/{id}: the uploaded archive exceeded the entry-count, uncompressed-size or compression-ratio limits, so extraction stopped. Until now this failure was reported asout_of_memory. The new code appears on extractions that fail from this release on; materials that already failed this way keepout_of_memory, and theirerror_codeis not rewritten.error_codeis an open set: branch on the codes you handle and treat any code you do not recognise as a generic extraction failure, as you wouldextraction_failed. The API Reference lists the known codes. -
full_character_countandestimated_duration_minutesonGET /materials/{id}, matching the text preview fields of the same names. Both are always present. Both arenullwhile the material ispending,uploading,extracting,queued_deeporfailedand are set once it isextracted,converting,readyorpartial.estimated_duration_minutesisnullexactly whenfull_character_countisnull.
Changed
estimated_duration_minutesonGET /materials/{id}/text-previewis now always a number>= 0and nevernull. It estimates the narration time of the whole material fromfull_character_countand does not depend on the e-reader.Idempotency-Keyreplay rules are now documented. A replay with the same key and body returns the stored first response, except in two cases that are never stored, so the replay runs the request again and repeats its side effects: a 5xx response, and a response larger than 736 KiB. No current endpoint returns a response that large, so forPOST /tts:synthesizeonly a 5xx makes a retry run (and charge) again. A retry while the first request is still running is409 idempotency_key_reuse.DELETE /keys/{id}now refuses to revoke the account's last active admin-scoped key with409 cannot_revoke_last_admin_key. Rotate instead: mint the replacement admin key first, then revoke the old one. Previously an account could revoke its only admin key and lose all self-serve key management.
[1.0.0-beta.2] — 2026-08-22
Added
currencyon everyGET /ereadersentry and onGET /ereaders/{id}/priceresponses. It is always"EUR": Lecturia quotes and bills in one base currency, soprice_per_1k_charsand everyprice_by_platformamount is euros. Credit fields (credits,credits_per_100k) are unchanged and remain the authoritative unit.
Changed
- BREAKING:
GET /ereadersrenamesprice_per_1k_chars_usdtoprice_per_1k_chars. The value itself is unchanged — read the new field name and the newcurrencyfield instead of assuming dollars. Migration: update your response parsing to the new field names shown in the API Reference; no request changes are needed.
[1.0.0-beta] — 2026-08-13
Added
- Initial public beta of the SaaS API at
https://api.lecturia.ai/v1/saas. See the API Reference for full request/response schemas. - Account & keys:
POST /auth/register,POST /auth/login,GET /me,GET /usage, API key management under/keys(sandbox and live keys). - Materials pipeline: upload via
POST /materials/upload-url+POST /materials/{id}/confirm-upload, extraction status, text preview, reading-layer document, and per-chunk conversion control (convert,convert-next,convert-all) with per-chunk audio URLs. - Direct synthesis:
POST /tts:synthesize(synchronous) andPOST /tts:synthesize-asyncfor raw text without a material. - E-readers & pricing:
GET /ereaders,GET /ereaders/{id}/pricefor upfront credit quotes. - Credits:
GET /credits/balance,GET /credits/transactions. - Conversions:
GET /conversions,GET /conversions/{id}. - Webhooks: CRUD under
/webhooks, HMAC-signed deliveries with delivery log and per-delivery retry,POST /webhooks/{id}/rotate-secret. - Uniform error envelope on every 4xx/5xx:
{ "error": { code, message, details?, request_id } }with stable machine-readable codes.