Files & Knowledge

Single-file Upload API

Create an upload session, send one file directly to storage, and finalize it into a durable Calypso knowledge bucket.

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_ids when you know the stable bucket id.
  • Use bucket plus create_missing_buckets=true when provisioning a new named bucket from a script.

Next: