LeapOCRLeapOCR Docs

Direct Upload

Create a job and generate presigned URLs for direct file upload to S3

Direct Upload

Create a job and generate presigned URLs for direct file upload to S3. Uses multipart upload for all files (1 part for small files, multiple parts for large files ≥50MB).

Endpoint

POST /ocr/uploads/direct

Parameters

ParameterTypeRequiredDescription
filebinaryYesPDF file to process
output_typestringNoOutput format: structured, markdown, per_page_structured
modelstringNoModel to use (default: standard-v1)
instructionstringNoProcessing instructions (cannot use with schema or template_slug)
schemastringNoJSON schema as string (cannot use with instruction)
template_slugstringNoDocument template slug (cannot use with instruction or schema)

Note: Only one of template_slug, schema, or instruction can be provided per request.

Output Types

  • structured: Structured data extraction. Requires either template_slug OR format (with schema & instructions)
  • markdown: Page-by-page OCR. All configuration fields are optional
  • per_page_structured: Per-page structured extraction

SDK Examples

import { LeapOCR } from "leapocr";

const client = new LeapOCR({
  apiKey: process.env.LEAPOCR_API_KEY,
});

// Upload a local file
const job = await client.ocr.processFile("./document.pdf");

console.log(`Job created: ${job.jobId}`);

// Wait for processing to complete
const result = await client.ocr.waitUntilDone(job.jobId);
console.log("Processing complete!");
# Note: The SDKs handle the multipart upload automatically.
# For direct HTTP usage, see the "Manual Implementation" section below.

# Step 1: Initiate direct upload
curl -X POST https://api.leapocr.com/api/v1/ocr/uploads/direct \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "file_name": "document.pdf",
    "content_type": "application/pdf",
    "file_size": 1048576
  }'
import asyncio
import os
from pathlib import Path
from leapocr import LeapOCR

async def main():
    async with LeapOCR(os.getenv("LEAPOCR_API_KEY")) as client:
        # Upload a local file
        job = await client.ocr.process_file(Path("document.pdf"))

        print(f"Job created: {job.job_id}")

        # Wait for processing to complete
        result = await client.ocr.wait_until_done(job.job_id)
        print("Processing complete!")

asyncio.run(main())
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "github.com/leapocr/leapocr-go"
)

func main() {
    client, err := ocr.New(os.Getenv("LEAPOCR_API_KEY"))
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    // Upload a local file
    file, err := os.Open("document.pdf")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    job, err := client.ProcessFile(ctx, file, "document.pdf")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Job created: %s\n", job.ID)

    // Wait for processing to complete
    result, err := client.WaitUntilDone(ctx, job.ID)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Processing complete!")
}

Response

{
  "job_id": "job_abc123"
}

Error Responses

Status CodeDescription
400Bad Request
401Unauthorized
402Insufficient credits
500Internal Server Error

Using Templates

You can use pre-configured templates for common document types:

const job = await client.ocr.processFile(
  "./document.pdf",
  {
    templateSlug: "invoice-extraction",
  }
);
curl -X POST https://api.leapocr.com/api/v1/ocr/uploads/direct \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "file_name": "document.pdf",
    "content_type": "application/pdf",
    "file_size": 1048576,
    "template_slug": "invoice-extraction"
  }'
from leapocr import ProcessOptions

job = await client.ocr.process_file(
    Path("document.pdf"),
    options=ProcessOptions(
        template_slug="invoice-extraction"
    ),
)
file, err := os.Open("document.pdf")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

job, err := client.ProcessFile(ctx, file, "document.pdf",
    ocr.WithTemplateSlug("invoice-extraction"),
)

Custom Schema Extraction

Define custom extraction schemas with instructions for specific use cases:

const job = await client.ocr.processFile(
  "./document.pdf",
  {
    format: "structured",
    schema: {
      type: "object",
      properties: {
        invoice_number: { type: "string" },
        date: { type: "string" },
        total_amount: { type: "number" },
      },
    },
    instructions: "Extract invoice details from the document",
  }
);
curl -X POST https://api.leapocr.com/api/v1/ocr/uploads/direct \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "file_name": "document.pdf",
    "content_type": "application/pdf",
    "file_size": 1048576,
    "format": "structured",
    "schema": {
      "type": "object",
      "properties": {
        "invoice_number": {"type": "string"},
        "date": {"type": "string"},
        "total_amount": {"type": "number"}
      }
    },
    "instructions": "Extract invoice details from the document"
  }'
from leapocr import ProcessOptions, Format

job = await client.ocr.process_file(
    Path("document.pdf"),
    options=ProcessOptions(
        format=Format.STRUCTURED,
        schema={
            "type": "object",
            "properties": {
                "invoice_number": {"type": "string"},
                "date": {"type": "string"},
                "total_amount": {"type": "number"},
            },
        },
        instructions="Extract invoice details from the document",
    ),
)
file, err := os.Open("document.pdf")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

schema := map[string]interface{}{
    "type": "object",
    "properties": map[string]interface{}{
        "invoice_number": map[string]string{"type": "string"},
        "date": map[string]string{"type": "string"},
        "total_amount": map[string]string{"type": "number"},
    },
}

job, err := client.ProcessFile(ctx, file, "document.pdf",
    ocr.WithFormat(ocr.FormatStructured),
    ocr.WithSchema(schema),
    ocr.WithInstructions("Extract invoice details from the document"),
)

Complete Upload (Advanced)

Note: The SDKs handle multipart upload completion automatically. You typically don't need to call this endpoint directly unless you're implementing custom upload logic.

After uploading file chunks to S3, you need to complete the upload by providing all part ETags.

Endpoint

POST /ocr/uploads/{job_id}/complete

How It Works

  1. Initiate Upload: Call the Direct Upload endpoint to get presigned URLs
  2. Upload Parts: Upload file parts to the presigned URLs (S3 returns ETags)
  3. Complete Upload: Call the complete endpoint with all part ETags
  4. Processing Begins: The job automatically starts processing

Manual Implementation

If you're implementing direct uploads without the SDK, follow this flow:

Step 1: Initiate Upload

curl -X POST https://api.leapocr.com/api/v1/ocr/uploads/direct \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "file_name": "document.pdf",
    "file_size": 1048576,
    "output_type": "structured"
  }'

Response:

{
  "job_id": "job_abc123",
  "upload_id": "upload_xyz789",
  "parts": [
    {
      "part_number": 1,
      "upload_url": "https://s3.amazonaws.com/..."
    }
  ]
}

Step 2: Upload Parts to S3

# Upload each part and capture the ETag from response headers
curl -X PUT "https://s3.amazonaws.com/..." \
  --upload-file document-part1.pdf \
  -D - | grep -i etag

Step 3: Complete Upload

curl -X POST https://api.leapocr.com/api/v1/ocr/uploads/job_abc123/complete \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "parts": [
      {
        "part_number": 1,
        "etag": "\"abc123def456...\""
      }
    ]
  }'

Important: - ETags must include surrounding quotes: "abc123def456..." - Parts must be provided in sequential order - All parts from the upload must be included

Multipart Upload Details

File Size Thresholds

  • < 50MB: Single part upload
  • ≥ 50MB: Multiple parts (5MB per part)

ETag Format

ETags are returned by S3 in the response headers and must be provided exactly as received:

ETag: "abc123def456789..."

The quotes are part of the ETag value and must be included in the completion request.

Next Steps