Files and uploads
The v3 API never receives file content, and no operation accepts multipart/form-data. Your client sends the bytes directly to the storage service through presigned URLs that the API issues. The API manages the upload session and returns a file_id when the upload is complete.
Upload a file
- Open an upload session with the purpose, file name, size, and, if available, the checksum:
POST /api/v3/uploads
Idempotency-Key: 5f7d9f0a-3f8f-4a14-8b5c-6f9d2e0c1a77
{"purpose": "pdf", "filename": "handbook.pdf", "size_bytes": 10485760, "content_type": "application/pdf", "sha256": "9f86d0..."}The response includes the session id (upl_...), part_size, parts_total, and expires_at. Use one of these purposes: image, pdf, import, editor, document.
-
Request URLs for the parts you are about to send (
POST /uploads/{upload_id}/partswithpart_numbers). Each URL accepts aPUTrequest sent directly to the storage service and is valid for 15 minutes. Keep theETagthat the storage service returns for each part. -
Complete the upload with the list of parts and their ETags (
POST /uploads/{upload_id}/completions). The response is the upload session with statuscompletedand afileobject containing the fileid(file_...), extension, size,content_type, and checksum. -
Use the
file_idin the resource that needs it, such as a lesson attachment, a showcase image, or an import spreadsheet.
While the session is open, GET /uploads/{upload_id} returns its current status and GET /uploads/{upload_id}/parts lists the parts the storage service has already received. To abandon an upload, send PATCH /uploads/{upload_id} with status: "cancelled".
You can send large files in parts, in parallel, and resume an interrupted upload: the session survives network failures, and sending the same part again is safe. An open session expires after 24 hours.
Download a file
Downloads always go through a short-lived link, never through a URL stored in a resource:
POST /api/v3/files/{file_id}/download-links
{"ttl_seconds": 300, "inline": false, "download_name": "handbook.pdf"}The response includes url, method, and expires_at. A link is valid for at most 900 seconds, which is also the default when you omit ttl_seconds.
GET /files lists the files in your account and can be filtered by purpose and by creation date (created_after, created_before). Results are always sorted from newest to oldest; the sort parameter has no effect. DELETE /files/{file_id} permanently deletes the file from storage and from the account's file inventory. Deleted files are not moved to the trash and cannot be restored.
For every field and parameter, see the Uploads and Files references.
Limits and errors
| Limit | Value |
|---|---|
| File size | 5 GiB |
| Part URL lifetime | 15 minutes |
| Download link lifetime | 900 seconds |
| Open session lifetime | 24 hours |
| Code | When |
|---|---|
upload_integrity_mismatch | The received bytes do not match the declared size, checksum, or content type. The exact cause is in details[].reason. The session is marked as failed, and you must open a new one. |
state_conflict | The session was already completed, cancelled, failed, or expired. |
storage_unavailable | The storage service did not respond. Retry with backoff. |
validation_failed | The purpose is not supported, the size exceeds the limit, or a part number is out of range. |
Presigned URLs are never written to logs, audit entries, or events. They are kept only in the idempotency receipt, which lasts 48 hours, so a replay can return a URL that has already expired. To get a valid URL, send the request again with a new Idempotency-Key.