Single-file Upload API
When to use it
Use the single-file Upload API when an integration emits one document at a time and needs durable ingestion. Every upload session must choose a bucket destination.
Good fits:
- CRM or help desk document sync
- a backend job that uploads one generated PDF
- a webhook that sends one attachment
- a script that needs retry-safe single-file ingestion
Endpoints
Create the upload session:
POST /v1/knowledge/files/upload-session
Finalize after uploading bytes:
POST /v1/knowledge/files/upload-session/{session_id}/finalize
Use a project API key:
Authorization: Bearer sk_...
Create request
{
"filename": "contract.pdf",
"content_type": "application/pdf",
"size_bytes": 184233,
"title": "Customer Contract",
"tags": ["legal", "customer"],
"metadata": { "external_id": "crm_123" },
"bucket": "contracts",
"create_missing_buckets": true
}
Bucket destination fields:
bucket_ids: existing bucket ids.bucket_slugs: existing bucket slugs.bucket: shorthand for one bucket slug.create_missing_buckets: allow slug-based provisioning during session creation.
Use the Idempotency-Key header for retryable jobs.
Create response
{
"session_id": "sess_123",
"upload_strategy": "gcs_resumable",
"upload_url": "https://storage.googleapis.com/...",
"expires_at": "2026-06-08T21:00:00Z",
"request_id": "req_current"
}
Treat upload_url as a short-lived bearer capability. Do not log it or store it in durable application state.
Upload bytes
Upload the file directly to the returned URL:
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
-H "Content-Range: bytes 0-184232/184233" \
--data-binary @/absolute/path/to/contract.pdf
Browser clients should use the same resumable upload URL and include Content-Type and Content-Range for the full object upload.
Finalize
curl -X POST "https://api.calypso.so/v1/knowledge/files/upload-session/$SESSION_ID/finalize" \
-H "Authorization: Bearer sk_..." \
-H "Content-Type: application/json" \
-d '{}'
The finalize response returns a knowledge file id and indexing task id. A queued response means the file was accepted and indexing work was queued. It does not mean the file is immediately retrievable.
Poll:
GET /v1/knowledge/files/{file_id}
GET /v1/knowledge/tasks/{task_id}
For bucketed uploads, wait until bucketSyncStatus is active before testing bucket-scoped retrieval.
Missing or invalid buckets
If no destination is provided, the API returns 400 bucket_required.
If a requested bucket cannot be resolved or assigned, the API returns 400 bucket_assignment_failed. Fix the bucket id or slug, or set create_missing_buckets=true for slug-based provisioning.
Limits
- Max file size: 25 MB.
- Rate limit: 5 create requests per second per team.
- Metadata max: 8 KB.
- Tags max: 20 tags.
Best practices
- Use stable idempotency keys for automated retries.
- Store your upstream source id in
metadata. - Use
bucket_idswhen you know the stable bucket id. - Use
bucketpluscreate_missing_buckets=truewhen provisioning a new named bucket from a script.
Next: