File Storage
Persist files your agents generate or fetch — images, documents, datasets — and retrieve or share them later. Bytes flow directly between your client and Google Cloud Storage via short-lived presigned URLs; Sapiom only mints the URLs and tracks metadata. No GCS account, no bucket setup.
Ways to call this
Section titled “Ways to call this”Reach file storage from a step through the typed ctx.sapiom.fileStorage client (upload, getDownloadUrl, list, setVisibility, delete) — sapiom_dev_orchestrations_check validates the call at author time. See Using Capabilities.
Call sapiom_file_upload, sapiom_file_download, sapiom_file_list, sapiom_file_set_visibility, or sapiom_file_delete directly via the remote MCP — no workflow to author. Run tool_discover to find tools by goal.
Wrap your HTTP client with @sapiom/fetch (or @sapiom/axios) and call the gateway directly — from anywhere, including inside a workflow step. See the Quick Example below and the API reference for full parameters.
Quick Example
Section titled “Quick Example”import { createFetch } from "@sapiom/fetch";
const sapiomFetch = createFetch({ apiKey: process.env.SAPIOM_API_KEY, agentName: "my-agent",});
const baseUrl = "https://file-storage.services.sapiom.ai";
// 1. Create an upload slot — returns a presigned PUT URL + file_idconst slotRes = await sapiomFetch(`${baseUrl}/upload`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content_type: "image/png", file_name: "chart.png" }),});const { file_id, upload_url, required_headers } = await slotRes.json();
// 2. PUT the bytes straight to GCS (NOT through Sapiom) using the presigned URLawait fetch(upload_url, { method: "PUT", headers: required_headers, body: pngBytes });
// 3. Once verified (poll GET /files until status is "uploaded"), mint a download URLconst dlRes = await sapiomFetch(`${baseUrl}/download/${file_id}`);const { download_url } = await dlRes.json();const bytes = await fetch(download_url).then((r) => r.arrayBuffer());How It Works
Section titled “How It Works”File storage is control-plane only: the gateway issues presigned Google Cloud Storage URLs and tracks file metadata, but the bytes never pass through Sapiom — your client uploads and downloads them directly to/from GCS using the short-lived URLs.
Upload is two steps: create a slot (POST /upload returns a presigned PUT URL valid for 15 minutes), then PUT the bytes to that URL. A background verification sweep then confirms the object landed and flips the file’s status from pending_upload to uploaded — until it’s verified, GET /download/:id returns 409, so poll GET /files until the file’s status is uploaded before downloading. Download is the reverse — GET /download/:id returns a presigned GET URL.
The PUT (and the download GET) go to the presigned URL with your platform’s native fetch, not sapiomFetch — never send your Sapiom API key to GCS — and pass the slot’s required_headers unmodified.
Files are private by default (only the owning tenant can download) or public (any tenant with a Sapiom API key can download — never anonymous).
Provider
Section titled “Provider”Powered by Google Cloud Storage. Presigned URLs are signed locally (V4) and expire after 15 minutes.
API Reference
Section titled “API Reference”Endpoints
Section titled “Endpoints”Base URL: https://file-storage.services.sapiom.ai
| Method | Path | Description | Pricing |
|---|---|---|---|
| POST | /upload | Create a presigned upload slot | No charge* |
| GET | /download/:id | Mint a presigned download URL | No charge* |
| GET | /files | List your files (metadata only) | Free |
| PATCH | /:id | Change a file’s visibility | No charge* |
| DELETE | /:id | Delete a file | No charge* |
Create upload slot
Section titled “Create upload slot”Endpoint: POST https://file-storage.services.sapiom.ai/upload
Returns a presigned PUT URL (15-minute TTL) and a file_id. PUT the bytes directly to that URL with the returned required_headers.
Request
Section titled “Request”| Parameter | Type | Required | Description |
|---|---|---|---|
content_type | string | Yes | MIME type, e.g. image/png |
file_name | string | No | Original file name |
visibility | string | No | private (default) or public |
expected_file_size | number | No | Declared size in bytes |
{ "content_type": "image/png", "file_name": "chart.png", "visibility": "private" }Response
Section titled “Response”{ "file_id": "8f1d1e01-db60-493d-9be8-69d46d95f399", "upload_url": "https://storage.googleapis.com/...", "expires_at": "2026-06-25T04:15:00.000Z", "required_headers": { "content-type": "image/png", "x-goog-content-length-range": "0,5368709120" }}Then upload the bytes: PUT {upload_url} with the required_headers and the file as the body.
Get download URL
Section titled “Get download URL”Endpoint: GET https://file-storage.services.sapiom.ai/download/:id
Returns a presigned GET URL (15-minute TTL) and records one download. Private files are downloadable only by the owning tenant; public files by any tenant.
Response
Section titled “Response”{ "download_url": "https://storage.googleapis.com/...", "expires_at": "2026-06-25T04:15:00.000Z" }List files
Section titled “List files”Endpoint: GET https://file-storage.services.sapiom.ai/files (free)
Returns the calling tenant’s non-deleted files (metadata only — no download URLs). Paginated.
Request
Section titled “Request”| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | No | Max files (1–100, default 50) |
offset | number | No | Pagination offset (0–100000, default 0) |
GET /files?limit=50&offset=0Response
Section titled “Response”{ "files": [ { "file_id": "8f1d1e01-db60-493d-9be8-69d46d95f399", "file_name": "chart.png", "content_type": "image/png", "visibility": "private", "status": "uploaded", "expected_file_size": "20480", "actual_file_size": "20480", "created_at": "2026-06-25T04:00:00.000Z", "uploaded_at": "2026-06-25T04:00:05.000Z", "deleted_at": null, "download_request_count": 3 } ], "limit": 50, "offset": 0, "has_more": false}A file’s status is one of pending_upload, uploaded, deleted, or error_state. Size fields are returned as strings (the values are 64-bit); download_request_count is a number. file_name, uploaded_at, actual_file_size, and deleted_at are null when not applicable (e.g. before verification).
Set visibility
Section titled “Set visibility”Endpoint: PATCH https://file-storage.services.sapiom.ai/:id
Flip a file between private and public. Returns the updated metadata.
Request
Section titled “Request”{ "visibility": "public" }Delete
Section titled “Delete”Endpoint: DELETE https://file-storage.services.sapiom.ai/:id
Deletes the object and soft-deletes the metadata. Idempotent (deleting an already-deleted file succeeds). Only the owning tenant can delete. Returns 204 No Content.
Error Codes
Section titled “Error Codes”| Code | Description |
|---|---|
| 400 | Invalid request (e.g. malformed content_type) |
| 401 | No valid Sapiom tenant identity — authenticate with the Sapiom SDK |
| 403 | Forbidden — private file owned by another tenant |
| 404 | File not found, deleted, or in an error state |
| 409 | File is still pending_upload (bytes not yet verified) |
| 429 | Rate limit exceeded |
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Max upload size | 5 GiB (default) |
| Presigned URL TTL | 15 minutes |
| List page size | 1–100 (default 50) |
Pricing
Section titled “Pricing”File-storage operations are not charged today. Unlike per-call capabilities — which settle a micropayment on each request — file storage is a control-plane service and is not metered per request. When usage-based billing ships it will be metered on usage (e.g. data stored and downloads) rather than charged per call. This page will be updated when it lands.