Files
Upload, list, read, delete, and share files through the file system API.
Use /v1/files for file operations and for quick one-request uploads up to 100 MiB. Resumable uploads handle files up to 5 TB.
File object
The API returns this object for a file:
| Field | Type | Description |
|---|---|---|
type | string | Always file. |
id | string | File identifier. |
name | string | The final path segment. |
path | string | The path without its leading slash. |
label | string or null | Optional display label. |
content_type | string | Stored media type. |
content_disposition | string | inline or attachment. |
size_bytes | integer | Stored size in bytes. |
sha256 | string or null | SHA-256 digest when available, as lowercase hexadecimal. |
etag | string or null | Storage entity tag. Use it as the If-Match precondition on a replace or a move. |
revision | integer | Byte generation. A new file starts at 1; each replace adds one. |
visibility | string | Effective visibility: public, unlisted, or private. |
expires_at | string or null | ISO 8601 expiration time. |
agent_id | string or null | The X-Agent-ID sent by the request that created the file. |
run_id | string or null | The X-Run-ID sent by the request that created the file. |
url | string or null | Hosted URL for public and unlisted files. null for private files. |
markdown | string or null | Markdown link when url is present. |
created_at | string | ISO 8601 creation time. |
updated_at | string | ISO 8601 update time. |
A file with no explicit visibility uses its project's current default_visibility. Public and unlisted files return a hosted URL; private files require a temporary access URL.
Hosted URLs look like https://f.agentfsusercontent.com/f/<id>. Browser-executed content types (text/html, application/xhtml+xml, image/svg+xml, text/xml, and application/xml) are served from a separate, sandboxed origin instead: https://s.agentfsusercontent.com/f/<id>. This applies whatever type the upload declared. Older f.agentfs.cloud and s.agentfs.cloud links keep working: they redirect permanently to the same path on the new host.
Upload a file
POST /v1/files
Store one file in a single request with a key that has write permission on the target path. One request carries up to 100 MiB; send larger files through /v1/uploads.
Request body
Use multipart/form-data, or send the bytes as the raw request body.
Raw body. Send any Content-Type other than multipart/form-data, set Content-Length to the file size, and pass the fields below as query parameters (path, project, prefix, name, if_exists, visibility, expires_in, content_disposition, label). The Content-Type header is the file's type; application/octet-stream or a missing header means it is inferred from the name. Raw bodies stream straight through without being buffered, so this is the form to use from SDKs and scripts. With Idempotency-Key, a raw request is fingerprinted by its fields and size rather than its bytes.
curl -sS -X POST "https://agentfs.cloud/v1/files?path=research/notes.md" \
-H "Authorization: Bearer $AGENTFS_KEY" \
-H "Content-Type: text/markdown" \
--data-binary @notes.mdFrom a URL. Send application/json with url plus any of the fields below. The server fetches the URL itself and streams the bytes into storage, so nothing passes through your process. The URL must be http or https, must not redirect, must not point at a private or internal host, and may deliver at most 100 MB. content_type overrides what the source reports; name sets the filename when no path is given, defaulting to the last URL segment.
curl -sS -X POST https://agentfs.cloud/v1/files \
-H "Authorization: Bearer $AGENTFS_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/report.pdf","project":"research"}'Form. Use multipart/form-data with the fields below.
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | File bytes. |
path | string | No | project/filename or a nested path. Required for a chosen location; needs an API key. |
visibility | string | No | public, unlisted, or private for authenticated uploads. Uploads without a key may omit it or send unlisted. If omitted for an authenticated upload, the file follows the project's current default visibility. |
expires_in | string or null | No | Duration such as 24h or 7d using an s, m, h, d, or w suffix, or null. Authenticated values can be at most 52 weeks. Uploads without a key cannot set it and expire after 24 hours. If omitted, a project default may apply; null is accepted but also allows that project default to apply. |
content_disposition | string | No | inline or attachment. Defaults to inline. |
label | string or null | No | Optional label, at most 200 characters. |
if_exists | string | No | error (the default) or replace. See upsert to a path. replace requires path, so it requires an API key. |
Path segments may contain only letters, digits, ., _, and -; paths have at most 16 segments and 2,048 encoded characters. When an authenticated request omits path, use the key's default project when it has one, otherwise default, and generate a sanitized filename with a suffix. Uploads without a key generate a path under guest; see Upload without a key. The content type is inferred from the filename when the uploaded file reports no type or reports application/octet-stream.
Idempotency header
Idempotency-Key is optional and must contain 1–200 characters. A repeated key with the same request returns the existing file. Reusing it for a different request returns idempotency_conflict.
Returns 201 Created and a Location header containing /v1/files/{id}.
Upsert to a path
By default a live file at path returns path_exists. Send if_exists=replace to write over it instead:
curl -X POST https://agentfs.cloud/v1/files \
-H "Authorization: Bearer $AGENTFS_KEY" \
-F "file=@report.pdf;type=application/pdf" \
-F "path=reports/report.pdf" \
-F "if_exists=replace"The file keeps its ID, path, and hosted URL; revision goes up by one. Only the bytes and the content type change. Visibility, label, and expiry stay as they are, so set them with the upload that created the file. The response is 200 OK with the file object. A create is still 201 Created.
Uploading the same bytes and content type again is a no-op that returns the current file, so a retry is safe.
Send If-Match: <etag> to make the replacement conditional on the file you last read. A mismatch returns 412 precondition_failed and nothing is written.
if_exists=replace needs a path.
Response fields
Returns a file object.
Request
curl -X POST https://agentfs.cloud/v1/files \
-H "Authorization: Bearer $AGENTFS_KEY" \
-H "Idempotency-Key: report-2026-03" \
-F "file=@report.pdf;type=application/pdf" \
-F "path=reports/report.pdf" \
-F "visibility=private" \
-F "expires_in=24h" \
-F "label=Quarterly report"Response
{
"type": "file",
"id": "f_8f2c",
"name": "report.pdf",
"path": "reports/report.pdf",
"label": "Quarterly report",
"content_type": "application/pdf",
"content_disposition": "inline",
"size_bytes": 48213,
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"etag": "etag-report",
"revision": 1,
"visibility": "private",
"expires_at": "2026-03-03T12:00:00.000Z",
"agent_id": null,
"run_id": null,
"url": null,
"markdown": null,
"created_at": "2026-03-02T12:00:00.000Z",
"updated_at": "2026-03-02T12:00:01.000Z"
}Errors
unauthorized (401), invalid_agent_label (400), invalid_metadata (400), invalid_path (400), api_key_required (401), precondition_failed (412), file_pinned (409), forbidden (403), file_too_large (413), storage_limit_exceeded (413), billing_unavailable (503), quota_exceeded (429), idempotency_conflict (409), path_exists (409), region_unavailable (422), project_create_failed (500), folder_create_failed (500), upload_unavailable (503), upload_failed (502), anonymous_quota_unavailable (503), internal_error (500).
List files
GET /v1/files
List files visible to the API key, ready files by default. Results are ordered newest first, with the file ID breaking ties. The key must have read permission for the requested project or path.
Query parameters
Filters combine. They narrow the list; the key's read scopes are applied separately and always win.
| Parameter | Type | Required | Description |
|---|---|---|---|
project | string | No | Project name or project ID. Values beginning with p_ are treated as IDs; other values are treated as names. |
path | string | No | One exact path such as reports/2026/final.pdf. |
prefix | string | No | Path prefix such as reports/2026. Matches that path and everything below it. |
q | string | No | Case-insensitive text matched against the file name or stored path. search is the same filter under its older name. |
search | string | No | Alias of q. |
run_id | string | No | Files created by requests that sent this X-Run-ID. |
agent_id | string | No | Files created by requests that sent this X-Agent-ID. |
status | string | No | Comma list of ready, uploading, failed, and trashed. Defaults to ready. Use trashed to find deleted files you can restore. |
limit | integer | No | Number of items, from 1 through 200. Defaults to 50. |
cursor | string | No | Opaque cursor from the previous response. |
A filter value must contain 1–200 characters; anything else returns invalid_query.
curl -G "https://agentfs.cloud/v1/files" \
-H "Authorization: Bearer $AGENTFS_KEY" \
--data-urlencode "prefix=reports/2026" \
--data-urlencode "run_id=run-2026-04-01"Response fields
| Field | Type | Description |
|---|---|---|
items | array | File objects with project and project_id fields added. |
items[].project | string | Project name. |
items[].project_id | string | Project ID. |
items[].status | string | ready, uploading, failed, or trashed. |
next_cursor | string or null | Cursor for the next page, or null when there is no next page. |
Returns 200 OK.
Request
curl "https://agentfs.cloud/v1/files?project=p_1a2b&limit=50&q=report" \
-H "Authorization: Bearer $AGENTFS_KEY"Response
{
"items": [
{
"type": "file",
"id": "f_8f2c",
"name": "report.pdf",
"path": "reports/report.pdf",
"label": "Quarterly report",
"content_type": "application/pdf",
"content_disposition": "inline",
"size_bytes": 48213,
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"etag": "etag-report",
"revision": 1,
"visibility": "public",
"expires_at": null,
"agent_id": "research-agent",
"run_id": "run_2026_03_02",
"url": "https://f.agentfsusercontent.com/f/f_8f2c",
"markdown": "[Quarterly report](https://f.agentfsusercontent.com/f/f_8f2c)",
"created_at": "2026-03-02T12:00:00.000Z",
"updated_at": "2026-03-02T12:00:01.000Z",
"project": "reports",
"project_id": "p_1a2b",
"status": "ready"
}
],
"next_cursor": null
}Errors
unauthorized (401), invalid_agent_label (400), project_not_found (404), forbidden (403), invalid_query (400), internal_error (500).
Get a file
GET /v1/files/:id
Return one ready file with an API key that has read permission on its path.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | File ID. |
Response fields
Returns a file object.
Returns 200 OK.
Request
curl https://agentfs.cloud/v1/files/f_8f2c \
-H "Authorization: Bearer $AGENTFS_KEY"Response
{
"type": "file",
"id": "f_8f2c",
"name": "report.pdf",
"path": "reports/report.pdf",
"label": "Quarterly report",
"content_type": "application/pdf",
"content_disposition": "inline",
"size_bytes": 48213,
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"etag": "etag-report",
"revision": 1,
"visibility": "private",
"expires_at": "2026-03-03T12:00:00.000Z",
"agent_id": null,
"run_id": null,
"url": null,
"markdown": null,
"created_at": "2026-03-02T12:00:00.000Z",
"updated_at": "2026-03-02T12:00:01.000Z"
}Errors
unauthorized (401), invalid_agent_label (400), file_not_found (404), forbidden (403), internal_error (500).
Read a file's bytes
GET /v1/files/:id/content
Redirect to the file's bytes. The key needs read permission on the file path. A public or unlisted file redirects to its hosted URL; a private file redirects to a signed URL that is valid for 5 minutes. Use this to read a file you stored without minting a share link first.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | File ID. |
Response
Returns 302 Found with the URL in Location and an empty body. Follow the redirect with curl -L. If the file was blocked for abuse, the hosted URL answers 451 file_blocked.
HTTP/1.1 302 Found
location: https://f.agentfsusercontent.com/f/f_8f2c?expires=1772539200&v=1&signature=example-signature
cache-control: no-storeRequest
curl -L https://agentfs.cloud/v1/files/f_8f2c/content \
-H "Authorization: Bearer $AGENTFS_KEY"Errors
unauthorized (401), invalid_agent_label (400), file_not_found (404), forbidden (403), internal_error (500).
Delete a file
DELETE /v1/files/:id
Delete a file with an API key that has delete permission on its path, including an unfinished file row.
A delete moves the file to the trash, where it is purged after 30 days; its bytes still count toward your storage until then. Until it is purged, you can restore it. To free the space now, add ?permanent=true: the file is removed for good and cannot be restored. Both forms are safe to repeat. Repeating a permanent delete returns file_not_found (404), because the file is gone.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | File ID. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
permanent | true | No | Delete for good and free the storage now. Works on a file that is already in the trash. |
Response
Returns 204 No Content with an empty body.
HTTP/1.1 204 No ContentRequest
curl -X DELETE https://agentfs.cloud/v1/files/f_8f2c \
-H "Authorization: Bearer $AGENTFS_KEY"Errors
unauthorized (401), invalid_agent_label (400), file_not_found (404), forbidden (403), storage_unavailable (503), storage_delete_failed (502), internal_error (500).
Restore a file
POST /v1/files/:id/restore
Bring a file back from the trash. It returns to the same path with the same ID and hosted URL. The key needs write permission on the file path.
Restoring a file that is already live returns it again with 200, so a retry is safe. If another live file now holds the path, the response is 409 path_exists: move or delete that file first. A file that was purged, deleted with ?permanent=true, or never existed returns 404 file_not_found.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | File ID. |
Response
Returns 200 OK with the file object.
Request
curl -X POST https://agentfs.cloud/v1/files/f_8f2c/restore \
-H "Authorization: Bearer $AGENTFS_KEY"Errors
unauthorized (401), file_not_found (404), forbidden (403), path_exists (409), internal_error (500).
Create a file access URL
POST /v1/files/:id/access
Return a hosted URL for a file the API key can share. The key must have share permission on the file path.
Public and unlisted files return their existing URL and expires_at: null. Private files get a signed URL that defaults to one hour and may last up to seven days.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | File ID. |
Request body
Send a JSON object. The body may be empty.
| Field | Type | Required | Description |
|---|---|---|---|
expires_in | string | No | Duration such as 1h or 7d using an s, m, h, d, or w suffix. Used for private files only; the maximum is seven days. |
Response fields
| Field | Type | Description |
|---|---|---|
file_id | string | File ID. |
url | string | Hosted file URL. |
markdown | string | Markdown link using the file label or name. |
expires_at | string or null | ISO 8601 expiration for a private URL, or null for public and unlisted files. |
Returns 200 OK.
Request
curl -X POST https://agentfs.cloud/v1/files/f_8f2c/access \
-H "Authorization: Bearer $AGENTFS_KEY" \
-H "Content-Type: application/json" \
-d '{"expires_in":"24h"}'Response
{
"file_id": "f_8f2c",
"url": "https://f.agentfsusercontent.com/f/f_8f2c?expires=1772539200&v=1&signature=example-signature",
"markdown": "[Quarterly report](https://f.agentfsusercontent.com/f/f_8f2c?expires=1772539200&v=1&signature=example-signature)",
"expires_at": "2026-03-03T12:00:00.000Z"
}Errors
unauthorized (401), invalid_agent_label (400), file_not_found (404), forbidden (403), invalid_expiration (400), internal_error (500).
Revoke private links
POST /v1/files/:id/access/revoke
Invalidate every private link issued for this file so far. Links created before the revoke return 404 from then on; links you create afterwards work as usual. Use it when a private link leaked. The key needs write permission on the file path.
Public and unlisted URLs are not affected. To hide one of those, change the file's visibility or delete it.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | File ID. |
Response fields
| Field | Type | Description |
|---|---|---|
id | string | File ID. |
access_version | integer | The file's new access version. Private links carry it as v. |
Returns 200 OK.
Request
curl -X POST https://agentfs.cloud/v1/files/f_8f2c/access/revoke \
-H "Authorization: Bearer $AGENTFS_KEY"Response
{
"id": "f_8f2c",
"access_version": 2
}Errors
unauthorized (401), file_not_found (404), forbidden (403), internal_error (500).
AgentFS