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
| Parameter | Type | Required | Description |
|---|---|---|---|
source_files | File(s) | Yes | Source document(s) to extract data from |
target_excel | File | Yes | Excel template (.xlsx) to fill |
model | string | No | OCR model: qomplement-OCR-v1 (default) or qomplement-OCR-XL-v1 (high precision) |
fill_mode | string | No | Fill strategy (default: smart) |
webhook_url | string | No | URL to receive results on completion |
Fill Modes
| Mode | Description |
|---|---|
smart | AI maps source data to existing cells only — preserves template structure |
complete | AI fills all empty cells it can identify a value for |
replace | AI 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
| Field | Type | Description |
|---|---|---|
download_url | string | URL to download the filled Excel file (append to base URL) |
expires_at | string | URL expiration (7 days) |
cells_written | number | Number of cells that were written to |
sheets_modified | number | Number 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)
}