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.

3calls answered
1receipt re-hashed
0records verified
$0.02list price for a stranger
1,414ms end to end
delivered

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'

→ HTTP 402 · 442 ms

price_usd0.02
rails["x402", "credits"]
determinismreplayable
witnesses["tesseract"]
free.per_day_keyless10
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"

→ HTTP 200 · 861 ms · list price $0.02 · receipt sha256:7a407611790e1546…

the image sent, media/invoice.png
invoice.png
words20
lines7
chars126
engine.nametesseract
engine.version5.3.0
engine.langeng
engine.acuity1568
image.w1000
image.h600
image.fitted[1000, 600]
image.sha256aea08f226ec7ac2a…
charged_usd0.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'

→ HTTP 200 · 112 ms

price_usd0.02
engine.nametesseract
engine.version5.3.0
languages["eng"]
default_langeng
acuity.default1568
acuity.max2560
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:

Failed calls are never charged. Nothing about a caller is kept. The rules: the charter.