Skip to content

Files API

Store your Cloud project's files through /v1, charged per stored GB-day.

/v1/files is your Cloud project’s file store, like S3 or R2: files addressed by a path, kept until you delete them. Your code, your agents and your sandboxes share it. Each file’s path is unique in the project, such as reports/2026/q3.pdf; there are no folders to create, and a prefix like reports/ simply lists the paths that start with it.

Use a developer API key, or the key Dexto gives a deployed Function or Site, with https://api.dexto.ai. A key only ever sees its own project’s files. A CLI sign-in key gets 403.

POST /v1/files

Send the bytes as the body, with their Content-Type and Content-Length. Files up to 80 MiB go in one request:

Terminal window
curl -X POST "https://api.dexto.ai/v1/files?path=reports/q3.pdf" \
-H "Authorization: Bearer $DEXTO_API_KEY" \
-H "Content-Type: application/pdf" \
--data-binary @q3.pdf
Query or headerDefaultNotes
pathRequired. 1-1,024 bytes, no leading or trailing /, .. or \.
overwritefalsetrue replaces the file at path; otherwise a taken path is 409.
expires_atISO 8601 time to delete the file at.
X-Content-SHA256Lowercase hex SHA-256; the upload is refused when the bytes differ.

The response is the file, 201 when it was created and 200 when it replaced one:

{
"object": "file",
"id": "3a0f8c2e-…",
"path": "reports/q3.pdf",
"size_bytes": 482113,
"content_type": "application/pdf",
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"metadata": {},
"expires_at": null,
"created_by": { "api_key_id": "c41e…", "agent_id": null, "user_id": "8d2a…" },
"created_at": "2026-10-14T18:20:11.000Z",
"updated_at": "2026-10-14T18:20:11.000Z"
}

created_by names the key or agent that wrote the file first, each null once deleted, and the person who wrote it with their key or an OAuth token, null for agents and deployed code.

Files up to 5 GiB use an upload session. Start it with the file’s size and SHA-256:

POST /v1/files/uploads
Terminal window
curl -X POST https://api.dexto.ai/v1/files/uploads \
-H "Authorization: Bearer $DEXTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"path":"data/events.parquet","size_bytes":104857600,"sha256":"<hex>","content_type":"application/octet-stream"}'

The session has a part_size_bytes (16 MiB), a part_count and an upload_url. PUT each part to <upload_url>/<part_number>, starting at 1; every part but the last is exactly part_size_bytes. The upload URL works without an API key until the session expires a day later, so you can hand it to a client. Upload a part again to replace it; GET /v1/files/uploads/{id} lists the parts received so far.

Terminal window
curl -X PUT "$UPLOAD_URL/1" --data-binary @part-1

Then complete it. Dexto assembles the parts and checks them against size_bytes and sha256; when they differ, the upload is discarded and the request answers 400.

POST /v1/files/uploads/{upload_id}/complete

POST /v1/files/uploads/{upload_id}/abort abandons a session and its parts.

GET /v1/files

GET /v1/files?prefix=reports/&limit=100 lists files in path order. contains keeps paths that contain a string, ignoring case. When has_more is true, pass next_after as after for the next page. The older next_cursor carries the same value and is deprecated.

GET /v1/files/{file_id} reads a file’s metadata and GET /v1/files/{file_id}/content downloads its bytes.

POST /v1/files/{file_id}/url
Terminal window
curl -X POST "https://api.dexto.ai/v1/files/$FILE_ID/url" \
-H "Authorization: Bearer $DEXTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"expires_in":3600}'

Anyone with the returned url can download the file, without an API key, until expires_at. URLs last an hour by default and at most a day. Overwriting or deleting the file ends them. Files are never public otherwise; host public pages with Sites.

POST /v1/files/{file_id} changes metadata (up to 32 string entries and 4,096 bytes, replacing the old metadata), content_type or expires_at (null keeps the file until deleted). DELETE /v1/files/{file_id} deletes the file and its bytes.

Give an agent a files tool to find, read, search, write, edit, delete and share files under a prefix. A running sandbox copies files in with POST /v1/sandboxes/{id}/files/import and saves its files with /files/export. A media generation started with save_to saves its outputs as files; each resource of the generation names its file_id and the file_path it was saved at.

Stored files are charged once a day to the project, at Cloudflare R2’s storage price plus 25%: $0.000625 per GB a day, about $0.019 per GB a month. Uploads are checked against your credits and spend limits before any byte is accepted. Listing, reading, downloading, signed URLs, deleting and sandbox copies are free.

StatusCodeWhen
400validation_failedAn invalid path or field, or bytes that differ from SHA-256.
402insufficient_creditsThe organization’s credits do not cover the upload.
402spend_limit_reachedThe project or key reached its monthly spend limit.
403forbiddenThe key is not a developer key or a deployed key.
404not_foundNo such file or upload in the key’s project.
409conflictThe path is taken, or the upload is missing parts.
413payload_too_largeA one-request upload over 80 MiB.
429rate_limitedOver 600 reads or 120 writes a minute for the organization.