Extract text from images and scans with OCR

OCR turns pixels back into characters, imperfectly. You will send images by upload and by URL, rescue scanned PDFs, clean what comes back, and decide when a human should check it.

Here is real output from an OCR call on a screenshot of a blog post:

{
  "text": "A year after that (in 2021) I hired somebody tp#lp me write blog posts for\nmy personal website.\n\nThe point is, | like reinvesting the money | make\nback into my business."
}

Most of it is right. "tp#lp" was "to help", where a cursor or a smudge sat over the letters. And twice the word "I" came back as a vertical bar, because in that font they are nearly the same shape. Nothing in the response tells you which words are wrong.

That is OCR in one paragraph: fast, cheap, mostly correct, and wrong in ways that look plausible. The engineering is in getting good input to it, cleaning its output, and designing your product so that "mostly correct" is acceptable or caught.

Two ways to send an image

Image to Text (imagetotext, 10 credits) has two input styles on the same URL:

  • Upload a JPG, PNG or GIF as multipart/form-data in a field named image, up to 5 MB.
  • Point at it with a JSON body {"url": "https://..."}, when the image is already reachable on the public internet.

The response has text on every plan. words, characters and lines are premium; count them yourself from text if your plan does not include them.

If the image contains no recognisable text at all, the API returns 404 with "could not find any text". That is a result, not a fault. A blank photo, a picture of a cat, a page that is entirely an illustration: all legitimate inputs with no text in them. Do not retry a 404 and do not page anyone about it. Map it to an empty string and move on.

// Node 18+, ESM
import { readFile } from 'node:fs/promises';
import path from 'node:path';

const KEY = process.env.APIVERVE_API_KEY;
const URL_ = 'https://api.apiverve.com/v1/imagetotext';

async function unwrap(res) {
  const body = await res.json();
  if (res.status === 404) return { text: '' }; // no text found: a valid answer
  if (!res.ok || body.status !== 'ok') throw new Error(`ocr failed (${res.status}): ${body.error}`);
  return body.data;
}

export async function ocrFile(filePath) {
  const form = new FormData();
  form.append('image', new Blob([await readFile(filePath)]), path.basename(filePath));
  const res = await fetch(URL_, {
    method: 'POST',
    headers: { 'x-api-key': KEY },
    body: form,
    signal: AbortSignal.timeout(30_000),
  });
  return unwrap(res);
}

export async function ocrUrl(imageUrl) {
  const res = await fetch(URL_, {
    method: 'POST',
    headers: { 'x-api-key': KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ url: imageUrl }),
    signal: AbortSignal.timeout(30_000),
  });
  return unwrap(res);
}

Which style to use depends on where the bytes are. Files a user just uploaded to your server are already in memory; upload them. Images already sitting at a public or signed URL (your bucket, another API's downloadURL) can be passed by reference, which saves you downloading and re-uploading them. The URL must be reachable from the open internet: localhost, private network addresses and pages behind a login will fail.

Check yourself

Your OCR worker sees a 404 from imagetotext on about 3% of uploads and retries each one five times before alerting. What should it do instead?

Answer the check above to continue.

Run every example on your own key. Free plan, no card.

Scaling up?

Volume pricing, custom SLAs, and dedicated support for high-traffic teams.

Contact sales