Document uploads
Audience
Integrators uploading evidence documents to a project.
What you'll accomplish
Upload a file via the presigned flow, finalize the source, and know where to poll for extraction status.
Project documents are sources (kind: document). Uploading triggers background extraction. The complete response includes sourcePid for polling.
Requires projects:write. See Authentication.
Presigned upload
File bytes go directly to object storage. This is the supported path for integrators and large files.
POST /projects/:projectPid/documents/upload-init
→ uploadUrl + storage key
PUT uploadUrl (no Authorization header)
POST /projects/:projectPid/documents/upload-complete
→ sourcePid
Step-by-step
1. Init — presigned PUT URL (valid five minutes):
curl -sS -X POST 'https://api.air.thalus.ai/projects/<projectPid>/documents/upload-init' \
-H 'Authorization: Bearer <rawKey>' \
-H 'Content-Type: application/json' \
-d '{"filename":"evidence.pdf","contentType":"application/pdf"}'
2. Upload — PUT raw file to uploadUrl with the same Content-Type. No AIR auth header.
3. Complete — start extraction:
curl -sS -X POST 'https://api.air.thalus.ai/projects/<projectPid>/documents/upload-complete' \
-H 'Authorization: Bearer <rawKey>' \
-H 'Content-Type: application/json' \
-d '{
"s3Key": "<storage key from init>",
"filename": "evidence.pdf",
"contentType": "application/pdf"
}'
Poll GET /projects/:projectPid/documents until status is no longer processing — Async jobs.
Limits and supported types
| Limit | Value |
|---|---|
| Max file size | 50 MiB per file |
| Max artifacts per assessment | 5 |
| Presigned URL TTL | 5 minutes |
Supported extensions: .pdf, .docx, .pptx, .xlsx, .txt, .md, .csv.
Very large files may time out during extraction even when upload succeeds.
Common failures
| Symptom | Likely cause | Fix |
|---|---|---|
403 on init | Missing projects:write | Add scope or use integrations:full |
404 on init | Wrong projectPid or wrong domain | GET /domains/<domainPid>/projects |
PUT fails or 403 from storage | Wrong Content-Type, expired URL, or corrupt body | Re-run init within five minutes; match Content-Type |
409 on complete | Storage key already used or upload never finished | Fresh init → PUT → complete |
Source error after complete | Unsupported or corrupt file | Check lastError on document row |
Stuck in processing | Large file or parser timeout | Wait with backoff; check lastError; retry regenerate if needed |
After upload
| Action | Route |
|---|---|
| List documents | GET /projects/:projectPid/documents |
| List artifacts | GET /projects/:projectPid/artifacts |
| Download original | GET /projects/:projectPid/documents/:sourcePid/download |
| Re-run extraction | POST /projects/:projectPid/documents/:sourcePid/regenerate |
| Archive | POST /projects/:projectPid/documents/:sourcePid/archive |
Deleting a document archives derived artifacts used as assessment evidence.
Related
- Integration flow — upload in a full pipeline
- Async jobs — poll until
connected - Errors —
409and upload troubleshooting