3 creditsOCR API — searchable text from any scan
Tesseract, 8 languages, embedded text layer.
OCR converts scanned documents and images into machine-readable text. Send a scanned PDF or image and receive the recognised text back, with the option to write it into the PDF as a searchable text layer so the original stays intact. Output quality depends far more on the input scan than on the engine — 300 DPI, straight, and well-lit produces near-perfect results.
How does the OCR API work?
One HTTP request from any language. Every ASHDOCS endpoint uses the same X-API-Key header.
curl -X POST https://www.ashdocs.com/api/v1/tools/ocr \
-H "X-API-Key: ash_live_..." \
-F "files=@scanned.pdf" \
-F 'options={"language":"eng"}'import requests
r = requests.post(
"https://www.ashdocs.com/api/v1/tools/ocr",
headers={"X-API-Key": "ash_live_..."},
files=[("files", open("scanned.pdf", "rb"))],
data={"options": '{"language":"eng"}'},
)
job = r.json()
print(job["status"], job["output_files"][0]["url"])import { readFile } from "node:fs/promises";
const form = new FormData();
form.append("files", new Blob([await readFile("scanned.pdf")]), "scanned.pdf");
form.append("options", JSON.stringify({"language":"eng"}));
const res = await fetch("https://www.ashdocs.com/api/v1/tools/ocr", {
method: "POST",
headers: { "X-API-Key": "ash_live_..." },
body: form,
});
const job = await res.json();
console.log(job.status, job.output_files[0].url);using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "ash_live_...");
using var form = new MultipartFormDataContent();
form.Add(new ByteArrayContent(await File.ReadAllBytesAsync("scanned.pdf")), "files", "scanned.pdf");
form.Add(new StringContent("{\"language\":\"eng\"}"), "options");
var res = await client.PostAsync("https://www.ashdocs.com/api/v1/tools/ocr", form);
var job = await res.Content.ReadAsStringAsync();
Console.WriteLine(job);import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class Sample {
static final String BOUNDARY = "AshdocsBoundary7MA4YWxkTrZu0gW";
public static void main(String[] args) throws Exception {
ByteArrayOutputStream body = new ByteArrayOutputStream();
addFile(body, "files", "scanned.pdf");
addField(body, "options", "{\"language\":\"eng\"}");
body.write(("--" + BOUNDARY + "--\r\n").getBytes(StandardCharsets.UTF_8));
HttpRequest req = HttpRequest.newBuilder(URI.create("https://www.ashdocs.com/api/v1/tools/ocr"))
.header("X-API-Key", "ash_live_...")
.header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY)
.POST(HttpRequest.BodyPublishers.ofByteArray(body.toByteArray()))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
static void addFile(ByteArrayOutputStream body, String name, String path) throws Exception {
body.write(("--" + BOUNDARY + "\r\nContent-Disposition: form-data; name=\"" + name + "\"; filename=\""
+ Path.of(path).getFileName() + "\"\r\nContent-Type: application/octet-stream\r\n\r\n").getBytes(StandardCharsets.UTF_8));
body.write(Files.readAllBytes(Path.of(path)));
body.write("\r\n".getBytes(StandardCharsets.UTF_8));
}
static void addField(ByteArrayOutputStream body, String name, String value) throws Exception {
body.write(("--" + BOUNDARY + "\r\nContent-Disposition: form-data; name=\"" + name + "\"\r\n\r\n"
+ value + "\r\n").getBytes(StandardCharsets.UTF_8));
}
}#include <stdio.h>
#include <curl/curl.h>
int main(void) {
CURL *curl = curl_easy_init();
if (!curl) return 1;
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "X-API-Key: ash_live_...");
curl_mime *mime = curl_mime_init(curl);
curl_mimepart *part;
part = curl_mime_addpart(mime);
curl_mime_name(part, "files");
curl_mime_filedata(part, "scanned.pdf");
part = curl_mime_addpart(mime);
curl_mime_name(part, "options");
curl_mime_data(part, "{\"language\":\"eng\"}", CURL_ZERO_TERMINATED);
curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
curl_easy_setopt(curl, CURLOPT_URL, "https://www.ashdocs.com/api/v1/tools/ocr");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
CURLcode rc = curl_easy_perform(curl);
printf("\n");
curl_mime_free(mime);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return rc == CURLE_OK ? 0 : 1;
}How do I use OCR API in Make, Zapier or n8n?
Add an HTTP "Make a request" module and POST the file. Where the OCR result is JSON rather than a file, leave Parse response on so the text arrives as usable data rather than raw bytes.
Make setup guide →Use Webhooks by Zapier with a POST action. Map the returned text into a Google Sheets row, an Airtable field, or an email body.
Zapier setup guide →Use the HTTP Request node. For JSON output leave Response Format as JSON; for a searchable PDF set it to File and name the binary property.
n8n setup guide →Trigger on a new attachment, send it for OCR, and write the recognised text back to a long-text field on the same record.
Airtable setup guide →Common problems and fixes
| Symptom | Cause | Fix |
|---|---|---|
| Recognition is poor or garbled | Scan resolution below roughly 300 DPI, or low contrast | Rescan at 300 DPI or higher with good lighting. Input quality affects the result more than any engine setting. |
| Text comes out in the wrong order | A multi-column layout read straight across both columns | Use layout-aware processing where available. Check a two-column document early — this fails silently and produces readable-looking nonsense. |
| A rotated page returns gibberish | The page is stored sideways or upside down | Detect and correct orientation before processing. Scanners frequently store pages rotated without any visual indication. |
| Handwriting is not recognised | OCR is trained on printed text | Modern OCR handles print well and handwriting poorly. Do not build a workflow that depends on handwriting recognition. |
| Non-English text fails | The wrong language model is applied | Specify the document language explicitly. Latin-script defaults will not read Devanagari, Cyrillic, Arabic or CJK. |
| Tables lose their structure | OCR returns text, not layout | Use dedicated table extraction for tabular data. OCR gives you the words; it does not tell you which cell they belong to. |
Common use cases
- →Digitizing archives
- →Making receipts searchable
- →Feeding ML pipelines
Frequently asked questions
What is OCR and when do I need it?+
OCR — optical character recognition — converts images of text into machine-readable characters. You need it whenever a PDF has no text layer: scans, photographs, faxes, or any document produced by printing and scanning back.
How do I know if a PDF needs OCR?+
Run a text extraction. If a page that visibly contains paragraphs returns almost nothing, it is scanned and needs OCR. A full page of text should return hundreds of characters.
How accurate is OCR?+
On clean printed text scanned at 300 DPI or above, accuracy is typically very high. It degrades with lower resolution, poor contrast, skew, unusual fonts, and background noise. Input quality matters more than engine choice.
Can OCR make a scanned PDF searchable?+
Yes. The recognised text is written back into the PDF as an invisible layer beneath the original page image, so the document looks unchanged but becomes searchable and selectable.
Does OCR work on photographs of documents?+
It can, but results are noticeably worse than a flatbed scan. Phone photographs introduce skew, uneven lighting, shadows and perspective distortion. If you control the capture process, improving it is far cheaper than compensating downstream.