Skip to main content

Fill PDF Form

Fill a PDF form using source documents, natural language instructions, or explicit mappings. The API detects form fields in your target PDF and uses AI to map the right values to the right fields.

POST /v1/fill/pdf
info

This endpoint always processes asynchronously and returns 202 with a job ID.

Three Ways to Fill

MethodDescriptionWhen to use
Source documentsUpload source doc(s) — AI extracts data and maps to form fieldsYou have documents containing the data (passport, invoice, etc.)
InstructionsDescribe what to fill in plain languageYou know the values and want a simple way to specify them
Explicit mappingsProvide JSON {field_name: value} pairsYou know the exact field names and want full control

At least one of source_files, instructions, or field_mappings must be provided.

Request

Content-Type: multipart/form-data

ParameterTypeRequiredDescription
target_pdfFileYesPDF form to fill
source_filesFile(s)NoSource document(s) to extract data from (30+ formats supported)
instructionsstringNoNatural language instructions for filling (e.g., "Fill client name as 'Acme Corp', date as '2025-03-01'")
field_mappingsstringNoExplicit JSON mappings {"field": "value"} — skips AI mapping
modelstringNoOCR model for source processing: qomplement-OCR-v1 (default) or qomplement-OCR-XL-v1
flattenbooleanNoRemove form fields after filling (default: false)
webhook_urlstringNoURL to receive results on completion
warning

At least one of source_files, instructions, or field_mappings must be provided. The API returns 400 if none are specified.

Response (202)

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"poll_url": "/v1/jobs/550e8400-e29b-41d4-a716-446655440000"
}

Completed Result

Poll GET /v1/jobs/{id} until complete:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "fill_pdf",
"status": "completed",
"result": {
"download_url": "/v1/jobs/550e8400-e29b-41d4-a716-446655440000/download",
"expires_at": "2025-03-05T10:30:00Z",
"fields_filled": 8,
"fields_total": 12,
"field_details": [
{
"field_name": "first_name",
"value": "John",
"confidence": 0.95,
"reason": "Matched from primary_entity"
},
{
"field_name": "date_of_birth",
"value": "1990-05-15",
"confidence": 0.88,
"reason": "Matched to 'fecha_nacimiento' in source"
}
]
}
}

Result Fields

FieldTypeDescription
download_urlstringURL to download the filled PDF (append to base URL)
expires_atstringURL expiration (7 days from creation)
fields_fillednumberNumber of fields that were filled
fields_totalnumberTotal form fields detected in the target PDF
field_detailsarrayPer-field breakdown with confidence and mapping reason

Examples

cURL

Auto-fill from source document:

curl -X POST https://developer-api.qomplement.com/v1/fill/pdf \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@passport.jpg" \
-F "target_pdf=@application_form.pdf"

High precision model:

curl -X POST https://developer-api.qomplement.com/v1/fill/pdf \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@passport.jpg" \
-F "target_pdf=@application_form.pdf" \
-F "model=qomplement-OCR-XL-v1"

Natural language instructions:

curl -X POST https://developer-api.qomplement.com/v1/fill/pdf \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "target_pdf=@application_form.pdf" \
-F "instructions=Fill client name as 'Acme Corp', address as '123 Main St, NY 10001', date as today's date"

Explicit field mappings:

curl -X POST https://developer-api.qomplement.com/v1/fill/pdf \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "target_pdf=@form.pdf" \
-F 'field_mappings={"first_name":"John","last_name":"Smith","date":"2025-02-26"}'

Flatten after filling (read-only):

curl -X POST https://developer-api.qomplement.com/v1/fill/pdf \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@invoice.pdf" \
-F "target_pdf=@form.pdf" \
-F "flatten=true"

Multiple source documents:

curl -X POST https://developer-api.qomplement.com/v1/fill/pdf \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@passport.jpg" \
-F "source_files=@utility_bill.pdf" \
-F "target_pdf=@application_form.pdf"

Poll for result and download:

# 1. Start the fill job
JOB_ID=$(curl -s -X POST https://developer-api.qomplement.com/v1/fill/pdf \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@invoice.pdf" \
-F "target_pdf=@form.pdf" | jq -r '.id')

# 2. Poll until complete
while true; do
STATUS=$(curl -s https://developer-api.qomplement.com/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" | jq -r '.status')
echo "Status: $STATUS"
[ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
sleep 3
done

# 3. Download the filled PDF
curl -o filled_form.pdf \
"https://developer-api.qomplement.com/v1/jobs/$JOB_ID/download" \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY"

Python

import requests
import time
import os

API_KEY = os.environ["QOMPLEMENT_API_KEY"]
BASE_URL = "https://developer-api.qomplement.com/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}


def fill_pdf(source_paths=None, target_pdf_path="", field_mappings=None, instructions=None, flatten=False, model=None):
"""Fill a PDF form from source documents, instructions, or explicit mappings."""
files = []
if source_paths:
for path in source_paths:
files.append(("source_files", open(path, "rb")))
files.append(("target_pdf", open(target_pdf_path, "rb")))

data = {}
if field_mappings:
import json
data["field_mappings"] = json.dumps(field_mappings)
if instructions:
data["instructions"] = instructions
if flatten:
data["flatten"] = "true"
if model:
data["model"] = model

response = requests.post(
f"{BASE_URL}/fill/pdf",
headers=HEADERS,
files=files,
data=data,
)
response.raise_for_status()

job = response.json()
return wait_for_job(job["id"])


def wait_for_job(job_id, interval=3, timeout=300):
"""Poll a job until completion."""
elapsed = 0
while elapsed < timeout:
resp = requests.get(f"{BASE_URL}/jobs/{job_id}", headers=HEADERS)
job = resp.json()

if job["status"] == "completed":
return job
elif job["status"] == "failed":
raise Exception(f"Job failed: {job['error']['message']}")

if job.get("progress"):
p = job["progress"]
print(f"Progress: {p['current']}/{p['total']}{p['message']}")

time.sleep(interval)
elapsed += interval

raise TimeoutError("Job timed out")


def download_result(job_id, output_path):
"""Download the filled PDF."""
resp = requests.get(
f"{BASE_URL}/jobs/{job_id}/download",
headers=HEADERS,
)
resp.raise_for_status()

with open(output_path, "wb") as f:
f.write(resp.content)
print(f"Downloaded filled PDF to {output_path}")


# Auto-fill from source documents
job = fill_pdf(
source_paths=["passport.jpg", "utility_bill.pdf"],
target_pdf_path="application_form.pdf",
)
print(f"Filled {job['result']['fields_filled']}/{job['result']['fields_total']} fields")

for field in job["result"]["field_details"]:
print(f" {field['field_name']}: {field['value']} (confidence: {field['confidence']})")

# Download the result
download_result(job["id"], "filled_application.pdf")

# Natural language instructions
job = fill_pdf(
target_pdf_path="application_form.pdf",
instructions="Fill client name as 'Acme Corp', address as '123 Main St, NY 10001'",
)
download_result(job["id"], "filled_application.pdf")

# Explicit field mappings
job = fill_pdf(
target_pdf_path="form.pdf",
field_mappings={
"first_name": "John",
"last_name": "Smith",
"date": "2025-02-26",
},
)
download_result(job["id"], "filled_form.pdf")

JavaScript / TypeScript

const fs = require("fs");
const path = require("path");

const API_KEY = process.env.QOMPLEMENT_API_KEY;
const BASE_URL = "https://developer-api.qomplement.com/v1";

async function fillPdf(sourcePaths, targetPdfPath, options = {}) {
const form = new FormData();

for (const srcPath of sourcePaths) {
form.append(
"source_files",
new Blob([fs.readFileSync(srcPath)]),
path.basename(srcPath)
);
}
form.append(
"target_pdf",
new Blob([fs.readFileSync(targetPdfPath)]),
path.basename(targetPdfPath)
);

if (options.fieldMappings) {
form.append("field_mappings", JSON.stringify(options.fieldMappings));
}
if (options.flatten) {
form.append("flatten", "true");
}
if (options.model) {
form.append("model", options.model);
}

const response = await fetch(`${BASE_URL}/fill/pdf`, {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}` },
body: form,
});

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const job = await response.json();
return await waitForJob(job.id);
}

async function waitForJob(jobId, interval = 3000, timeout = 300000) {
const start = Date.now();
while (Date.now() - start < timeout) {
const resp = await fetch(`${BASE_URL}/jobs/${jobId}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const job = await resp.json();

if (job.status === "completed") return job;
if (job.status === "failed") throw new Error(job.error.message);

await new Promise((r) => setTimeout(r, interval));
}
throw new Error("Job timed out");
}

async function downloadResult(jobId, outputPath) {
const resp = await fetch(`${BASE_URL}/jobs/${jobId}/download`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const buffer = Buffer.from(await resp.arrayBuffer());
fs.writeFileSync(outputPath, buffer);
console.log(`Downloaded filled PDF to ${outputPath}`);
}

// Usage
(async () => {
// Auto-fill from source documents
const job = await fillPdf(
["passport.jpg", "utility_bill.pdf"],
"application_form.pdf"
);

console.log(`Filled ${job.result.fields_filled}/${job.result.fields_total} fields`);
for (const field of job.result.field_details) {
console.log(` ${field.field_name}: ${field.value} (${field.confidence})`);
}

// Download the filled PDF
await downloadResult(job.id, "filled_application.pdf");

// Explicit field mappings
const job2 = await fillPdf([], "form.pdf", {
fieldMappings: { first_name: "John", last_name: "Smith" },
flatten: true,
});
await downloadResult(job2.id, "filled_form.pdf");
})();

Go

package main

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

const baseURL = "https://developer-api.qomplement.com/v1"

func fillPdf(sourcePaths []string, targetPdfPath string, apiKey string) (map[string]interface{}, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)

// Add source files
for _, srcPath := range sourcePaths {
file, err := os.Open(srcPath)
if err != nil {
return nil, err
}
part, _ := writer.CreateFormFile("source_files", srcPath)
io.Copy(part, file)
file.Close()
}

// Add target PDF
target, err := os.Open(targetPdfPath)
if err != nil {
return nil, err
}
part, _ := writer.CreateFormFile("target_pdf", targetPdfPath)
io.Copy(part, target)
target.Close()
writer.Close()

req, _ := http.NewRequest("POST", baseURL+"/fill/pdf", body)
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", writer.FormDataContentType())

resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var job map[string]interface{}
json.NewDecoder(resp.Body).Decode(&job)

// Poll for results
return waitForJob(job["id"].(string), apiKey)
}

func waitForJob(jobID string, apiKey string) (map[string]interface{}, error) {
for i := 0; i < 100; i++ {
req, _ := http.NewRequest("GET", baseURL+"/jobs/"+jobID, nil)
req.Header.Set("Authorization", "Bearer "+apiKey)

resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}

var job map[string]interface{}
json.NewDecoder(resp.Body).Decode(&job)
resp.Body.Close()

if job["status"] == "completed" {
return job, nil
}
if job["status"] == "failed" {
return nil, fmt.Errorf("job failed: %v", job["error"])
}

time.Sleep(3 * time.Second)
}
return nil, fmt.Errorf("job timed out")
}

func downloadResult(jobID string, outputPath string, apiKey string) error {
req, _ := http.NewRequest("GET", baseURL+"/jobs/"+jobID+"/download", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)

resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

out, _ := os.Create(outputPath)
defer out.Close()
io.Copy(out, resp.Body)

fmt.Printf("Downloaded filled PDF to %s\n", outputPath)
return nil
}

func main() {
apiKey := os.Getenv("QOMPLEMENT_API_KEY")

job, err := fillPdf([]string{"passport.jpg"}, "application_form.pdf", apiKey)
if err != nil {
fmt.Println("Error:", err)
return
}

result := job["result"].(map[string]interface{})
fmt.Printf("Filled %.0f/%.0f fields\n", result["fields_filled"], result["fields_total"])

downloadResult(job["id"].(string), "filled_application.pdf", apiKey)
}