API Documentation
Complete guide to integrating OCR capabilities into your applications
Table of Contents
Quick Start
1. Get Your API Key
First, you'll need an API key to authenticate your requests:
- Create an account or sign in
- Navigate to the API Keys page
- Click "Create New Key" and give it a descriptive name
- Copy your API key and store it securely
2. Make Your First Request
Here's a simple example using 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.
- 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
/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
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 -X POST https://applyocr.com/api/v1/ocr/process \
-H "X-API-Key: your_api_key_here" \
-F "file=@document.pdf"
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}'
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}")
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);
}
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
$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);
?>
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
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
{
"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
/api/v1/ocr/batch
Process multiple documents at once by uploading a ZIP archive containing PDFs or images.
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 -X POST https://applyocr.com/api/v1/ocr/batch \
-H "X-API-Key: your_api_key_here" \
-F "file=@documents.zip"
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}")
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
$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);
?>
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
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
{
"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)
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.