Ramadan Last 10 Nights Campaign

Used by thousands of students worldwide - help us reach 150 members

... / 150 members...
APIOCR & Digitized Books

OCR & Digitized Books

/ocr

Upload PDFs or page images for asynchronous OCR, poll job status, and read, search, edit, import and export the resulting digitized books ("BookTexts"). The flow is: POST /ocr uploads the file to S3, creates a pending BookText and enqueues an SQS job; an OCR worker posts pages and the final result back through the /ocr/result callbacks; clients poll GET /ocr/status/:jobId and then fetch the text with GET /ocr/text/:jobId or GET /ocr/books/:bookTextId. Page credits are deducted (and overage billed at $0.02/page) only when a job is reported as completed.

POST/ocr/result/page

Submit a single OCR page result (worker callback)

Auth requirededitor / admin only

Callback used by the OCR worker to push incremental, per-page results while a job is still being processed. It is not intended for end-user clients. The page is upserted into the BookTextPage collection keyed by (BookText, pageNumber), and the parent BookText is updated: status is set to "processing", metadata.pagesProcessed and metadata.lastPageUpdate are written, and pageCount is updated when provided. If no BookText exists for the jobId (an orphaned job) one is created with status "processing" and language "ar". When status is "failed" and an error string is supplied, the error is recorded in the BookText metadata.pageErrors array (replacing any existing entry for the same page number). No page credits are recorded by this endpoint.

Body parameters

jobIdrequired
string
OCR job id (UUID) returned by POST /ocr and carried in the SQS message.
fileIdrequired
string
ObjectId of the uploaded File record. Only used when a BookText has to be created for an orphaned job.
userIdrequired
string
ObjectId of the user who owns the job. Only used when a BookText has to be created for an orphaned job.
pageNumberrequired
number
Page number this result belongs to. 0 is accepted; only undefined is rejected.
status
string
Page-level status. Only the value "failed" (together with error) has an effect: it records a page error.
text
string
Extracted text for the page.Default: ''
error
string
Error message for the page; stored in metadata.pageErrors when status is "failed".
s3Key
string
S3 key of the rendered page image.Default: null
pageCount
number
Total number of pages in the document. Updates BookText.pageCount when truthy.
pagesProcessed
number
Number of pages processed so far; stored in metadata.pagesProcessed.

Errors

  • 400Missing jobId, fileId, userId or pageNumber
  • 401No bearer token provided
  • 403Invalid token, or the user is neither editor nor admin
  • 500Error processing page update
Request
curl -X POST "https://app.ummahspot.com/ocr/result/page" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jobId":"string","fileId":"string","userId":"string","pageNumber":0}'
Response · 200
{
  "success": true
}
POST/ocr/result

Submit the final OCR result for a job (worker callback)

Auth requirededitor / admin only

Callback used by the OCR worker to post the final result of a job once processing finishes (or fails). It is not intended for end-user clients. The BookText for the jobId is updated (or created if the job is orphaned). If extractedText is longer than 10,000 characters it is uploaded to S3 as ocr-results/<jobId>.txt, metadata.textS3Key and metadata.textLength are set, and only a 1,000-character preview is kept in the database; shorter text is stored inline. Supplied metadata is merged into the existing metadata. Each entry in pages is upserted into the BookTextPage collection by pageNumber. Page-credit accounting happens here: when status is "completed" and the page count (pageCount, else pages.length, else the stored pageCount) is greater than 0, usage is recorded against the userId given in the body. Pages are first taken from the remaining monthly tier credits; any pages beyond that are billed immediately at $0.02/page through a Stripe invoice, and a usage log is written. Owners with the editor or admin role are never charged. The free/overage breakdown is saved to metadata.usageInfo. A failure while recording usage does not fail the request: it is stored in metadata.usageError and usageInfo is returned as null. Usage is recorded on every call that reports status "completed".

Body parameters

jobIdrequired
string
OCR job id (UUID).
fileIdrequired
string
ObjectId of the uploaded File record. Only used when a BookText has to be created for an orphaned job.
userIdrequired
string
ObjectId of the user who owns the job. Page usage is recorded against this user.
extractedText
string
Full extracted text of the document. Stored in S3 when longer than 10,000 characters.
language
string
Language code. Keeps the existing value when omitted ("ar" for newly created records).
pageCount
number
Total number of pages. Keeps the existing value when omitted.
pages
array
Array of page objects: { pageNumber: number, text?: string, s3Key?: string, isAIGenerated?: boolean (default true) }. Each is upserted by pageNumber.
metadata
object
Arbitrary metadata merged into BookText.metadata (only merged when extractedText is also provided, or used as the initial metadata of an orphaned job).
status
string
New job status: pending, processing, completed, failed or submission_failed. "completed" triggers page-usage recording and sets completedAt.
error
string
Job-level error message.
processingTime
number
Processing time in milliseconds.

Errors

  • 400Missing jobId, fileId or userId
  • 401No bearer token provided
  • 403Invalid token, or the user is neither editor nor admin
  • 500Error saving OCR result (database, S3 upload or validation failure)
Request
curl -X POST "https://app.ummahspot.com/ocr/result" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jobId":"string","fileId":"string","userId":"string"}'
Response · 201
{
  "message": "OCR result saved successfully",
  "bookTextId": "66f1c0a7e4b0a1d2c3f4a5b6",
  "status": "completed",
  "usageInfo": {
    "freePages": 12,
    "overagePages": 0,
    "overageChargeCents": 0,
    "totalPagesUsed": 212,
    "remainingCredits": 788,
    "paymentStatus": "not_required"
  }
}
POST/ocr

Upload a document and create an OCR job

Auth requiredSubscription required

Starts the asynchronous OCR flow. Requires an active subscription (users with the editor or admin role bypass the subscription check); the check runs before the upload is parsed. The uploaded file is stored in S3 under ocr/<uuid>-<originalname>, a File record is created, a BookText is created with status "pending", and a job message (jobId, fileId, userId, S3 location, file name/type/size, language, customPrompt, metadata) is sent to the OCR SQS queue. The call returns immediately with the jobId: poll GET /ocr/status/:jobId until status is "completed" or "failed", then fetch the text with GET /ocr/text/:jobId or GET /ocr/books/:bookTextId. Having remaining credits is not required to upload: no credits are deducted at upload time. Pages are counted when the worker reports the job as completed; pages beyond the monthly tier credits are billed at $0.02/page. usageInfo in the response is a snapshot of the caller's credits at upload time (for editors/admins remainingCredits and tierCredits are null, meaning unlimited). Note that visibility is stored on the File record only; the BookText is always created private and can be made public with PATCH /ocr/books/:bookTextId. If the SQS send fails the request returns 500 although the File and pending BookText records have already been created.

Send the body as multipart/form-data.

Body parameters

filerequired
file
The document to OCR (single file). Accepted MIME types: application/pdf, image/png, image/jpeg, image/jpg, image/tiff, image/bmp, image/webp. Maximum size 1000 MB.
fileName
string
Display name for the document.Default: original file name
language
string
Language code of the document, passed to the worker and stored on the BookText.Default: ar
author
string
Author stored on the File record.Default: Unknown
tags
string
Comma-separated list of tags.Default: ''
categories
string
Comma-separated list of categories. When empty the File record gets the category "ocr" and the BookText gets none.Default: ''
visibility
string
Visibility of the File record: "public" or "private". Does not affect the BookText visibility.Default: private
customPrompt
string
Custom prompt forwarded to the OCR worker in the job message.
jobMetadata
string
JSON-encoded object stored as the BookText metadata and forwarded to the worker. Must be valid JSON.

Errors

  • 400No file uploaded
  • 401No bearer token provided
  • 403Invalid token, or no active subscription (error: "SUBSCRIPTION_REQUIRED", subscriptionRequired: true)
  • 500Failed to verify subscription status; unsupported file type or file too large (rejected by the upload parser and returned as a plain-text 500); invalid jobMetadata JSON; or S3/database/SQS failure ("Error creating OCR job")
Request
curl -X POST "https://app.ummahspot.com/ocr" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -F "file=@/path/to/file"
Response · 201
{
  "message": "OCR job created successfully",
  "jobId": "0b9d6c1e-6f0a-4d5e-9a57-2f3d1c8e7a41",
  "bookTextId": "66f1c0a7e4b0a1d2c3f4a5b6",
  "fileId": "66f1c0a6e4b0a1d2c3f4a5b1",
  "fileName": "matn-al-ajrumiyyah.pdf",
  "s3Key": "ocr/5d2f8a90-3c1b-4e7f-8a21-9b0c4d6e7f10-matn-al-ajrumiyyah.pdf",
  "sqsMessageId": "c2a1f6d4-8e7b-4a39-b5d0-1f2e3d4c5b6a",
  "status": "pending",
  "usageInfo": {
    "remainingCredits": 800,
    "tierCredits": 1000,
    "pagesUsed": 200,
    "billingPeriodEnd": "2026-10-01T00:00:00.000Z"
  }
}
GET/ocr/status/:jobId

Get the status of an OCR job

Auth required

Poll this endpoint after POST /ocr. Returns the job state without any text. status is one of pending, processing, completed, failed or submission_failed. hasFullText is true when the full text was large enough to be stored in S3. Only the user who created the job can view it; admins and editors can view any job.

Path parameters

jobId
string
Job id (UUID) returned by POST /ocr

Errors

  • 401No bearer token provided
  • 403Invalid token, or the job belongs to another user ("Unauthorized to view this job")
  • 404OCR job not found
  • 500Error retrieving OCR job status
Request
curl -X GET "https://app.ummahspot.com/ocr/status/:jobId" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "success": true,
  "job": {
    "jobId": "0b9d6c1e-6f0a-4d5e-9a57-2f3d1c8e7a41",
    "fileId": "66f1c0a6e4b0a1d2c3f4a5b1",
    "fileName": "matn-al-ajrumiyyah.pdf",
    "status": "completed",
    "language": "ar",
    "pageCount": 12,
    "processingTime": 48213,
    "createdAt": "2026-09-18T10:15:00.000Z",
    "updatedAt": "2026-09-18T10:16:02.000Z",
    "completedAt": "2026-09-18T10:16:02.000Z",
    "hasFullText": true
  }
}
GET/ocr/text/:jobId

Get the extracted text for an OCR job

Auth required

Returns the full extracted text and every page of a job in a single response (pages are not paginated; use GET /ocr/books/:bookTextId for paginated pages with image URLs). If the full text was stored in S3 it is fetched and returned in extractedText instead of the truncated preview. Pages come from the BookTextPage collection sorted by pageNumber, falling back to the legacy embedded pages array for old documents. The endpoint does not check the job status, so text may be empty or partial while the job is still processing. Only the user who created the job can read it; admins and editors can read any job.

Path parameters

jobId
string
Job id (UUID) returned by POST /ocr

Errors

  • 401No bearer token provided
  • 403Invalid token, or the job belongs to another user ("Unauthorized to view this text")
  • 404Extracted text not found for this job
  • 500Error retrieving full text from storage (S3) or error retrieving extracted text
Request
curl -X GET "https://app.ummahspot.com/ocr/text/:jobId" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "success": true,
  "data": {
    "jobId": "0b9d6c1e-6f0a-4d5e-9a57-2f3d1c8e7a41",
    "fileId": "66f1c0a6e4b0a1d2c3f4a5b1",
    "fileName": "matn-al-ajrumiyyah.pdf",
    "extractedText": "الكلام هو اللفظ المركب المفيد بالوضع",
    "language": "ar",
    "pageCount": 12,
    "pages": [
      {
        "_id": "66f1c0e2e4b0a1d2c3f4a5c0",
        "pageNumber": 1,
        "text": "الكلام هو اللفظ المركب المفيد بالوضع",
        "s3Key": "0b9d6c1e-6f0a-4d5e-9a57-2f3d1c8e7a41/page-1.png",
        "isAIGenerated": true
      }
    ],
    "status": "completed",
    "completedAt": "2026-09-18T10:16:02.000Z"
  }
}
GET/ocr/jobs

List the current user's OCR jobs

Auth required

Returns the OCR jobs created by the authenticated user, newest first, with offset pagination. Always scoped to the caller, including for admins and editors.

Query parameters

status
string
Filter by job status: pending, processing, completed, failed or submission_failed.
limit
number
Maximum number of jobs to return.Default: 20
offset
number
Number of jobs to skip.Default: 0

Errors

  • 401No bearer token provided
  • 403Invalid token
  • 500Error retrieving OCR jobs
Request
curl -X GET "https://app.ummahspot.com/ocr/jobs" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "success": true,
  "total": 7,
  "limit": 20,
  "offset": 0,
  "jobs": [
    {
      "jobId": "0b9d6c1e-6f0a-4d5e-9a57-2f3d1c8e7a41",
      "bookTextId": "66f1c0a7e4b0a1d2c3f4a5b6",
      "fileId": "66f1c0a6e4b0a1d2c3f4a5b1",
      "fileName": "matn-al-ajrumiyyah.pdf",
      "status": "completed",
      "language": "ar",
      "tags": ["nahw"],
      "categories": ["grammar"],
      "pageCount": 12,
      "processingTime": 48213,
      "createdAt": "2026-09-18T10:15:00.000Z",
      "updatedAt": "2026-09-18T10:16:02.000Z",
      "completedAt": "2026-09-18T10:16:02.000Z"
    }
  ]
}
GET/ocr/books/:bookTextId

Get a digitized book with its text and pages

Auth optional

Returns one digitized book with file details, owner, optional full text and a paginated slice of its pages. Public books are readable by anyone, including anonymous callers; private books only by their owner, admins and editors. The owner's userEmail, the source file's s3Key (the book-level s3Key) and the internal metadata object are only returned to the book's owner, admins and editors; for every other caller — including anonymous readers of a public book — those three keys are absent from the response. The example below shows the owner's view. The page-level s3Key inside pages[] is returned to every caller. When includeText is "true" the full text is returned in extractedText (fetched from S3 if it was stored there); any other value omits the field. Pages are sorted by pageNumber and paginated with pageLimit/pageOffset; totalPages and hasMore describe the pagination. Each page with an s3Key gets imageUrl, a presigned URL of the page image valid for 1 hour (null if there is no image or signing fails). Old documents without BookTextPage records fall back to the legacy embedded pages array.

Path parameters

bookTextId
string
BookText ObjectId

Query parameters

pageLimit
number
Maximum number of pages to return.Default: 50
pageOffset
number
Number of pages to skip.Default: 0
includeText
string
Set to anything other than "true" (e.g. "false") to omit extractedText and avoid loading the full text.Default: true

Errors

  • 403Book is private and the caller is not its owner, an editor or an admin
  • 404Book not found
  • 500Error retrieving full text from storage (S3) or error retrieving book (including a malformed bookTextId)
Request
curl -X GET "https://app.ummahspot.com/ocr/books/:bookTextId" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "success": true,
  "book": {
    "bookTextId": "66f1c0a7e4b0a1d2c3f4a5b6",
    "jobId": "0b9d6c1e-6f0a-4d5e-9a57-2f3d1c8e7a41",
    "fileId": "66f1c0a6e4b0a1d2c3f4a5b1",
    "fileName": "matn-al-ajrumiyyah.pdf",
    "author": "Ibn Ajurrum",
    "fileType": "application/pdf",
    "fileSize": 1843200,
    "s3Key": "ocr/5d2f8a90-3c1b-4e7f-8a21-9b0c4d6e7f10-matn-al-ajrumiyyah.pdf",
    "userId": "65a0f3b2e4b0a1d2c3f4a111",
    "username": "abdullah",
    "userEmail": "abdullah@example.com",
    "extractedText": "الكلام هو اللفظ المركب المفيد بالوضع",
    "language": "ar",
    "tags": ["nahw"],
    "categories": ["grammar"],
    "pageCount": 12,
    "totalPages": 12,
    "pageLimit": 50,
    "pageOffset": 0,
    "hasMore": false,
    "pages": [
      {
        "pageNumber": 1,
        "text": "الكلام هو اللفظ المركب المفيد بالوضع",
        "s3Key": "0b9d6c1e-6f0a-4d5e-9a57-2f3d1c8e7a41/page-1.png",
        "imageUrl": "https://sharh-app-ocr-cache.s3.amazonaws.com/arabic-vision/...signed...",
        "isAIGenerated": true
      }
    ],
    "status": "completed",
    "processingTime": 48213,
    "metadata": {
      "pagesProcessed": 12,
      "usageInfo": {
        "freePages": 12,
        "overagePages": 0,
        "overageChargeCents": 0
      }
    },
    "createdAt": "2026-09-18T10:15:00.000Z",
    "updatedAt": "2026-09-18T10:16:02.000Z",
    "completedAt": "2026-09-18T10:16:02.000Z",
    "visibility": "public"
  }
}
PATCH/ocr/books/:bookTextId

Update a digitized book

Auth required

Updates book details, visibility, text and/or pages. Only the owner of the book, editors and admins may update it. All fields are optional and only supplied fields change. fileName and author are written to the linked File record. visibility is only applied when it is "public" or "private" (other values are silently ignored). tags and categories accept an array or a comma-separated string. pages can be an array of page objects, each upserted by pageNumber (text defaults to empty, s3Key to null and isAIGenerated to true when omitted; pages not listed are left untouched), or an object keyed by page number for partial updates where only the supplied fields of each page change. The response returns a summary of the book without text or pages.

Path parameters

bookTextId
string
BookText ObjectId

Body parameters

fileName
string
New display name (stored on the File record).
author
string
New author (stored on the File record).
language
string
Language code.
extractedText
string
Replaces the book-level extracted text stored in the database.
pages
array | object
Either an array of { pageNumber, text?, s3Key?, isAIGenerated? } upserted by pageNumber, or an object keyed by page number, e.g. { "3": { "text": "...", "isAIGenerated": false } }, updating only the supplied fields.
visibility
string
"public" or "private".
tags
string[] | string
Array of tags or a comma-separated string. Replaces the existing tags.
categories
string[] | string
Array of categories or a comma-separated string. Replaces the existing categories.

Errors

  • 401No bearer token provided
  • 403Invalid token, or the caller is not the owner, an editor or an admin ("Unauthorized to update this book")
  • 404Book not found
  • 500Error updating book
Request
curl -X PATCH "https://app.ummahspot.com/ocr/books/:bookTextId" \
  -H "Authorization: Bearer $SHARH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"fileName":"string"}'
Response · 200
{
  "success": true,
  "message": "Book updated successfully",
  "book": {
    "bookTextId": "66f1c0a7e4b0a1d2c3f4a5b6",
    "jobId": "0b9d6c1e-6f0a-4d5e-9a57-2f3d1c8e7a41",
    "fileId": "66f1c0a6e4b0a1d2c3f4a5b1",
    "fileName": "متن الآجرومية",
    "author": "Ibn Ajurrum",
    "visibility": "public",
    "language": "ar",
    "tags": ["nahw"],
    "categories": ["grammar"],
    "pageCount": 12,
    "status": "completed",
    "createdAt": "2026-09-18T10:15:00.000Z",
    "updatedAt": "2026-09-18T11:02:40.000Z"
  }
}
GET/ocr/books

List digitized books

Auth optional

Lists digitized books with offset pagination, without text or pages. Visibility rules: anonymous callers only see public books; signed-in members see public books plus their own; admins and editors see all books. Each item includes the owner (userId, username) and hasFullText (true when the full text is stored in S3), which are always present. userEmail and the internal metadata object are only included on items whose book the caller owns, or on every item when the caller is an admin or editor; for other callers — including anonymous ones — those two keys are absent from the item. The example below shows an item as seen by its owner. Default order is newest first. The name_asc/name_desc sort options sort on the referenced file name at the database level, where the book only holds a reference to the file, so they do not reliably order by name.

Query parameters

status
string
Filter by job status: pending, processing, completed, failed or submission_failed.
language
string
Filter by language code, e.g. "ar".
limit
number
Maximum number of books to return.Default: 20
offset
number
Number of books to skip.Default: 0
sort
string
One of date_asc, date_desc, name_asc, name_desc, pages_asc, pages_desc. Unknown values fall back to date_desc.Default: date_desc
tags
string
Comma-separated tags (or repeated parameter); matches books having any of them.
categories
string
Comma-separated categories (or repeated parameter); matches books having any of them.

Errors

  • 500Error retrieving books
Request
curl -X GET "https://app.ummahspot.com/ocr/books" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
{
  "success": true,
  "total": 42,
  "limit": 20,
  "offset": 0,
  "books": [
    {
      "bookTextId": "66f1c0a7e4b0a1d2c3f4a5b6",
      "jobId": "0b9d6c1e-6f0a-4d5e-9a57-2f3d1c8e7a41",
      "fileId": "66f1c0a6e4b0a1d2c3f4a5b1",
      "fileName": "matn-al-ajrumiyyah.pdf",
      "author": "Ibn Ajurrum",
      "fileType": "application/pdf",
      "fileSize": 1843200,
      "userId": "65a0f3b2e4b0a1d2c3f4a111",
      "username": "abdullah",
      "userEmail": "abdullah@example.com",
      "status": "completed",
      "language": "ar",
      "tags": ["nahw"],
      "categories": ["grammar"],
      "pageCount": 12,
      "processingTime": 48213,
      "metadata": {
        "pagesProcessed": 12
      },
      "visibility": "public",
      "hasFullText": true,
      "createdAt": "2026-09-18T10:15:00.000Z",
      "updatedAt": "2026-09-18T10:16:02.000Z",
      "completedAt": "2026-09-18T10:16:02.000Z"
    }
  ]
}
POST/ocr/books/:bookTextId/import

Import a digitized book as a translation Book

Auth required

Creates a new Book (the bilingual, line-based format used by the /books API) from a digitized book. Every OCR page becomes one line with the page text in the Arabic field and empty English, commentary and rootwords fields. The new book takes its title from the file name and its author from the file, is owned by the caller, and records metadata.importedFromVision = bookTextId. Only the owner of the digitized book, editors and admins may import it. Callers who are not editors or admins must additionally have an active Stripe subscription on the $50/month tier, otherwise 403 with error "subscription_required" is returned. The request has no body.

Path parameters

bookTextId
string
BookText ObjectId of the digitized book to import

Errors

  • 400This book has no pages to import
  • 401No bearer token provided
  • 403Invalid token; caller is not the owner, an editor or an admin ("Unauthorized to import this book"); or no active $50/month subscription (error: "subscription_required")
  • 404Book not found
  • 500Unable to verify subscription status (error: "subscription_check_failed") or error importing book
Request
curl -X POST "https://app.ummahspot.com/ocr/books/:bookTextId/import" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 201
{
  "success": true,
  "message": "Book imported successfully",
  "bookId": "66f1d4b3e4b0a1d2c3f4a7d9"
}
GET/ocr/books/:bookTextId/export/docx

Export a digitized book as a Word document

Auth required

Generates a .docx file from all pages of a digitized book (sorted by page number) and returns it as a download. Only the owner of the book, editors and admins may export it; a public book cannot be exported by other users. Callers who are not editors or admins must also have an active subscription (checked before the book is looked up). The document is laid out right-to-left with an Arabic font when the book language is ar, ur, fa or he, otherwise left-to-right. Options control an optional title page (file name, author, language, page count, creation date), a "Page N" heading before each page, whether each page starts on a new page, and the body font size.

Path parameters

bookTextId
string
BookText ObjectId

Query parameters

includeTitlePage
string
"true" to add a title page; any other value disables it.Default: true
includePageHeadings
string
"true" to add a "Page N" heading before each page; any other value disables it.Default: true
pageBreaks
string
"true" to start every OCR page on a new document page; any other value produces one continuous section.Default: true
fontSize
number
Body font size in half-points (24 = 12pt). Clamped to the range 16-40; invalid values fall back to 24.Default: 24

Errors

  • 401No bearer token provided
  • 403Invalid token; no active subscription (error: "SUBSCRIPTION_REQUIRED", subscriptionRequired: true); or caller is not the owner, an editor or an admin ("Unauthorized to export this book")
  • 404Book not found
  • 500Error exporting book
Request
curl -X GET "https://app.ummahspot.com/ocr/books/:bookTextId/export/docx" \
  -H "Authorization: Bearer $SHARH_TOKEN"
Response · 200
Binary file download, not JSON.

Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Disposition: attachment; filename="<url-encoded sanitized file name>.docx"

The body is the generated Word document.