Capabilities
Templates
Upload a DOCX template and let Alloovium fill it from your vault. Two capabilities: start a fill job, then poll it until the filled document is ready to download.
Lifecycle
Template fills are asynchronous. Every fill follows the same four-step pattern:
- POST the DOCX template (plus optional scoping) to
capability. - Get back a
field. The job starts in statestate. - Poll
capabilityevery 2–5 seconds. - When
condition, download the result fromfield.
Same shape as workflows
states). If you have already written a poller for one, you can reuse it for the other.templates.start_fill
/api/v2/templates/fillUpload a DOCX template and start a fill job. Returns a job_id you poll for status.
Request
Multipart form with up to three fields: file (required — the DOCX bytes), project (optional — scope sourcing to a single project), and docs (optional — comma-separated UUID list for deep scoping to specific documents).
bashcurl -sS https://api.alloovium.com/api/v2/templates/fill \ -H "Authorization: Bearer $ALLOOVIUM_API_KEY" \ -H "Idempotency-Key: submittal-review-2026-04-08" \ -F "file=@./Submittal_Review_Template.docx" \ -F "project_id=8f2e3c1a-..." \ -F "document_ids=4a1c2b3d-...,9e8f7a6b-..."
field—extonly. Non-DOCX returnscode.- Files above the maximum size return
code. field— optional project UUID. The caller must have view access or the endpoint returnscode.field— optional comma-separated UUIDs. Invalid UUIDs returncode.
Response
json{ "job_id": "c1d2e3f4-...", "status": "pending" }
The job is created in state state. The template bytes are uploaded to tenant-scoped object storage and the fill pipeline picks the job up.
REST-only — no MCP
Attach an Idempotency-Key
header header so a dropped response does not kick off the same fill twice — see Idempotency.Permission override
flag — a tenant-level permission — will get code even with scope in scope. Org admins bypass this check.templates.get_fill_status
/api/v2/templates/fill/{job_id}Poll a fill job's progress, stage, presigned download URL, and error state.
Response (running)
json{ "job_id": "c1d2e3f4-...", "status": "running", "progress": 42, "progress_stage": "Filling narrative sections", "created_at": "2026-04-08T10:12:00+00:00", "started_at": "2026-04-08T10:12:05+00:00", "completed_at": null, "filled_document_id": null, "filled_download_url": null, "error_code": null, "error_message": null }
Response (completed)
json{ "job_id": "c1d2e3f4-...", "status": "completed", "progress": 100, "progress_stage": "done", "created_at": "2026-04-08T10:12:00+00:00", "started_at": "2026-04-08T10:12:05+00:00", "completed_at": "2026-04-08T10:14:31+00:00", "filled_document_id": "9a8b7c6d-...", "filled_download_url": "https://alloovium-storage.s3.ap-southeast-2.amazonaws.com/...signed...", "error_code": null, "error_message": null }
field is a presigned S3 URL valid for 1 hour. Re-poll capability to refresh it — the URL is generated fresh on every status call once the job is complete.
Response (failed)
json{ "job_id": "c1d2e3f4-...", "status": "failed", "progress": 64, "progress_stage": "Filling tables", "created_at": "2026-04-08T10:12:00+00:00", "started_at": "2026-04-08T10:12:05+00:00", "completed_at": "2026-04-08T10:13:47+00:00", "filled_document_id": null, "filled_download_url": null, "error_code": "table_fill_failed", "error_message": "Unable to match any source document to the submittal schedule table." }
Status states
| Status | Terminal? | Notes |
|---|---|---|
| pending | no | Job created; template uploaded; not yet picked up. |
| running | no | Pipeline executing. progress + progress_stage update as it runs. |
| completed | yes | Filled DOCX ready. filled_document_id and filled_download_url are populated. |
| failed | yes | Runtime error. error_code + error_message explain what went wrong. |
| cancelled | yes | Cancelled by the user or by a timeout. |
Tenant isolation
field owned by another tenant returns code, not 403 — we do not distinguish the two so attackers cannot probe ID space.Polling pattern
pythonimport time, httpx def wait_for_fill(job_id: str, api_key: str, timeout_s: int = 600): url = f"https://api.alloovium.com/api/v2/templates/fill/{job_id}" headers = {"Authorization": f"Bearer {api_key}"} deadline = time.monotonic() + timeout_s while time.monotonic() < deadline: resp = httpx.get(url, headers=headers) resp.raise_for_status() payload = resp.json() status = payload["status"] if status == "completed": return payload # filled_download_url is ready if status in ("failed", "cancelled"): raise RuntimeError( f"fill {job_id} ended in {status}: {payload.get('error_message')}" ) time.sleep(3) # 2-5s polling is ideal raise TimeoutError(f"fill {job_id} did not finish in {timeout_s}s")
Download the filled DOCX promptly
field is a presigned S3 URL valid for 1 hour. If you need it again later, just call capability again — it will re-sign on every request.See also
- Workflows — same async state machine, different execution engine.
- Idempotency — never kick off the same fill twice.