Skip to main content

Fill Excel Template

Extract data from source documents and populate an Excel template. The API analyzes your template structure and uses AI to place the right values in the right cells.

POST /v1/fill/excel
info

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

Request

Content-Type: multipart/form-data

ParameterTypeRequiredDescription
source_filesFile(s)YesSource document(s) to extract data from
target_excelFileYesExcel template (.xlsx) to fill
modelstringNoOCR model: qomplement-OCR-v1 (default) or qomplement-OCR-XL-v1 (high precision)
fill_modestringNoFill strategy (default: smart)
webhook_urlstringNoURL to receive results on completion

Fill Modes

ModeDescription
smartAI maps source data to existing cells only — preserves template structure
completeAI fills all empty cells it can identify a value for
replaceAI replaces placeholder values (e.g., {{name}}, TBD, [fill])

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_excel",
"status": "completed",
"result": {
"download_url": "/v1/jobs/550e8400-e29b-41d4-a716-446655440000/download",
"expires_at": "2025-03-05T10:30:00Z",
"cells_written": 15,
"sheets_modified": 2
}
}

Result Fields

FieldTypeDescription
download_urlstringURL to download the filled Excel file (append to base URL)
expires_atstringURL expiration (7 days)
cells_writtennumberNumber of cells that were written to
sheets_modifiednumberNumber of sheets that were modified

Examples

cURL

Smart fill (default):

curl -X POST https://developer-api.qomplement.com/v1/fill/excel \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@invoice.pdf" \
-F "target_excel=@report_template.xlsx"

Complete fill:

curl -X POST https://developer-api.qomplement.com/v1/fill/excel \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@invoice.pdf" \
-F "target_excel=@report_template.xlsx" \
-F "fill_mode=complete"

Replace placeholders:

curl -X POST https://developer-api.qomplement.com/v1/fill/excel \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@invoice.pdf" \
-F "target_excel=@template_with_placeholders.xlsx" \
-F "fill_mode=replace"

High precision model:

curl -X POST https://developer-api.qomplement.com/v1/fill/excel \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@complex_report.pdf" \
-F "target_excel=@template.xlsx" \
-F "model=qomplement-OCR-XL-v1"

Multiple source documents:

curl -X POST https://developer-api.qomplement.com/v1/fill/excel \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@invoice1.pdf" \
-F "source_files=@invoice2.pdf" \
-F "source_files=@receipt.jpg" \
-F "target_excel=@monthly_report.xlsx" \
-F "fill_mode=smart"

Poll and download:

# 1. Start the fill job
JOB_ID=$(curl -s -X POST https://developer-api.qomplement.com/v1/fill/excel \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY" \
-F "source_files=@invoice.pdf" \
-F "target_excel=@template.xlsx" | 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 Excel
curl -o filled_report.xlsx \
"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_excel(source_paths, target_excel_path, fill_mode="smart", model=None):
"""Fill an Excel template from source documents."""
files = []
for path in source_paths:
files.append(("source_files", open(path, "rb")))
files.append(("target_excel", open(target_excel_path, "rb")))

data = {"fill_mode": fill_mode}
if model:
data["model"] = model

response = requests.post(
f"{BASE_URL}/fill/excel",
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 Excel file."""
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 Excel to {output_path}")


# Smart fill (default)
job = fill_excel(
source_paths=["invoice.pdf"],
target_excel_path="report_template.xlsx",
)
print(f"Wrote {job['result']['cells_written']} cells across {job['result']['sheets_modified']} sheets")
download_result(job["id"], "filled_report.xlsx")

# Complete fill with multiple source docs
job = fill_excel(
source_paths=["invoice1.pdf", "invoice2.pdf", "receipt.jpg"],
target_excel_path="monthly_report.xlsx",
fill_mode="complete",
)
download_result(job["id"], "filled_monthly_report.xlsx")

# Replace placeholders with high precision
job = fill_excel(
source_paths=["complex_report.pdf"],
target_excel_path="template_with_placeholders.xlsx",
fill_mode="replace",
model="qomplement-OCR-XL-v1",
)
download_result(job["id"], "filled_template.xlsx")

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 fillExcel(sourcePaths, targetExcelPath, 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_excel",
new Blob([fs.readFileSync(targetExcelPath)]),
path.basename(targetExcelPath)
);

if (options.fillMode) {
form.append("fill_mode", options.fillMode);
}
if (options.model) {
form.append("model", options.model);
}

const response = await fetch(`${BASE_URL}/fill/excel`, {
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 Excel to ${outputPath}`);
}

// Usage
(async () => {
// Smart fill
const job = await fillExcel(["invoice.pdf"], "report_template.xlsx");
console.log(`Wrote ${job.result.cells_written} cells across ${job.result.sheets_modified} sheets`);
await downloadResult(job.id, "filled_report.xlsx");

// Complete fill with multiple sources
const job2 = await fillExcel(
["invoice1.pdf", "invoice2.pdf"],
"monthly_report.xlsx",
{ fillMode: "complete" }
);
await downloadResult(job2.id, "filled_monthly_report.xlsx");
})();

Go

package main

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

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

func fillExcel(sourcePaths []string, targetExcelPath string, fillMode 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 Excel
target, err := os.Open(targetExcelPath)
if err != nil {
return nil, err
}
part, _ := writer.CreateFormFile("target_excel", targetExcelPath)
io.Copy(part, target)
target.Close()

// Add fill mode
writer.WriteField("fill_mode", fillMode)
writer.Close()

req, _ := http.NewRequest("POST", baseURL+"/fill/excel", 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)

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 Excel to %s\n", outputPath)
return nil
}

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

job, err := fillExcel([]string{"invoice.pdf"}, "report_template.xlsx", "smart", apiKey)
if err != nil {
fmt.Println("Error:", err)
return
}

result := job["result"].(map[string]interface{})
fmt.Printf("Wrote %.0f cells across %.0f sheets\n", result["cells_written"], result["sheets_modified"])

downloadResult(job["id"].(string), "filled_report.xlsx", apiKey)
}