Skip to main content

Jobs

Async operations return a job ID. Use these endpoints to check status, retrieve results, and download filled files.

Get Job Status

GET /v1/jobs/{job_id}

Response

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "extract",
"status": "processing",
"progress": {
"current": 2,
"total": 5,
"message": "Processing document 2 of 5..."
},
"result": null,
"error": null,
"created_at": "2025-02-26T10:30:00Z",
"started_at": "2025-02-26T10:30:05Z",
"completed_at": null,
"processing_time_ms": null
}

Job Statuses

StatusDescription
pendingJob created, waiting to start
processingCurrently being processed
completedFinished successfully — result field contains output
failedProcessing failed — error field contains details

Completed Extraction Job

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "extract",
"status": "completed",
"progress": null,
"result": {
"model": "qomplement-OCR-v1",
"document_type": "invoice",
"language": "en",
"confidence": 92,
"fields": {
"invoice_number": "INV-2025-001234",
"total_amount": "1500.50",
"vendor_name": "ABC Corp"
},
"tables": [],
"pages_processed": 3,
"processing_time_ms": 8000
},
"error": null,
"created_at": "2025-02-26T10:30:00Z",
"started_at": "2025-02-26T10:30:05Z",
"completed_at": "2025-02-26T10:30:13Z",
"processing_time_ms": 8000
}

Completed Fill Job

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "fill_pdf",
"status": "completed",
"progress": null,
"result": {
"download_url": "/v1/jobs/550e8400-e29b-41d4-a716-446655440000/download",
"expires_at": "2025-03-05T10:30:00Z",
"fields_filled": 8,
"fields_total": 12
},
"error": null,
"created_at": "2025-02-26T10:30:00Z",
"started_at": "2025-02-26T10:30:05Z",
"completed_at": "2025-02-26T10:35:12Z",
"processing_time_ms": 307000
}

Failed Job

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "fill_pdf",
"status": "failed",
"result": null,
"error": {
"code": "no_form_fields",
"message": "Target PDF has no fillable form fields"
}
}

Download Filled File

GET /v1/jobs/{job_id}/download

For completed fill jobs (PDF or Excel), download the resulting file:

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

Returns the binary file with appropriate Content-Type and Content-Disposition headers.

List Jobs

GET /v1/jobs

Query Parameters

ParameterTypeDefaultDescription
statusstringFilter: pending, processing, completed, failed
typestringFilter: extract, fill_pdf, fill_excel
limitinteger20Results per page (max 100)
offsetinteger0Pagination offset

Response

{
"jobs": [
{
"id": "550e8400-...",
"type": "extract",
"status": "completed",
"created_at": "2025-02-26T10:30:00Z",
"processing_time_ms": 3456
}
],
"total": 157,
"has_more": true
}

Examples

cURL

# Check job status
curl https://developer-api.qomplement.com/v1/jobs/JOB_ID \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY"

# List completed extraction jobs
curl "https://developer-api.qomplement.com/v1/jobs?status=completed&type=extract&limit=10" \
-H "Authorization: Bearer $QOMPLEMENT_API_KEY"

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

Python — Polling Pattern

import time
import requests

def wait_for_job(job_id, api_key, interval=3, timeout=300):
"""Poll a job until it completes or times out."""
headers = {"Authorization": f"Bearer {api_key}"}
url = f"https://developer-api.qomplement.com/v1/jobs/{job_id}"
elapsed = 0

while elapsed < timeout:
response = requests.get(url, headers=headers).json()

if response["status"] == "completed":
return response["result"]
elif response["status"] == "failed":
raise Exception(response["error"]["message"])

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

time.sleep(interval)
elapsed += interval

raise TimeoutError(f"Job {job_id} did not complete in {timeout}s")


def download_file(job_id, output_path, api_key):
"""Download the filled file from a completed job."""
headers = {"Authorization": f"Bearer {api_key}"}
url = f"https://developer-api.qomplement.com/v1/jobs/{job_id}/download"

resp = requests.get(url, headers=headers)
resp.raise_for_status()

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

JavaScript — Polling Pattern

async function waitForJob(jobId, apiKey, interval = 3000, timeout = 300000) {
const headers = { Authorization: `Bearer ${apiKey}` };
const url = `https://developer-api.qomplement.com/v1/jobs/${jobId}`;
const start = Date.now();

while (Date.now() - start < timeout) {
const resp = await fetch(url, { headers });
const job = await resp.json();

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

if (job.progress) {
console.log(`Progress: ${job.progress.current}/${job.progress.total}${job.progress.message}`);
}

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

async function downloadFile(jobId, outputPath, apiKey) {
const resp = await fetch(
`https://developer-api.qomplement.com/v1/jobs/${jobId}/download`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
const buffer = Buffer.from(await resp.arrayBuffer());
fs.writeFileSync(outputPath, buffer);
console.log(`Downloaded to ${outputPath}`);
}

Go — Polling Pattern

func waitForJob(jobID string, apiKey string, timeout time.Duration) (map[string]interface{}, error) {
deadline := time.Now().Add(timeout)

for time.Now().Before(deadline) {
req, _ := http.NewRequest("GET",
"https://developer-api.qomplement.com/v1/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["result"].(map[string]interface{}), nil
}
if job["status"] == "failed" {
return nil, fmt.Errorf("job failed: %v", job["error"])
}

time.Sleep(3 * time.Second)
}
return nil, fmt.Errorf("timeout waiting for job %s", jobID)
}
tip

Prefer webhooks over polling for production use. They're more efficient and deliver results as soon as processing completes.