Skip to content

API reference

The OCR-AI API

Read PDFs and images into text, fields and tables, from an MCP client or over plain HTTPS.

Base URL
https://ocr-ai.com
MCP
https://ocr-ai.com/mcp
Markdown
https://ocr-ai.com/docs.md
01The agent calls the tool02Fields come back, pages are counted03Out of pages: a payment link, nothing charged

01Authentication

Every call that reads or counts pages needs an API key, sent as Authorization: Bearer ocrai_sk_… (or as X-API-Key: ocrai_sk_…).

Keys are created on your account page, after you ask for access on the agents page. A key is shown once, when it is created, and can be revoked there at any time. Keep keys on the server side: never in a web page or a mobile app.

02MCP

Endpoint: /mcp, over the Streamable HTTP transport. The server is stateless: every POST gets one JSON response, with no session and no event stream. Supported protocol versions: 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.

initialize and tools/list work without a key; tools/call needs one. Problems a model can act on (no key, not enough pages, an argument the tool does not accept, a file it cannot read) come back as tool results with isError: true, a sentence for the model, and an error code in structuredContent.

Requests that send no Origin header, and requests from any https origin, are accepted: the API key is the only credential. A page on a null, file: or plain-http origin, or on any other scheme, is refused with 403, at the CORS preflight too.

A call of many pages can take more than a minute. MCP clients stop waiting after a set time (60 seconds by default in the MCP TypeScript SDK): raise it for read_document, or read fewer pages per call. A call whose connection closes before the answer is sent is refunded in full; pages a client stops waiting for while it keeps the connection open are charged.

ToolArgumentsReturns
read_documentfile_url or file_base64 (one of them); pages, e.g. "1-5,8"; hint: auto, invoice, receipt, card, table, text (card is a business card); language of labels and summary: en or heMarkdown text, and structuredContent: the document object
check_balancenonebalance, expired, buy_url, packs
buy_pagespack: p100, p500, p2000; currency: USD (default) or ILSbuy_url for that pack, with its pages and price
mcp.json
{
  "mcpServers": {
    "ocr-ai": {
      "type": "http",
      "url": "https://ocr-ai.com/mcp",
      "headers": {
        "Authorization": "Bearer ocrai_sk_…"
      }
    }
  }
}
Calling a tool without an MCP client
curl https://ocr-ai.com/mcp \
  -H "Authorization: Bearer ocrai_sk_…" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
       "params": {"name": "read_document",
                  "arguments": {"file_url": "https://example.com/invoice.pdf"}}}'

03REST

POST/api/v1/read

Send the document in one of three ways:

  • multipart/form-data with a file field named file, and optional fields pages, hint and language;
  • application/json with file_base64 or file_url (a public https link), and optional pages, hint and language;
  • the file itself, with its own Content-Type (application/pdf, image/jpeg, image/png or image/webp), and the options as query parameters.

The answer is the document object. The file's type is taken from its bytes, not from its name or the declared type.

Fields and query parameters are checked: one the read does not know, one of the wrong type, or a hint or language outside its list answers 400 bad_request, and the message names it.

Every check is made before the answer starts. The 200 status is then sent at once, and the body when the read is done. If the read fails after that, the body breaks off instead of arriving as partial JSON, and every page of the call is given back. A caller that disconnects before the body is sent gets its pages back the same way.

Upload a file
curl https://ocr-ai.com/api/v1/read \
  -H "Authorization: Bearer ocrai_sk_…" \
  -F "file=@invoice.pdf" \
  -F "pages=1-3"
A file by URL
curl https://ocr-ai.com/api/v1/read \
  -H "Authorization: Bearer ocrai_sk_…" \
  -H "Content-Type: application/json" \
  -d '{"file_url": "https://example.com/invoice.pdf", "hint": "invoice"}'
The raw file
curl "https://ocr-ai.com/api/v1/read?pages=1-3&language=he" \
  -H "Authorization: Bearer ocrai_sk_…" \
  -H "Content-Type: application/pdf" \
  --data-binary @invoice.pdf

GET/api/v1/balance

Pages left on the key's account, whether unused pages have expired, and the packs, each with a buy_url. With language=he in the query string, the links open the Hebrew page and the packs are priced in shekels.

Balance
curl https://ocr-ai.com/api/v1/balance \
  -H "Authorization: Bearer ocrai_sk_…"

04The document object

FieldMeaning
object"document"
total_pagesPages in the file.
pages_readPages read in this call.
pages_chargedPages charged in this call. Failed pages are not charged.
balancePages left on the account after this call.
next_pagesThe page range for the next call, when the file has more pages than one call reads; otherwise null.
buy_urlPresent when the account ran out of pages: where a person can add more.
pages[]One entry per page read, in page order: page, status (done, failed or no_pages) and, for a page that was read, partial, document_type, language, summary, fields, tables and text.

Each field has key, label, value and type, and when they apply normalized (dates as YYYY-MM-DD, amounts and numbers as plain decimals), box ([ymin, xmin, ymax, xmax], 0–1000 relative to the page) and uncertain (true when the value was hard to read).

Each table has columns and rows (an array of cells per row), and when known a title and a box.

Example response (shortened)
{
  "object": "document",
  "total_pages": 1,
  "pages_read": 1,
  "pages_charged": 1,
  "balance": 99,
  "next_pages": null,
  "pages": [
    {
      "page": 1,
      "status": "done",
      "partial": false,
      "document_type": "invoice",
      "language": "he",
      "summary": "חשבונית מס 20417 מסטודיו תמר לקפה נמל, על סך 6,018.00 ₪ כולל מע״מ.",
      "fields": [
        {
          "key": "document_number",
          "label": "מספר חשבונית",
          "value": "20417",
          "type": "id",
          "box": [
            124,
            743,
            143,
            798
          ]
        },
        {
          "key": "issue_date",
          "label": "תאריך הפקה",
          "value": "21/10/2026",
          "normalized": "2026-10-21",
          "type": "date",
          "box": [
            144,
            706,
            163,
            798
          ]
        },
        {
          "key": "total",
          "label": "סה״כ לתשלום",
          "value": "6,018.00 ₪",
          "normalized": "6018",
          "type": "money",
          "box": [
            595,
            86,
            618,
            202
          ]
        }
      ],
      "tables": [
        {
          "title": "פירוט החשבונית",
          "columns": [
            "#",
            "תיאור",
            "כמות",
            "מחיר יחידה",
            "סה״כ"
          ],
          "rows": [
            [
              "1",
              "עיצוב שלט חזית",
              "1",
              "2,400.00",
              "2,400.00"
            ],
            [
              "2",
              "הדפסה על לוח אלומיניום 120×60",
              "2",
              "680.00",
              "1,360.00"
            ]
          ],
          "box": [
            349,
            73,
            516,
            928
          ]
        }
      ],
      "text": "סטודיו תמר — שילוט ועיצוב גרפי בע״מ\nרח׳ הנגב 12, באר שבע · טל׳ 08-6230145\n…"
    }
  ]
}

05ERP and CRM

The keys in fields are fixed: the same field always arrives under the same key, so you map them once to your ERP or CRM fields. The machine value is in normalized: dates as YYYY-MM-DD, amounts as decimals.

A flow in Make, n8n, Zapier or Power Automate: an HTTP step sends the document to POST /api/v1/read, maps fields and tables, and creates the record in your system. A value with "uncertain": true can stop for a person to check.

KeysIn your system's record
vendor_name, vendor_tax_id, vendor_addressThe vendor, or a supplier account in the ERP.
document_number, allocation_number, issue_date, due_dateThe document header: number, allocation number and dates.
subtotal, discount, vat_rate, vat_amount, total, currencyThe document's amounts.
tables[0].rowsThe line items.
full_name, job_title, company, email, phone, mobile, fax, website, addressA contact or lead in the CRM. On a business card, document_type is business_card.

The workspace also downloads the same data as files: an ERP file and a CRM file with fixed columns, and a vCard. Column lists on the integrations page

06Pages and payment

  • One page of a PDF, or one image, is one page. A page is charged when it is read; if the read fails, the page is given back and its entry says status: "failed" and refunded: true.
  • A call is checked before anything is read. When the account has fewer pages than the call needs, the answer is 402 payment_required, with needed, balance, total_pages, buy_url and packs. Nothing is read and nothing is charged.
  • buy_url opens the key's top-up page, where a pack is chosen; each pack's own buy_url, in packs, opens Stripe Checkout for that pack directly. Paying adds the pages to the key's account. It signs nobody in and gives no access to the account, so the links are safe to hand to whoever pays. In a read with language: "he", the page is in Hebrew and the packs are priced in shekels.
  • New accounts get 100 free pages with their first key. If the day's free starts have all been given, the pages are added at a read or balance call on a later day. Pages stay valid for 12 months from the last time pages were added to the account, by a purchase or the free start.
A 402 response
{
  "error": "payment_required",
  "message": "This call reads 4 pages; the key's account has 3. Ask your user to add pages at buy_url, or read fewer pages with \"pages\".",
  "needed": 4,
  "balance": 3,
  "total_pages": 4,
  "buy_url": "https://ocr-ai.com/en/topup?key=key_7Hq2mXc9Lp0a",
  "packs": [
    {
      "pack": "p100",
      "pages": 100,
      "price": 9,
      "currency": "USD",
      "buy_url": "https://ocr-ai.com/api/v1/buy?key=key_7Hq2mXc9Lp0a&pack=p100&currency=USD"
    },
    {
      "pack": "p500",
      "pages": 500,
      "price": 29,
      "currency": "USD",
      "buy_url": "https://ocr-ai.com/api/v1/buy?key=key_7Hq2mXc9Lp0a&pack=p500&currency=USD"
    },
    {
      "pack": "p2000",
      "pages": 2000,
      "price": 89,
      "currency": "USD",
      "buy_url": "https://ocr-ai.com/api/v1/buy?key=key_7Hq2mXc9Lp0a&pack=p2000&currency=USD"
    }
  ]
}

07Limits

  • Up to 20 pages per call, 20 MB per file, and 28 MB per request body, so a 20 MB file sent as base64 fits.
  • A PDF may have up to 5000 pages in all; a longer one is refused whole (422 too_many_pages). A page larger than 8 MB once taken out of its PDF fails and is not charged (page_too_large).
  • About 30 calls a minute per key; above that, 429 rate_limited.
  • file_url must be a public https link: no user name or password in it, no IP address and no internal host name. It may redirect up to 3 times, and every address on the way is held to the same rule. The download must finish within 20 seconds.
  • Pages are read a few at a time, yet a call of many pages can take more than a minute: give the request enough time, or read fewer pages per call.

08Errors

Every error answer has error, a stable code, and message, a sentence a model can pass on to its user. Some add facts: get_key_url on 401, the payment fields on 402, max_pages on too_many_pages.

HTTPerrorWhen
400bad_requestThe body could not be read or is not a JSON object, or a field or parameter is unknown, of the wrong type or outside its allowed values. The message names it.
400missing_fileNo multipart field named file, or neither file_base64 nor file_url.
400bad_base64file_base64 is not valid base64.
400bad_urlfile_url is not a public https link, or one of its redirects leads to an address that is not.
400bad_pagespages is malformed, or selects no page of the file.
401unauthorizedNo key, or a key that is unknown or revoked. The answer has get_key_url and a WWW-Authenticate header.
402payment_requiredThe account has fewer pages than the call needs. Nothing is read or charged.
404not_foundNo such endpoint under /api/v1.
405method_not_allowedThe wrong HTTP method: read is POST, balance is GET.
413too_largeThe request body is over 28 MB, or the file is over 20 MB.
415unsupported_typeThe Content-Type is not multipart, JSON or a supported file type, or the file is not a PDF, JPEG, PNG or WebP.
422unreadable_pdfThe PDF cannot be opened, counted or split, or its internal structure is too large to open.
422too_many_pagesThe PDF has more than 5000 pages.
422fetch_failedfile_url could not be downloaded: an error status, more than 3 redirects, or more than 20 seconds.
429rate_limitedToo many calls in the last minute.
500server_errorAn unexpected failure before the read started. Nothing was charged.
503engine_unavailableReading is unavailable right now. Nothing was charged.

A page that failed

A call can succeed while some of its pages do not. Every entry in pages[] has a status, and a failed page has an error code as well:

status / errorMeaning
doneThe page was read and charged.
failedThe page was not read, and was given back (refunded: true). error says why:
engine_busyReading was busy. Try the page again in a minute.
engine_refusedThis page cannot be read.
engine_failedThe read failed. Try again.
unreadable_pageThe page could not be taken out of its PDF.
page_too_largeThe page is larger than 8 MB once taken out of its PDF.
no_pagesThe account ran out of pages during the call: this page was not read or charged, and the answer has buy_url.

MCP errors

/mcp answers 200 with a JSON-RPC response, or a batch of them, and 202 with no body when only notifications or responses were posted. Its errors:

HTTPJSON-RPCWhen
400-32700The body is not JSON.
400-32600The batch is empty.
400-32000The MCP-Protocol-Version header names a version this server does not support.
403-32000The request's Origin is refused.
405-32000Not a POST.
413-32000The body is over 28 MB.
500-32603An internal error before any read started. Nothing was charged.
200-32600A message that is not a JSON-RPC request: not an object, jsonrpc is not "2.0", method is not a string, or id is not a string or an integer.
200-32601The method does not exist.
200-32602params is not an object, or the tool does not exist.
200-32603An internal error during a read. Every page of that call was given back.

Inside tools/call, problems a model can act on come back as tool results with isError: true, their code in structuredContent.error: missing_key and invalid_key (with get_key_url), rate_limited, and bad_request for an argument the tool does not accept. read_document adds the codes of the REST read: payment_required (with the payment fields), missing_file, bad_base64, bad_url, bad_pages, too_large, unsupported_type, unreadable_pdf, too_many_pages, fetch_failed and engine_unavailable.

This reference as Markdown, for a model's context: /docs.md

Ready to try it?

100 free pages with your first key. No card.

Get an API key