Uploads
Create and complete resumable multipart uploads.
Resumable uploads handle files up to 5,000,000,000,000 bytes (5 TB), and are required above 100 MiB. Files up to 5,000,000,000 bytes go up in one PUT unless you ask for parts. Larger files go up in at most 10,000 parts, each part_size bytes except the last.
The API keeps at most 64 active uploads for an organization. A session expires after 24 hours, or sooner when the file's expiration is sooner. An authenticated browser session can call these endpoints without an API key; an agent should send an API key.
Upload session shape
| Field | Type | Description |
|---|---|---|
type | string | Always upload. |
id | string | Upload session ID. |
file_id | string | Reserved file ID. |
status | string | creating, active, completing, completed, aborted, or failed. |
transport | string | s3: send the bytes to upload_url, or to the URLs from part URLs. worker: send parts to the API with upload a streamed part. |
part_size | integer | The file size for a single-PUT session. Otherwise the required size of every non-final part; the final part can be smaller. |
total_parts | integer | Number of parts calculated from expected_size_bytes and part_size. |
expected_size_bytes | integer | Declared complete-file size. |
uploaded_parts | array | Recorded parts, ordered by part_number. |
uploaded_parts[].part_number | integer | Part number starting at 1. |
uploaded_parts[].etag | string | Recorded part entity tag. |
uploaded_parts[].checksum_sha256 | string or null | Recorded direct-part SHA-256 checksum, when supplied. |
uploaded_parts[].size_bytes | integer | Recorded part size. |
expires_at | string | ISO 8601 session expiration time. |
upload_url | string | Present on an active s3 session of at most 5,000,000,000 bytes: a signed URL that accepts one PUT of exactly expected_size_bytes bytes. Larger s3 sessions have no upload_url; fetch part URLs instead. |
upload_url_expires_at | string | ISO 8601 time after which upload_url stops working. Fetch the session again for a fresh one. |
file | object | Included on a completed session; this is the file object. |
upload_url is a capability: anyone holding it can write that one object until it expires. Do not log it.
Create an upload
POST /v1/uploads
Create a file record and a resumable upload session. Send an API key with write permission on the target path, or use an authenticated browser session.
Creating is safe to retry. If the response is lost, send the same request again: while the first session is still live, the same path, size_bytes and sha256 return that session with 200 instead of path_exists. A different size or hash for the same path is still a conflict.
Files up to 5,000,000,000 bytes get a session with upload_url: PUT the whole file there, then call complete. Larger files, and any file sent with multipart: true, go up in parts: follow the session's transport, then call complete. Every part is resumable on its own. If you get 503 upload_unavailable, retry shortly.
The url in the create response is where the file will live. An HTML, SVG, or XML file can move to the sandboxed host s.agentfsusercontent.com at completion, so share the url from the completed file object.
Request body
Send JSON.
| Field | Type | Required | Description |
|---|---|---|---|
path | string | Yes | project/filename or a nested path. The path must include a project and filename. |
size_bytes | integer | Yes | Positive declared size, at most 5,000,000,000,000 bytes. |
content_type | string | No | Trimmed media type, 1–255 characters. If omitted, infer it from the filename. application/octet-stream is also replaced by filename inference. |
sha256 | string | No | Expected SHA-256 digest of the whole file as 64 hexadecimal characters. Completion reads the stored bytes back to check it, so it is accepted only for uploads up to 1 GiB (1,073,741,824 bytes); a mismatch returns 422 checksum_mismatch. |
visibility | string | No | public, unlisted, or private. |
expires_in | string or null | No | Duration such as 24h or 7d using an s, m, h, d, or w suffix, or null. A string can be at most 52 weeks. When omitted, use the project's default expiration if one exists. |
label | string or null | No | Optional label, at most 200 characters. |
content_disposition | string | No | inline or attachment. Defaults to inline. |
multipart | boolean | No | Defaults to false. true gives a multipart session at any size, with parts fetched through part URLs, so the upload can resume part by part. With false, files up to 5,000,000,000 bytes get one upload_url. |
Paths use at most 16 segments and 2,048 encoded characters. Every segment may contain only letters, digits, ., _, and -.
Returns 201 Created.
Response fields
Returns an upload session. A new session has no uploaded_parts. A direct session may include direct; a streamed session does not.
Request
curl -X POST https://agentfs.cloud/v1/uploads \
-H "Authorization: Bearer $AGENTFS_KEY" \
-H "Content-Type: application/json" \
-d '{
"path": "media/video.mp4",
"size_bytes": 150000000,
"content_type": "video/mp4",
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"visibility": "private",
"expires_in": "7d",
"label": "Demo video",
"content_disposition": "attachment",
"multipart": true
}'Response
{
"type": "upload",
"id": "up_7c31",
"file_id": "f_8f2c",
"status": "active",
"transport": "s3",
"part_size": 16777216,
"total_parts": 9,
"expected_size_bytes": 150000000,
"uploaded_parts": [],
"expires_at": "2026-03-03T12:00:00.000Z"
}Errors
unauthorized (401), invalid_agent_label (400), csrf_rejected (403), no_active_organization (403), invalid_upload (400), forbidden (403), path_exists (409), upload_rate_limited (429), storage_limit_exceeded (413), billing_unavailable (503), invalid_metadata (400), region_unavailable (422), project_create_failed (500), folder_create_failed (500), upload_unavailable (503), upload_create_failed (500 or 502), upload_not_found (404), upload_not_active (409), invalid_token (400), unknown_bucket (400), upload_failed (502), internal_error (500).
Get an upload
GET /v1/uploads/:id
Return the current session and its recorded parts. The API key must have write permission on the upload's file path.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Upload session ID. |
An unfinished session that has expired is reported as not found. A completed session remains readable.
Response fields
Returns an upload session. Completed sessions also include file.
Returns 200 OK.
Request
curl https://agentfs.cloud/v1/uploads/up_7c31 \
-H "Authorization: Bearer $AGENTFS_KEY"Response
{
"type": "upload",
"id": "up_7c31",
"file_id": "f_8f2c",
"status": "active",
"transport": "s3",
"part_size": 16777216,
"total_parts": 9,
"expected_size_bytes": 150000000,
"uploaded_parts": [
{
"part_number": 1,
"etag": "etag-part-1",
"checksum_sha256": null,
"size_bytes": 16777216
}
],
"expires_at": "2026-03-03T12:00:00.000Z"
}Errors
unauthorized (401), no_active_organization (403), invalid_agent_label (400), upload_not_found (404), forbidden (403), internal_error (500).
Get part URLs
GET /v1/uploads/:id/part-urls?from=1&to=100
Signed PUT URLs for the parts of a multipart s3 session, at most 100 per call. Each URL accepts exactly size_bytes bytes for its part and expires at expires_at; an expired URL answers 403, so fetch again for fresh ones. Parts can be sent in parallel. Storage answers each part with an ETag; you do not need to send it back, completion reads the parts from storage.
{
"upload_id": "up_7c31",
"part_size": 16777216,
"total_parts": 9,
"parts": [
{ "part_number": 1, "size_bytes": 16777216, "url": "https://<part_url>", "expires_at": "2026-03-02T17:55:00.000Z" }
]
}Errors: upload_not_found (404), upload_not_active (409) for a session without part URLs, invalid_part (400), upload_expired (410), upload_unavailable (503).
Upload a streamed part
PUT /v1/uploads/:id/parts/:part
Send one binary part to a worker session. The API key must have write permission on the file path. An s3 session refuses parts with 409 upload_not_active; send its bytes to upload_url instead.
Non-final parts must be exactly part_size bytes. The final part must be exactly the remaining bytes of expected_size_bytes. Send the exact byte count in Content-Length; any other size is a 400 invalid_part. Sending the same part number again replaces its recorded part.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Upload session ID. |
part | integer | Yes | Positive part number, starting at 1 and no greater than total_parts. |
Request body
Send the raw binary bytes. Content-Length is required.
Response fields
| Field | Type | Description |
|---|---|---|
part_number | integer | Stored part number. |
etag | string | Entity tag returned for the part. |
size_bytes | integer | Stored part size. |
Returns 200 OK.
Request
curl -X PUT https://agentfs.cloud/v1/uploads/up_7c31/parts/1 \
-H "Authorization: Bearer $AGENTFS_KEY" \
-H "Content-Length: $PART_SIZE" \
--data-binary @part-001.binResponse
{
"part_number": 1,
"etag": "etag-part-1",
"size_bytes": 16777216
}Errors
unauthorized (401), csrf_rejected (403), no_active_organization (403), invalid_agent_label (400), invalid_part (400), upload_not_found (404), upload_not_active (409), upload_expired (410), forbidden (403), length_required (411), upload_unavailable (503), invalid_token (400), unknown_bucket (400), part_upload_failed (502), upload_failed (502), internal_error (500).
Complete an upload
POST /v1/uploads/:id/complete
Verify every part and mark the file ready. The API key must have write permission on the file path. A single-PUT s3 session without sha256 completes on its own a few seconds after its bytes arrive; calling this endpoint just finishes it sooner.
For a worker or multipart s3 session the server requires contiguous part numbers from 1 through total_parts and an exact sum of part sizes. For a single-PUT s3 session it checks that the object at upload_url has arrived with exactly expected_size_bytes bytes, and verifies sha256 when one was declared; 409 object_not_ready means the bytes are not there yet. A retry after completion returns the file object again. 502 upload_complete_failed means storage could not assemble the parts; it is safe to retry, and if storage already finished, the retry succeeds.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Upload session ID. |
Response fields
On success, return a file object with 200 OK. During a concurrent completion, return the upload session with 202 Accepted while the session remains completing.
Request
curl -X POST https://agentfs.cloud/v1/uploads/up_7c31/complete \
-H "Authorization: Bearer $AGENTFS_KEY"Response
{
"type": "file",
"id": "f_8f2c",
"name": "video.mp4",
"path": "media/video.mp4",
"label": "Demo video",
"content_type": "video/mp4",
"content_disposition": "attachment",
"size_bytes": 150000000,
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"etag": "etag-video",
"revision": 1,
"visibility": "private",
"expires_at": "2026-03-09T12:00:00.000Z",
"tags": [],
"metadata": {},
"agent_id": null,
"run_id": null,
"url": null,
"markdown": null,
"created_at": "2026-03-02T12:00:00.000Z",
"updated_at": "2026-03-02T12:03:00.000Z"
}Errors
unauthorized (401), csrf_rejected (403), no_active_organization (403), invalid_agent_label (400), upload_not_found (404), forbidden (403), upload_not_active (409), upload_expired (410), missing_parts (409), size_mismatch (409 or 422), checksum_missing (409), checksum_mismatch (422), upload_not_pending (409), object_not_ready (409), upload_unavailable (503), invalid_token (400), unknown_bucket (400), upload_complete_failed (502), upload_finalize_failed (502), upload_failed (502), internal_error (500).
Abort an upload
DELETE /v1/uploads/:id
Abort an unfinished session and remove its reserved file. The API key must have delete permission on the file path.
An unknown session is treated as already aborted and returns 204. A completed session cannot be aborted.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Upload session ID. |
Response
Returns 204 No Content with an empty body.
HTTP/1.1 204 No ContentRequest
curl -X DELETE https://agentfs.cloud/v1/uploads/up_7c31 \
-H "Authorization: Bearer $AGENTFS_KEY"Errors
unauthorized (401), csrf_rejected (403), no_active_organization (403), invalid_agent_label (400), forbidden (403), upload_completed (409), upload_abort_unavailable (503), upload_abort_failed (502), upload_unavailable (503), invalid_token (400), upload_failed (502), internal_error (500).
AgentFS