API Documentation

Complete guide to integrating OCR capabilities into your applications

Quick Start

1. Get Your API Key

First, you'll need an API key to authenticate your requests:

  1. Create an account or sign in
  2. Navigate to the API Keys page
  3. Click "Create New Key" and give it a descriptive name
  4. Copy your API key and store it securely
Keep Your API Key Secure
Your API key is shown only once. Store it securely and never share it publicly or commit it to version control.

2. Make Your First Request

Here's a simple example using cURL:

cURL
curl -X POST https://applyocr.com/api/v1/ocr/process \
  -H "X-API-Key: your_api_key_here" \
  -F "file=@/path/to/document.pdf"

Authentication

The API supports two authentication methods:

1. API Key Authentication (Recommended for programmatic access)

Include your API key in the request header:

X-API-Key: your_api_key_here

2. Session-based Authentication (For web dashboard)

Uses JWT tokens stored in HTTP-only cookies. This method is automatically handled when logged into the web interface and is not recommended for programmatic API integration.

Security Best Practices:
  • Use environment variables to store your API key
  • Never hardcode API keys in your source code
  • Rotate your API keys regularly
  • Use different keys for development and production
  • Revoke keys immediately if compromised

Base URL

All API endpoints are relative to the base URL:

https://applyocr.com/api/v1

Process Single Document

POST /api/v1/ocr/process

Extract text, tables, and metadata from a single PDF or image document.

Request Parameters

Parameter Type Required Description
file File Required Document file to process (PDF, JPG, PNG, TIFF, WEBP)
options JSON String Optional Processing options (see below)

Processing Options

Option Type Default Description
enable_table_extraction boolean true Extract tables from documents
enable_language_detection boolean true Automatically detect document language (supports 90+ languages)
confidence_threshold float 0.5 Minimum confidence score (0.0-1.0)
return_images boolean false Return base64 encoded region images
pdf_password string null Password for protected PDFs
ocr_engine string auto OCR engine to use: "surya", "paddle", or "auto"
webhook_url string null Webhook URL for async results (not yet implemented - parameter is accepted but not processed)

Supported File Formats

PDF JPG/JPEG PNG TIFF/TIF WEBP

File Size Limits

  • Free tier: 50 MB per file
  • Starter/Pro tier: 200 MB per file
  • Enterprise tier: 500 MB per file
  • Maximum pages per document: Unlimited (but affects processing time)

Example Requests

cURL - Basic Request
curl -X POST https://applyocr.com/api/v1/ocr/process \
  -H "X-API-Key: your_api_key_here" \
  -F "file=@document.pdf"
cURL - With Options
curl -X POST https://applyocr.com/api/v1/ocr/process \
  -H "X-API-Key: your_api_key_here" \
  -F "file=@document.pdf" \
  -F 'options={"enable_table_extraction": true, "confidence_threshold": 0.7}'
Python - Using requests
import requests
import json

url = "https://applyocr.com/api/v1/ocr/process"
api_key = "your_api_key_here"

# Open file in binary mode
with open("document.pdf", "rb") as file:
    files = {"file": file}
    headers = {"X-API-Key": api_key}

    # Optional: Add processing options
    data = {
        "options": json.dumps({
            "enable_table_extraction": True,
            "confidence_threshold": 0.7
        })
    }

    response = requests.post(url, headers=headers, files=files, data=data)

    if response.status_code == 200:
        result = response.json()
        print(f"Extracted text: {result['full_text']}")
        print(f"Pages processed: {result['document_metadata']['pages']}")
    else:
        print(f"Error: {response.status_code} - {response.text}")
JavaScript - Using fetch
const apiKey = 'your_api_key_here';
const file = document.getElementById('fileInput').files[0];

// Create form data
const formData = new FormData();
formData.append('file', file);

// Optional: Add processing options
const options = {
    enable_table_extraction: true,
    confidence_threshold: 0.7
};
formData.append('options', JSON.stringify(options));

// Send request
try {
    const response = await fetch('https://applyocr.com/api/v1/ocr/process', {
        method: 'POST',
        headers: {
            'X-API-Key': apiKey
        },
        body: formData
    });

    if (response.ok) {
        const result = await response.json();
        console.log('Extracted text:', result.full_text);
        console.log('Pages processed:', result.document_metadata.pages);
    } else {
        const error = await response.json();
        console.error('Error:', error);
    }
} catch (error) {
    console.error('Request failed:', error);
}
Java - Using OkHttp
import okhttp3.*;
import java.io.File;
import java.io.IOException;

public class OCRClient {
    private static final String API_URL = "https://applyocr.com/api/v1/ocr/process";
    private static final String API_KEY = "your_api_key_here";

    public static void main(String[] args) throws IOException {
        OkHttpClient client = new OkHttpClient();

        File file = new File("document.pdf");

        RequestBody requestBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("file", file.getName(),
                RequestBody.create(file, MediaType.parse("application/pdf")))
            .addFormDataPart("options",
                "{\"enable_table_extraction\": true, \"confidence_threshold\": 0.7}")
            .build();

        Request request = new Request.Builder()
            .url(API_URL)
            .addHeader("X-API-Key", API_KEY)
            .post(requestBody)
            .build();

        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                String responseBody = response.body().string();
                System.out.println("Success: " + responseBody);
            } else {
                System.out.println("Error: " + response.code());
            }
        }
    }
}
PHP - Using cURL
<?php

$apiUrl = 'https://applyocr.com/api/v1/ocr/process';
$apiKey = 'your_api_key_here';
$filePath = 'document.pdf';

$ch = curl_init();

$file = new CURLFile($filePath);

$options = json_encode([
    'enable_table_extraction' => true,
    'confidence_threshold' => 0.7
]);

$postData = [
    'file' => $file,
    'options' => $options
];

curl_setopt_array($ch, [
    CURLOPT_URL => $apiUrl,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $postData,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: ' . $apiKey
    ]
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($httpCode === 200) {
    $result = json_decode($response, true);
    echo "Extracted text: " . $result['full_text'] . "\n";
    echo "Pages: " . $result['document_metadata']['pages'] . "\n";
} else {
    echo "Error: HTTP $httpCode - $response\n";
}

curl_close($ch);
?>
Ruby - Using Net::HTTP
require 'net/http'
require 'uri'
require 'json'

api_url = URI('https://applyocr.com/api/v1/ocr/process')
api_key = 'your_api_key_here'
file_path = 'document.pdf'

boundary = "----WebKitFormBoundary#{rand(1000000000)}"
post_body = []

# Add file
post_body << "--#{boundary}\r\n"
post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{File.basename(file_path)}\"\r\n"
post_body << "Content-Type: application/pdf\r\n\r\n"
post_body << File.read(file_path)
post_body << "\r\n"

# Add options
options = {
  enable_table_extraction: true,
  confidence_threshold: 0.7
}
post_body << "--#{boundary}\r\n"
post_body << "Content-Disposition: form-data; name=\"options\"\r\n\r\n"
post_body << options.to_json
post_body << "\r\n--#{boundary}--\r\n"

request = Net::HTTP::Post.new(api_url)
request['X-API-Key'] = api_key
request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
request.body = post_body.join

response = Net::HTTP.start(api_url.hostname, api_url.port, use_ssl: true) do |http|
  http.request(request)
end

if response.code == '200'
  result = JSON.parse(response.body)
  puts "Extracted text: #{result['full_text']}"
  puts "Pages: #{result['document_metadata']['pages']}"
else
  puts "Error: #{response.code} - #{response.body}"
end
Go - Using net/http
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
)

func main() {
    apiURL := "https://applyocr.com/api/v1/ocr/process"
    apiKey := "your_api_key_here"
    filePath := "document.pdf"

    // Create multipart form
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    // Add file
    file, err := os.Open(filePath)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    part, err := writer.CreateFormFile("file", filePath)
    if err != nil {
        panic(err)
    }
    io.Copy(part, file)

    // Add options
    options := map[string]interface{}{
        "enable_table_extraction": true,
        "confidence_threshold":    0.7,
    }
    optionsJSON, _ := json.Marshal(options)
    writer.WriteField("options", string(optionsJSON))

    writer.Close()

    // Create request
    req, err := http.NewRequest("POST", apiURL, body)
    if err != nil {
        panic(err)
    }

    req.Header.Set("X-API-Key", apiKey)
    req.Header.Set("Content-Type", writer.FormDataContentType())

    // Send request
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    // Parse response
    if resp.StatusCode == 200 {
        var result map[string]interface{}
        json.NewDecoder(resp.Body).Decode(&result)

        fmt.Printf("Extracted text: %v\n", result["full_text"])
        metadata := result["document_metadata"].(map[string]interface{})
        fmt.Printf("Pages: %v\n", metadata["pages"])
    } else {
        bodyBytes, _ := io.ReadAll(resp.Body)
        fmt.Printf("Error: %d - %s\n", resp.StatusCode, string(bodyBytes))
    }
}

Response Format

JSON Response
{
  "request_id": "123e4567-e89b-12d3-a456-426614174000",
  "timestamp": "2025-11-03T10:30:00Z",
  "document_metadata": {
    "filename": "document.pdf",
    "pages": 3,
    "file_type": "pdf",
    "file_size_bytes": 1048576,
    "processing_time_ms": 2500,
    "ocr_engine": "surya",
    "has_native_text": false
  },
  "pages": [
    {
      "page_number": 1,
      "dimensions": {
        "width": 2480,
        "height": 3508
      },
      "text_blocks": [
        {
          "text": "Sample heading",
          "confidence": 0.98,
          "bbox": [100, 200, 400, 250],
          "block_type": "heading"
        }
      ],
      "tables": [
        {
          "bbox": [100, 300, 800, 600],
          "rows": [
            ["Header 1", "Header 2"],
            ["Data 1", "Data 2"]
          ],
          "confidence": 0.95,
          "num_rows": 2,
          "num_cols": 2
        }
      ],
      "language": "en",
      "processing_time_ms": 850
    }
  ],
  "full_text": "Sample heading\n\nFull extracted text from all pages...",
  "errors": []
}

Response Fields

Field Type Description
request_id UUID Unique identifier for this request
timestamp ISO 8601 Processing timestamp
document_metadata Object Document metadata and processing info
pages Array Array of processed pages with text blocks and tables
full_text String Concatenated text from all pages
errors Array Processing errors or warnings (if any)

Process Batch Documents

POST /api/v1/ocr/batch

Process multiple documents at once by uploading a ZIP archive containing PDFs or images.

Batch Processing Benefits
Process multiple documents in a single request, reducing overhead and simplifying integration for bulk operations.

Request Parameters

Parameter Type Required Description
file File (ZIP) Required ZIP archive containing documents to process
options JSON String Optional Processing options (same as single document)

ZIP Archive Requirements

  • Archive must be in ZIP format
  • Maximum archive size varies by tier (same as file size limits)
  • All files in the archive must be supported document types (PDF, JPG, PNG, TIFF, WEBP)
  • Nested folders are supported

Example Requests

cURL
curl -X POST https://applyocr.com/api/v1/ocr/batch \
  -H "X-API-Key: your_api_key_here" \
  -F "file=@documents.zip"
Python
import requests

url = "https://applyocr.com/api/v1/ocr/batch"
api_key = "your_api_key_here"

with open("documents.zip", "rb") as file:
    files = {"file": file}
    headers = {"X-API-Key": api_key}

    response = requests.post(url, headers=headers, files=files)

    if response.status_code == 200:
        result = response.json()
        print(f"Total documents: {result['total_documents']}")
        print(f"Successful: {result['successful']}")
        print(f"Failed: {result['failed']}")

        # Process individual documents
        for doc in result['documents']:
            print(f"\n{doc['document_metadata']['filename']}:")
            print(f"  Text: {doc['full_text'][:100]}...")
    else:
        print(f"Error: {response.status_code}")
JavaScript
const apiKey = 'your_api_key_here';
const zipFile = document.getElementById('zipInput').files[0];

const formData = new FormData();
formData.append('file', zipFile);

try {
    const response = await fetch('https://applyocr.com/api/v1/ocr/batch', {
        method: 'POST',
        headers: {
            'X-API-Key': apiKey
        },
        body: formData
    });

    if (response.ok) {
        const result = await response.json();
        console.log(`Processed ${result.successful}/${result.total_documents} documents`);

        result.documents.forEach(doc => {
            console.log(`${doc.document_metadata.filename}: ${doc.full_text.substring(0, 100)}...`);
        });
    }
} catch (error) {
    console.error('Batch processing failed:', error);
}
PHP
<?php

$apiUrl = 'https://applyocr.com/api/v1/ocr/batch';
$apiKey = 'your_api_key_here';
$zipPath = 'documents.zip';

$ch = curl_init();

$file = new CURLFile($zipPath);

curl_setopt_array($ch, [
    CURLOPT_URL => $apiUrl,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => ['file' => $file],
    CURLOPT_HTTPHEADER => ['X-API-Key: ' . $apiKey]
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($httpCode === 200) {
    $result = json_decode($response, true);
    echo "Processed: {$result['successful']}/{$result['total_documents']} documents\n";

    foreach ($result['documents'] as $doc) {
        $filename = $doc['document_metadata']['filename'];
        $preview = substr($doc['full_text'], 0, 100);
        echo "\n$filename: $preview...\n";
    }
} else {
    echo "Error: HTTP $httpCode - $response\n";
}

curl_close($ch);
?>
Ruby
require 'net/http'
require 'uri'
require 'json'

api_url = URI('https://applyocr.com/api/v1/ocr/batch')
api_key = 'your_api_key_here'
zip_path = 'documents.zip'

boundary = "----WebKitFormBoundary#{rand(1000000000)}"
post_body = []

# Add ZIP file
post_body << "--#{boundary}\r\n"
post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{File.basename(zip_path)}\"\r\n"
post_body << "Content-Type: application/zip\r\n\r\n"
post_body << File.read(zip_path)
post_body << "\r\n--#{boundary}--\r\n"

request = Net::HTTP::Post.new(api_url)
request['X-API-Key'] = api_key
request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
request.body = post_body.join

response = Net::HTTP.start(api_url.hostname, api_url.port, use_ssl: true) do |http|
  http.request(request)
end

if response.code == '200'
  result = JSON.parse(response.body)
  puts "Processed: #{result['successful']}/#{result['total_documents']} documents"

  result['documents'].each do |doc|
    filename = doc['document_metadata']['filename']
    preview = doc['full_text'][0..99]
    puts "\n#{filename}: #{preview}..."
  end
else
  puts "Error: #{response.code} - #{response.body}"
end
Go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
)

func main() {
    apiURL := "https://applyocr.com/api/v1/ocr/batch"
    apiKey := "your_api_key_here"
    zipPath := "documents.zip"

    // Create multipart form
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    // Add ZIP file
    file, err := os.Open(zipPath)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    part, err := writer.CreateFormFile("file", zipPath)
    if err != nil {
        panic(err)
    }
    io.Copy(part, file)

    writer.Close()

    // Create request
    req, err := http.NewRequest("POST", apiURL, body)
    if err != nil {
        panic(err)
    }

    req.Header.Set("X-API-Key", apiKey)
    req.Header.Set("Content-Type", writer.FormDataContentType())

    // Send request
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    // Parse response
    if resp.StatusCode == 200 {
        var result map[string]interface{}
        json.NewDecoder(resp.Body).Decode(&result)

        fmt.Printf("Processed: %.0f/%.0f documents\n",
            result["successful"], result["total_documents"])

        documents := result["documents"].([]interface{})
        for _, doc := range documents {
            docMap := doc.(map[string]interface{})
            metadata := docMap["document_metadata"].(map[string]interface{})
            text := docMap["full_text"].(string)

            fmt.Printf("\n%s: %s...\n",
                metadata["filename"], text[:min(100, len(text))])
        }
    } else {
        bodyBytes, _ := io.ReadAll(resp.Body)
        fmt.Printf("Error: %d - %s\n", resp.StatusCode, string(bodyBytes))
    }
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

Response Format

JSON Response
{
  "request_id": "123e4567-e89b-12d3-a456-426614174000",
  "timestamp": "2025-11-03T10:30:00Z",
  "total_documents": 3,
  "successful": 3,
  "failed": 0,
  "total_processing_time_ms": 7500,
  "documents": [
    {
      "request_id": "234e4567-e89b-12d3-a456-426614174001",
      "document_metadata": {
        "filename": "invoice1.pdf",
        "pages": 1,
        "file_type": "pdf",
        "processing_time_ms": 2000
      },
      "pages": [...],
      "full_text": "Invoice text...",
      "errors": []
    },
    {
      "request_id": "345e4567-e89b-12d3-a456-426614174002",
      "document_metadata": {
        "filename": "receipt.jpg",
        "pages": 1,
        "file_type": "image",
        "processing_time_ms": 1800
      },
      "pages": [...],
      "full_text": "Receipt text...",
      "errors": []
    }
  ]
}

Error Handling

The API uses standard HTTP status codes to indicate success or failure. Error responses include detailed information to help diagnose issues.

HTTP Status Codes

Code Status Description
200 OK Request successful
400 Bad Request Invalid request parameters or malformed data
401 Unauthorized Missing or invalid API key
403 Forbidden API key doesn't have permission or quota exceeded
413 Payload Too Large File exceeds maximum size limit
422 Unprocessable Entity Valid request but unable to process (e.g., corrupted file)
429 Too Many Requests Rate limit exceeded
500 Internal Server Error Server error during processing

Error Response Format

{
  "error_code": "PROCESSING_FAILED",
  "message": "Failed to process document",
  "details": {
    "error": "Unsupported file format"
  },
  "request_id": "123e4567-e89b-12d3-a456-426614174000",
  "timestamp": "2025-11-03T10:30:00Z"
}

Common Error Codes

Error Code Description Resolution
INVALID_API_KEY API key is invalid or revoked Check your API key or generate a new one
FILE_TOO_LARGE File exceeds size limit Reduce file size or split into smaller files
UNSUPPORTED_FORMAT File format not supported Use PDF, JPG, PNG, TIFF, or WEBP format
CORRUPTED_FILE File is corrupted or invalid Verify file integrity and re-upload
RATE_LIMIT_EXCEEDED Too many requests Wait before retrying or upgrade your plan
PAGE_QUOTA_EXCEEDED Monthly page quota exhausted Purchase add-on pages, upgrade your plan, or wait for monthly reset
PROCESSING_FAILED Document processing failed Contact support with request ID

Rate Limits

Rate limits prevent abuse and ensure fair usage for all users. Limits vary by plan tier.

Rate Limit Headers

Each API response includes rate limit information in the headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1699012800

Rate Limits

Rate limits are applied per-endpoint to ensure fair usage and system stability:

Endpoint-Specific Limits (All Tiers)

  • OCR Processing: 10 requests/minute
  • Batch Processing: 5 requests/minute
  • Authentication (login): 5 requests per 5 minutes
  • Registration: 3 requests/hour
  • Password Reset: 3 requests/hour
  • Other endpoints: 100 requests/minute (default)

Note: These limits apply to all subscription tiers. Rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) are included in API responses.

  • Monthly page quota: Varies by plan (see pricing)
Handling Rate Limits
When you receive a 429 status code, implement exponential backoff in your retry logic. Check the X-RateLimit-Reset header to know when to retry.

Best Practices

Security

  • Store API keys in environment variables, never in source code
  • Use HTTPS for all API requests
  • Rotate API keys regularly
  • Use different keys for development, staging, and production
  • Revoke compromised keys immediately

Performance

  • Use batch processing for multiple documents to reduce overhead
  • Optimize image quality before uploading (high DPI not always necessary)
  • Implement retry logic with exponential backoff
  • Cache results when processing the same document multiple times
  • Monitor rate limit headers to avoid hitting limits

Error Handling

  • Always check HTTP status codes before processing responses
  • Log request_id for debugging and support inquiries
  • Implement proper error handling for all API calls
  • Validate files before uploading (format, size, content type)
  • Handle partial failures in batch processing gracefully

File Preparation

  • Ensure documents are clear and well-scanned (minimum 300 DPI recommended)
  • Remove password protection before uploading or provide the password
  • Compress images to reduce file size without losing quality
  • Use standard fonts in PDFs for better text extraction
  • Rotate documents to correct orientation before processing

Frequently Asked Questions

What file formats are supported?

We support PDF, JPG, JPEG, PNG, TIFF, TIF, and WEBP formats. For batch processing, files must be packaged in a ZIP archive.

How long does processing take?

Processing time varies based on document size, page count, and complexity. Single-page documents typically process in 1-3 seconds. First request may take longer (1-2 minutes) while models initialize.

What languages are supported?

ApplyOCR supports 90+ languages with automatic language detection using the Surya OCR engine.

Supported Language Families:

  • European: English, Spanish, French, German, Italian, Portuguese, Dutch, Polish, Romanian, and more
  • Asian: Chinese (Simplified & Traditional), Japanese, Korean, Thai, Vietnamese, Indonesian
  • Cyrillic: Russian, Ukrainian, Bulgarian, Serbian, Kazakh
  • Middle Eastern: Arabic, Hebrew, Persian, Turkish, Urdu
  • South Asian: Hindi, Bengali, Tamil, Telugu, Gujarati, Kannada, Malayalam
  • And many more...

Language is automatically detected from the document content and returned in the response with ISO 639-1 language codes (e.g., "en", "es", "zh", "ar").

Can I process password-protected PDFs?

Yes, include the password in the processing options using the pdf_password parameter.

How accurate is the OCR?

Accuracy depends on document quality. Our Surya-OCR engine achieves 97% accuracy on standard documents. Each text block includes a confidence score to help assess reliability.

Is my data secure?

Yes. All data is transmitted over HTTPS, files are processed in isolated environments, and temporary files are deleted immediately after processing. We do not store your documents. See our privacy policy for details.

What happens if processing fails?

Failed requests do not count against your quota. You'll receive an error response with details. Save the request_id for support inquiries.

Can I extract tables from documents?

Yes, table extraction is enabled by default. Tables are returned as structured 2D arrays with row/column information.

How do I upgrade my plan?

Visit the pricing page to view plans and upgrade. Changes take effect immediately.

Where can I get support?

For technical support, contact us with your request_id and error details. We typically respond within 24 hours.

Ready to Get Started?

Create an account and get your API key in minutes