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
| Method | Description | When to use |
|---|---|---|
| Source documents | Upload source doc(s) — AI extracts data and maps to form fields | You have documents containing the data (passport, invoice, etc.) |
| Instructions | Describe what to fill in plain language | You know the values and want a simple way to specify them |
| Explicit mappings | Provide JSON {field_name: value} pairs | You 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
| Parameter | Type | Required | Description |
|---|---|---|---|
target_pdf | File | Yes | PDF form to fill |
source_files | File(s) | No | Source document(s) to extract data from (30+ formats supported) |
instructions | string | No | Natural language instructions for filling (e.g., "Fill client name as 'Acme Corp', date as '2025-03-01'") |
field_mappings | string | No | Explicit JSON mappings {"field": "value"} — skips AI mapping |
model | string | No | OCR model for source processing: qomplement-OCR-v1 (default) or qomplement-OCR-XL-v1 |
flatten | boolean | No | Remove form fields after filling (default: false) |
webhook_url | string | No | URL 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
| Field | Type | Description |
|---|---|---|
download_url | string | URL to download the filled PDF (append to base URL) |
expires_at | string | URL expiration (7 days from creation) |
fields_filled | number | Number of fields that were filled |
fields_total | number | Total form fields detected in the target PDF |
field_details | array | Per-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)
}