How to Integrate LeapOCR in Your App: A Step-by-Step API + SDK Guide
A practical walkthrough for adding LeapOCR to your app using the JavaScript/TypeScript SDK, from installation to your first production-ready workflow.
How to Integrate LeapOCR in Your App: A Step-by-Step API + SDK Guide
Adding document processing to your application doesn’t require machine learning expertise. This guide walks through integrating LeapOCR into a Node.js or TypeScript project using the JavaScript SDK, moving from setup to extracting structured data.
I’m focusing on the TypeScript SDK here. If you prefer working with another language or calling the HTTP API directly, the LeapOCR documentation covers language-specific guides and API references.
What you’ll build
By the end of this guide, you’ll have code that can:
- Accept documents through URLs or file uploads (supporting PDF, Word, Excel, PowerPoint, images, and 100+ other formats)
- Submit them to LeapOCR for processing
- Poll for completion and retrieve results
- Return structured JSON ready for storage, search, or integration with other systems
Who this guide is for
This is written for developers who:
- Are building products or SaaS applications
- Know basic Node.js or TypeScript
- Have worked with environment variables and API keys before
I’ll skip the ML theory and focus on practical integration steps.
Prerequisites
Before starting, make sure you have:
- Node.js 18 or higher
- A LeapOCR account with an API key
- npm, yarn, or pnpm for package management
- A TypeScript or JavaScript project (Express, Next.js, NestJS, or a plain Node script works)
Create an API key in your LeapOCR dashboard, then:
- Save it as an environment variable (e.g.,
LEAPOCR_API_KEY) - Keep it out of version control
- Never reference it in client-side code
Keep the LeapOCR documentation handy for reference on other SDKs, raw HTTP requests, and model details.
SDK versus raw HTTP
LeapOCR exposes HTTP APIs, so you could use fetch or axios with JSON payloads directly. The SDK exists to make this easier.
Why the SDK helps:
- TypeScript types are built-in
- Less boilerplate code—no need to write multipart upload logic or polling mechanisms yourself
- Automatic retry logic handles transient network issues
- Works across Node.js, Deno, and Bun runtimes
When raw HTTP makes more sense:
- Your environment doesn’t allow installing the SDK
- You’re working in a language without an official LeapOCR SDK
For most JavaScript and TypeScript applications, the SDK is quicker to set up and simpler to maintain.
Installing and initializing the LeapOCR client
Start by installing the SDK:
npm install leapocr
# or
yarn add leapocr
# or
pnpm add leapocr
Create a helper module to initialize the client—for example, src/lib/leapocrClient.ts:
import { LeapOCR } from "leapocr";
if (!process.env.LEAPOCR_API_KEY) {
throw new Error("Missing LEAPOCR_API_KEY environment variable");
}
export const leapocr = new LeapOCR({
apiKey: process.env.LEAPOCR_API_KEY,
});
Import this client anywhere in your backend:
import { leapocr } from "../lib/leapocrClient";
// later: await leapocr.ocr.processURL(...)
Centralizing initialization makes it easier to change configuration later, mock the client in tests, and avoid creating multiple clients with different API keys.
Step 1: Processing a document from a URL
Let’s start with the simplest workflow: processing a document that’s already accessible via a public URL.
The processing lifecycle has three parts:
- Submit a document and receive a
jobId - Wait for the job to complete
- Fetch the results and access the extracted data
Here’s a working example:
import { leapocr } from "../lib/leapocrClient";
async function processInvoiceFromUrl() {
const job = await leapocr.ocr.processURL("https://example.com/invoice.pdf", {
format: "structured",
model: "standard-v1",
instructions: "Extract invoice number, date, and total amount",
});
// This helper polls until the job finishes
const status = await leapocr.ocr.waitUntilDone(job.jobId);
if (status.status !== "completed") {
throw new Error(`OCR job failed with status: ${status.status}`);
}
const result = await leapocr.ocr.getJobResult(job.jobId);
console.log("Credits used:", result.credits_used);
console.dir(result.pages, { depth: null });
}
processInvoiceFromUrl().catch(console.error);
You’ve now connected the LeapOCR client, submitted a URL for processing, and retrieved the results. In a real application, this would live in an API route or background worker rather than a standalone script, but the logic remains the same.
Step 2: Processing local files
Most applications handle user uploads or files stored locally (or in object storage) instead of public URLs. For Node.js environments, combine fs with processFile:
import { readFileSync } from "fs";
import { leapocr } from "../lib/leapocrClient";
async function processLocalInvoice(path: string) {
const fileBuffer = readFileSync(path);
const job = await leapocr.ocr.processFile(fileBuffer, {
format: "structured",
model: "pro-v1",
instructions: "Extract invoice number, vendor, date, currency, and total amount",
});
const status = await leapocr.ocr.waitUntilDone(job.jobId);
if (status.status !== "completed") {
throw new Error(`OCR job failed with status: ${status.status}`);
}
const result = await leapocr.ocr.getJobResult(job.jobId);
return result.pages;
}
In a web application, this function would live in your backend, be called from an API endpoint that receives file uploads, and return the extracted JSON to your frontend or persist it to a database.
Choosing models and output formats
LeapOCR provides several options for configuring how documents are processed.
Models:
standard-v1: Fast, general-purpose OCR and data extractionpro-v1: Higher accuracy on complex documents, with higher compute costs
Output formats:
"structured": Returns a single JSON object for the entire document (well-suited for forms and structured data)"markdown": Returns readable text (useful for full-text conversion and search indexing)
Choosing the right combination:
- Use
standard-v1with"structured"for most business documents - Switch to
"markdown"when you need searchable text rather than specific fields
Adding custom schemas for structured extraction
You usually know which fields you need before writing code. Instead of extracting data from generic OCR output, you can define a JSON schema that tells LeapOCR exactly what to return.
Option 1: Plain JSON schema
Define your expected structure as a JSON schema:
const invoiceSchema = {
type: "object",
properties: {
invoice_number: { type: "string" },
total_amount: { type: "number" },
invoice_date: { type: "string" },
vendor_name: { type: "string" },
},
};
Pass this schema when processing a file:
const job = await leapocr.ocr.processFile("./invoice.pdf", {
format: "structured",
model: "pro-v1",
schema: invoiceSchema,
instructions: "Multiply all monetary fields by 100",
});
const status = await leapocr.ocr.waitUntilDone(job.jobId);
if (status.status === "completed") {
const result = await leapocr.ocr.getJobResult(job.jobId);
console.log("Extracted invoice:", result.pages);
}
Option 2: Using existing Zod schemas
If your codebase already uses Zod for validation, you can reuse those schemas instead of writing duplicate definitions:
- Define your shape as a Zod schema
- Convert it to JSON Schema
- Pass the JSON Schema to LeapOCR
Here’s an example using zod-to-json-schema:
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
const InvoiceZodSchema = z.object({
invoice_number: z.string(),
total_amount: z.number(),
invoice_date: z.string(),
vendor_name: z.string(),
});
const invoiceJsonSchema = zodToJsonSchema(InvoiceZodSchema, "Invoice");
const job = await leapocr.ocr.processFile("./invoice.pdf", {
format: "structured",
schema: invoiceJsonSchema,
instructions: "Translate all text fields to French where reasonable",
});
LeapOCR uses the JSON Schema to structure extraction, while your application uses the original Zod schema for runtime validation and type safety. The instructions field lets you adjust behavior—like translating text or converting units—without changing your schema.
Both approaches give your downstream code a predictable shape for extracted data, while the instructions parameter provides flexibility for output formatting.
Waiting for jobs and monitoring progress
For most use cases, waitUntilDone handles everything you need—it polls automatically and returns when the job completes or fails.
If you want more control, like displaying progress in a user interface, you can poll manually:
const pollIntervalMs = 2000;
const maxAttempts = 150; // ~5 minutes
let attempts = 0;
while (attempts < maxAttempts) {
const status = await leapocr.ocr.getJobStatus(job.jobId);
console.log(`Status: ${status.status} (${status.progress?.toFixed(1)}% complete)`);
if (status.status === "completed") {
const result = await leapocr.ocr.getJobResult(job.jobId);
console.log("Processing complete!");
break;
}
if (status.status === "failed") {
throw new Error("OCR job failed");
}
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
attempts++;
}
Use waitUntilDone for server-side processing where the client doesn’t need real-time updates. For longer-running jobs or rich UIs, manual polling, webhooks, or message queues work better.
Handling errors, timeouts, and retries
Integration requires handling failure cases. Plan for:
- Authentication problems: invalid or missing API keys
- Invalid inputs: unsupported file types, corrupted documents, unreachable URLs
- Network issues: timeouts and transient failures
- Job failures: documents the model cannot process
The SDK includes retry logic for transient errors, but you should still wrap calls in try/catch, log the jobId and input metadata, and apply your own timeouts for long-running jobs.
Example error handling:
try {
const job = await leapocr.ocr.processURL(docUrl, {
format: "structured",
model: "standard-v1",
});
const status = await leapocr.ocr.waitUntilDone(job.jobId);
if (status.status !== "completed") {
throw new Error(`Job did not complete successfully: ${status.status}`);
}
} catch (error) {
console.error("LeapOCR error", { error, docUrl });
// Optionally notify an error tracking service or mark this document as failed
}
Using templates for reusable configurations
When you process the same type of document repeatedly—such as invoices from multiple vendors or standardized onboarding forms—templates reduce duplication.
Templates define the fields you need, model instructions, and output formats. Create them once in the LeapOCR dashboard, then reference them by templateSlug in your code:
const job = await leapocr.ocr.processFile("./invoice.pdf", {
templateSlug: "my-invoice-template",
model: "pro-v1",
});
const result = await leapocr.ocr.waitUntilDone(job.jobId);
console.log("Extracted data:", result.data);
Templates help when multiple services need consistent extraction behavior, or when you want to adjust extraction logic centrally without redeploying code.
Securing your API key
Your API key grants access to your LeapOCR account, so treat it like a password.
- Never hardcode it in source files
- Never expose it in browser or mobile client code
- Use environment variables or your hosting provider’s secrets manager
Common patterns:
- Local development: store
LEAPOCR_API_KEYin.env(and add.envto.gitignore) - Serverless/PaaS: configure the key through your platform’s environment variable interface
- Frontend applications: send files or URLs to your backend, which communicates with LeapOCR
If a key is accidentally exposed, rotate it immediately from the LeapOCR dashboard and redeploy with the new key.
Taking it to production
Getting code working locally is the first step. Running it reliably in production requires additional planning.
Production checklist:
- Log jobIds and document identifiers to trace failures
- Track credit usage over time to monitor costs
- Monitor error rates and latency for OCR operations
- Set up alerts when failure rates spike or processing backlogs grow
You can delete completed jobs to manage storage:
await leapocr.ocr.deleteJob(job.jobId);
console.log("Job deleted successfully");
Whether to delete jobs depends on your auditing requirements and how long you need to retain results. The key is making an intentional choice rather than letting jobs accumulate indefinitely.
Next steps
Once you’ve established the basic integration, apply it to real workflows in your application:
- Browse the LeapOCR documentation for additional options and examples
- Test different models and output formats to see which work best for your documents
- Define schemas or templates for your specific document types
- Read the guide on building an automated invoice processing system to see how this fits into a larger workflow
A practical approach is to start with one document type, validate that the integration works reliably, then expand to additional document types as needed.
Try LeapOCR on your own documents
Start with 100 free credits and see how your workflow holds up on real files.
Eligible paid plans include a 3-day trial with 100 credits after you add a credit card, so you can test actual PDFs, scans, and forms before committing to a rollout.
Keep reading
Related notes for the same operating context
More implementation guides, benchmarks, and workflow notes for teams building document pipelines.
The LeapOCR PHP SDK Is Live
Install the official LeapOCR PHP SDK from Packagist, process documents with a native PHP API, and ship OCR workflows without hand-rolling multipart uploads or polling.
Webhook Signature Verification Is Now Built Into the LeapOCR SDKs
The LeapOCR Go, Python, JavaScript, and PHP SDKs now include webhook signature verification helpers, so you can validate customer webhooks with the raw request body and timestamp header instead of reimplementing HMAC logic.
Integrating LeapOCR with TMS & WMS: A Guide for Logistics Engineers
How to build a resilient, high-throughput document ingestion pipeline for logistics using LeapOCR and Go.