Extract the text
Read the text out of an image without sending the image to a model. extract_text runs tesseract on a PNG, JPEG or WebP
(a URL, base64, or a file), fitted to an acuity first, and answers with the text, its counts, and the engine’s name and
version. No verdict and no record: text is not a measurement, so the answer carries a receipt and nothing more. The image
here is the synthetic invoice from our own test corpus, the one certify reads with its OCR witness.
Run 2026-10-11 05:26 UTC against api.s2ar.dev 0.1.1 · 3 calls · 1,414 ms · list price $0.02 for a stranger (these ran on the operator's key and cost nothing) · every number on this page re-derived by tools/walkthrough.py before it was written
1. The quote: HTTP 402
The price, the rails and the witness of this route. The quote costs nothing.
curl -s -X GET 'https://api.s2ar.dev/v1/extract_text?image_url=https%3A%2F%2Fs2ar.dev%2Fexamples%2Fmedia%2Finvoice.png' \
-H 'X-Assay-Quote: 1'
| price_usd | 0.02 |
|---|---|
| rails | ["x402", "credits"] |
| determinism | replayable |
| witnesses | ["tesseract"] |
| free.per_day_keyless | 10 |
the answer, as JSON
{
"price_usd": 0.02,
"rails": [
"x402",
"credits"
],
"determinism": "replayable",
"witnesses": [
"tesseract"
],
"free.per_day_keyless": 10
}
2. The invoice by URL, as a GET
The text as tesseract read it at the default acuity (long edge 1568; this image is smaller and is not touched), the word, line and character counts, the engine and its version, and the image by hash and size.
curl -s -X GET 'https://api.s2ar.dev/v1/extract_text?image_url=https%3A%2F%2Fs2ar.dev%2Fexamples%2Fmedia%2Finvoice.png' \
-H "Authorization: Bearer $S2AR_KEY"
| words | 20 |
|---|---|
| lines | 7 |
| chars | 126 |
| engine.name | tesseract |
| engine.version | 5.3.0 |
| engine.lang | eng |
| engine.acuity | 1568 |
| image.w | 1000 |
| image.h | 600 |
| image.fitted | [1000, 600] |
| image.sha256 | aea08f226ec7ac2a… |
| charged_usd | 0.0 |
the answer, as JSON
{
"words": 20,
"lines": 7,
"chars": 126,
"engine.name": "tesseract",
"engine.version": "5.3.0",
"engine.lang": "eng",
"engine.acuity": 1568,
"image.w": 1000,
"image.h": 600,
"image.fitted": [
1000,
600
],
"image.sha256": "aea08f226ec7ac2ab0cf57c548e3ee6796fd073a1a03bcf0f803e9a308e23c49",
"charged_usd": 0.0
}
The text field:
ACME SUPPLY CO — INVOICE
Invoice #: 2041
Date: 2026-08-01
PO Number: 7719
Subtotal: $1,290.00
Tax: $94.75
Total Due: $1,384.75
| re-derived by the generator | |
|---|---|
| ✓ | receipt sha256:7a407611790e1546… re-derived: sha256 over the canonical answer, header and body agree |
| ✓ | as the prose says: engine.name = "tesseract" · engine.lang = "eng" |
| ✓ | the answer names the image by hash: aea08f226ec7ac2a… equals sha256 of media/invoice.png |
3. What the host can read, free
The engine’s version and the languages installed on the host; a lang the host lacks is a 400 that charges nothing.
curl -s -X GET 'https://api.s2ar.dev/v1/extract_text/languages'
| price_usd | 0.02 |
|---|---|
| engine.name | tesseract |
| engine.version | 5.3.0 |
| languages | ["eng"] |
| default_lang | eng |
| acuity.default | 1568 |
| acuity.max | 2560 |
the answer, as JSON
{
"price_usd": 0.02,
"engine.name": "tesseract",
"engine.version": "5.3.0",
"languages": [
"eng"
],
"default_lang": "eng",
"acuity.default": 1568,
"acuity.max": 2560
}
The engine’s version rides every answer, so two hosts that disagree can be told apart, and the same bytes through the
same engine give the same text. When the text must become evidence — a signed record that a compression kept it —
certify with probe=ocr is the next call; extract_text is for the agent that just needs the words.
What this cannot buy
From the signed proceedings (/.well-known/proceedings.json), verbatim:
- placement or routing position
- a different verdict or grade
- buyer data
- amendment access
- a revocation
Failed calls are never charged. Nothing about a caller is kept. The rules: the charter.