Files
What Context can read
A file is accepted if its MIME type or extension resolves to one of these. Anything else is rejected at upload rather than silently indexed as junk.
| Family | Types | Modality | Citations point at |
|---|---|---|---|
| Documents | .pdf | Multimodal — text and page images | The page |
.docx | Text | The document | |
| Text | .md, .txt, and other text/* | Text | The document |
| Data | .csv, .json | Text | The document |
| Code | .py, .ts, .tsx, .js, .jsx, .java, .go, .rs, .sql, .html, .css, .scss, .yml, .yaml, .toml, .ini, .env, .sh | Text | The document |
| Images | .png, .jpg, .jpeg | Image — read for content, not filename | The image |
PDFs are the only document type read visually as well as textually, which is why they are the only ones that can cite a specific page. Images are indexed for what they depict, so a product photo answers a question about the product.
.xlsx does not resolve to a supported type — export the
sheet to .csv before uploading. Google Sheets is a separate integration for writing rows
back out, not a knowledge source.Two paths, one destination
| Path | When |
|---|---|
One-call upload — POST /v1/buckets/{bucket}/files | Your file is on hand. The simple case, made simple. |
Upload session — POST /v1/buckets/{bucket}/uploads | Browser-direct uploads, or retry-safe pipelines that want resumable storage uploads. |
Both land the file in the path bucket, queue indexing, and converge on the same source object with the same ready bit. The 25 MB per-file cap applies to both — a session buys resumability, not headroom, and a session create declaring a size_bytes over the cap is rejected up front with 413 file_too_large.
One-call upload
curl -X POST "https://api.calypso.so/v1/buckets/support-handbook/files" \
-H "Authorization: Bearer sk-..." \
-H "Idempotency-Key: crm-doc-123" \
-F "file=@/path/to/contract.pdf" \
-F "title=Customer Contract" \
-F "tags=legal,customer"
Multipart form fields: file (required), title, tags (comma-separated), metadata (JSON string). The response returns the source id and indexing task; poll GET /v1/sources/{id} until ready: true.
Upload sessions
The session flow uploads bytes directly to storage, keeping your API calls small and letting an interrupted transfer resume:
- Create the session:
POST /v1/buckets/{bucket}/uploads
Authorization: Bearer sk-...
Idempotency-Key: your-stable-upload-key
{
"filename": "contract.pdf",
"content_type": "application/pdf",
"size_bytes": 184233,
"title": "Customer Contract",
"tags": ["legal", "customer"]
}
{
"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 durably.
- Upload the bytes 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
- Finalize:
curl -X POST "https://api.calypso.so/v1/buckets/support-handbook/uploads/$SESSION_ID/finalize" \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{}'
The finalize response returns the source id and indexing task id. queued means accepted — not yet retrievable.
POST /v1/knowledge/files/upload-session (+ /finalize) with bucket_ids / bucket_slugs / bucket fields in the body. Same implementation, permanent alias.Waiting for readiness
GET /v1/sources/{id}
Wait for ready: true before testing retrieval. For deeper debugging: status, task, bucket_sync_status, and the provider-truth check GET /v1/sources/{id}?verify=gemini — the gemini object shows the live document state in the search index (STATE_ACTIVE, STATE_PENDING, STATE_FAILED).
Limits
- Max file size: 25 MB, on both paths.
- Rate limit: 5 create requests per second per team.
- Metadata max: 8 KB. Tags max: 20.
Best practices
- Use stable
Idempotency-Keyvalues for automated retries. - Store your upstream source id in
metadata. - Provision buckets explicitly with
PUT /v1/buckets/{slug}(Manage → Buckets) rather than relying on implicit creation during uploads.
Next: