Skip to content
Viresso. Documentation Open Viresso

Files API

On this page

The files API uploads and deletes workspace files, and downloads them through permanent tokenized URLs or expiring signed URLs returned in file and entry payloads.

Upload a file

POST /api/v1/{workspace}/files

Send one file per request using multipart/form-data. The API key needs the files:write ability (or *), and its creator needs the workspace's create-file permission. No browser session or CSRF token is required. Enable Upload files when creating or editing the key in Settings → API Keys.

curl "https://app.example.com/api/v1/acme/files" \
  -H "Authorization: Bearer $VIRESSO_API_KEY" \
  -H "Accept: application/json" \
  -F "file=@./photo.png"

The response is 201 Created with the file object shown below, without a data wrapper. Use its id in an entry attachment field. Uploads also appear in the workspace's file library. Each request uses the workspace in the URL.

Do not set the multipart Content-Type manually: curl or FormData must include its boundary. See Code Examples for Laravel and Node.js uploads.

JSON clients can send a base64-encoded file instead, with Content-Type: application/json:

{
  "file": {
    "name": "hello.txt",
    "type": "text/plain",
    "content": "SGVsbG8="
  }
}

Multipart uploads support files up to 250 MiB (262,144,000 bytes) by default, including .exe, .dmg, and .apk installers. The platform upload settings can restrict the permitted types and size. Installer validation checks the filename extension and detected content type, including binary EXE/DMG and ZIP-based APK formats; the client-supplied MIME type is not trusted. Compressed .dmg files detected as application/zlib must also have the UDIF koly signature at the start of their final 512-byte trailer. Files are streamed to storage and served as downloads.

JSON uploads retain a hard limit of 150 MiB decoded, even when the platform limit is higher. Use multipart for larger files. Invalid or missing files return 422 with errors.file. URL imports are not supported by this endpoint.

PHP and any reverse proxy must also allow the request. The project's php.ini sets upload_max_filesize and post_max_size to 1G, leaving room for multipart overhead; php artisan serve applies these limits automatically. For production PHP/FrankenPHP, load equivalent settings and configure any proxy body-size limit above 250 MiB. A request rejected before reaching Laravel may return 413. Existing installations must run php artisan migrate to upgrade saved defaults; custom administrator restrictions are preserved.

Uploads are limited to 60 requests per minute per API key by default, separately from downloads. Configure RATE_LIMIT_FILE_UPLOADS_PER_MINUTE to change this; local development limits are disabled unless enabled explicitly.

Successful API uploads add the stored file's byte size plus the outgoing response size to workspace bandwidth usage. Base64 uploads count decoded file bytes; multipart and base64 encoding overhead are excluded. Request logs keep these separately as upload_size and response_size. Rejected uploads contribute only their response bytes. Storage usage is tracked separately, and metrics appear after queued jobs are processed. These upload metrics apply to this API endpoint, not manager or direct-to-S3 uploads.

Read file details or refresh a temporary URL

GET /api/v1/{workspace}/files/{file}

Returns the file object directly, including fresh download and image links with their expiry timestamps (temporary_url, temporary_url_expires_at, temporary_image_url, and temporary_image_url_expires_at). Use the integer file ID from an upload or entry attachment. The API key needs files:read (or *), and its creator needs view-file permission. Enable Read files in Settings → API Keys. Upload-only keys need this additional ability to refresh links through this endpoint. Origin restrictions and workspace suspension still apply; foreign-workspace, user-owned, and missing files return 404. Responses are private and non-cacheable.

curl "https://app.example.com/api/v1/acme/files/18" \
  -H "Authorization: Bearer $VIRESSO_API_KEY" \
  -H "Accept: application/json"

Choose a URL lifetime

Add ?expires_in=120 to the file-details request to issue both temporary URLs for two minutes. expires_in is an optional integer duration in seconds, from 30 to 600 inclusive. Omitting it keeps the 600-second (10-minute) default. Empty, non-integer, and out-of-range values return 422 with errors.expires_in; the API does not silently clamp them.

curl "https://app.example.com/api/v1/acme/files/18?expires_in=120" \
  -H "Authorization: Bearer $VIRESSO_API_KEY" \
  -H "Accept: application/json"

Viresso computes expiry using its server clock. Both returned expiry timestamps match the requested lifetime. Upload and entry responses retain their 10-minute default. Apply expires_in only when requesting file details: adding it to an issued signed URL invalidates the signature. Shorter lifetimes do not add early revocation or retract downloaded copies.

Temporary downloads

Share the returned temporary_url when a recipient should have a link that expires at temporary_url_expires_at (10 minutes by default). The URL is a bearer credential: anyone holding it can download during that period, without an API key. It does not expose the file's permanent access token.

The signed route is GET /api/v1/{workspace}/files/{file}/temporary?uuid=...&expires=...&signature=.... Use the complete returned URL unchanged. An expired, missing, or modified signature returns 403; a deleted file, missing storage object, or workspace mismatch returns 404. Suspended workspaces return 403.

curl --location "$TEMPORARY_URL" --output downloaded-file

Local storage streams the file. Storage with temporary URL support redirects with 307 to a storage URL that expires at the same original timestamp, rather than granting another 10 minutes. Both paths use private, no-store cache directives and download-safe content headers. A storage URL already issued before deletion may remain usable until its expiry. Expiry prevents new requests; it cannot revoke downloaded copies or necessarily stop a transfer already in progress.

download_url and image_url remain permanent for compatibility. Adding a temporary URL does not make those links expire or make the whole file private. Share only temporary_url or temporary_image_url for expiring access, and fetch file details or its entry again when you need a fresh link. Clients caching API payloads must honor temporary_url_expires_at.

File details and temporary downloads use the configured file request rate limit (RATE_LIMIT_FILES_PER_MINUTE, 1200 by default). Details are limited per API key; temporary downloads are limited per workspace/file, shared across its signed links.

Temporary inline images and resizing

Use temporary_image_url in an <img> element. It points to Viresso's signed endpoint and expires at the timestamp indicated by temporary_image_url_expires_at. Both fields are null for non-images or files without valid URL credentials.

Viresso verifies access, fetches the image from the existing images.viresso.com proxy, and streams that response inline with its image content type. All resizing and format conversion happens at the existing proxy. Viresso does not redirect the browser to the proxy or expose the permanent file token in the temporary link.

Append transform parameters to the returned URL:

const src = new URL(file.temporary_image_url);
src.searchParams.set('width', '640');
src.searchParams.set('height', '360');
src.searchParams.set('fit', 'cover');
src.searchParams.set('format', 'webp');
src.searchParams.set('quality', '80');
document.querySelector('img').src = src.toString();
Parameter Accepted values
width, height Integer pixels, 1–4096
fit contain, cover, scale-down, crop, pad
format auto, jpeg, png, webp, avif
quality Integer, 1–100

Only these five transform parameters may change without invalidating the signature. The proxy applies its own supported format/transform behavior. The browser's Accept header is forwarded for format negotiation. Changing dimensions never extends expiry. Do not change the path, uuid, expires, or signature, or add unrelated query parameters.

Expired/invalid links and suspended workspaces return 403; deleted or foreign-workspace files return 404; invalid transform values return 422 (malformed signed query parameters can return 403). Proxy failures, unexpected redirects, and non-image proxy responses return a generic 502 without exposing upstream error bodies or URLs. Missing proxy images return 404.

Responses are inline and private, no-store, so a new request always passes Viresso's access checks even when the proxy has cached a variant. Image variants and temporary downloads share the per-workspace/file rate limit. Fetch file details or its entry again for a new link. Previously displayed or saved images cannot be revoked by expiry.

The proxy must be able to reach the original file. Temporary image requests use the same remote proxy in local development; local-only files are unavailable unless that existing proxy can reach their source. The existing image_url and download behavior remain unchanged.

Delete a file

DELETE /api/v1/{workspace}/files/{file}

Use the integer id returned by an upload or entry attachment. The API key needs files:delete (or *), and its creator needs the workspace's delete-file permission. Enable Delete files in Settings → API Keys. The upload ability (files:write) alone does not allow deletion. No browser session, CSRF token, or request body is required.

curl -X DELETE "https://app.example.com/api/v1/acme/files/18" \
  -H "Authorization: Bearer $VIRESSO_API_KEY" \
  -H "Accept: application/json"

A successful deletion returns 204 No Content with an empty body. Deletion permanently removes the file record and its references from attachment fields in every entry locale in the workspace. Single attachments become null; multiple attachments lose only the deleted file. Entries and other files remain available. The normal file.deleted event and configured webhooks are dispatched.

The deleted file's tokenized download URL returns 404. Stored content shared with another file record is preserved; unreferenced storage is removed by scheduled pruning. Previously issued temporary storage URLs may remain usable until they expire, and external caches may retain copies.

Status Meaning
204 File deleted; no response body
403 Invalid key, origin restriction, missing ability or creator permission, or suspended workspace
404 Missing workspace, invalid file ID, or file not found in the workspace (including already deleted files)
429 Deletion rate limit exceeded; check Retry-After

Deletions are limited to 60 requests per minute per API key by default, separately from uploads and downloads. Configure RATE_LIMIT_FILE_DELETES_PER_MINUTE to change this; local development limits are disabled unless enabled explicitly.

Download a file

GET /api/v1/{workspace}/files/{file}/{uuid}/{token}

Downloads a file by its integer ID, UUID, and access token.

Parameter Location Required Description
{workspace} URL Yes Workspace name
{file} URL Yes File ID (integer)
{uuid} URL Yes File UUID
{token} URL Yes File access token

Authentication

This endpoint does not require an API key. Access is controlled by the tokenized URL and the route workspace. A suspended workspace returns 403 Forbidden. Otherwise, the API returns 404 if the file does not belong to the workspace, the UUID/token pair is invalid, or the storage object is missing.

Example

curl "https://app.example.com/api/v1/acme/files/18/2f5f1b3c-0f66-4a1a-a252-7e49e9c9f3d1/{token}" \
  --output hero.jpg

Response behavior

The response depends on the configured storage disk:

  • Disks without temporary URLs (including the default local disk): Streams the file directly as application/octet-stream with download-safe response headers.
  • Disks with temporary URL support (such as S3): Redirects to a storage URL signed for 10 minutes. The app refreshes its cached redirect every 5 minutes and marks the redirect response as non-cacheable so clients do not reuse expired storage URLs.

File response shape

Upload responses, file-detail responses, and files rendered as part of entry responses include:

{
  "id": 18,
  "name": "hero.jpg",
  "mime_type": "image/jpeg",
  "size": 2048576,
  "tags": ["homepage"],
  "download_url": "https://app.example.com/api/v1/acme/files/18/2f5f1b3c-0f66-4a1a-a252-7e49e9c9f3d1/{token}",
  "temporary_url": "https://app.example.com/api/v1/acme/files/18/temporary?uuid=2f5f1b3c-0f66-4a1a-a252-7e49e9c9f3d1&expires=1779271200&signature=...",
  "temporary_url_expires_at": "2026-05-20T10:00:00+00:00",
  "image_url": "https://images.viresso.com/api/v1/acme/files/18/2f5f1b3c-0f66-4a1a-a252-7e49e9c9f3d1/{token}/hero.jpg",
  "temporary_image_url": "https://app.example.com/api/v1/acme/files/18/temporary-image?uuid=2f5f1b3c-0f66-4a1a-a252-7e49e9c9f3d1&expires=1779271200&signature=...",
  "temporary_image_url_expires_at": "2026-05-20T10:00:00+00:00",
  "alt_text": "Hero image",
  "created_at": "2026-05-20T09:30:00.000000Z",
  "updated_at": "2026-05-20T09:45:00.000000Z"
}
Field Description
id Integer file ID
name Display name
mime_type MIME type (e.g. image/jpeg)
size File size in bytes
download_url Permanent tokenized workspace API download URL
temporary_url Signed download URL valid for the requested lifetime (10 minutes by default); no permanent token exposed
temporary_url_expires_at ISO 8601 expiry timestamp for temporary_url; both are null if a URL cannot be issued
image_url Permanent image proxy URL for images, or null for non-images
temporary_image_url Signed Viresso URL streaming the existing proxy inline, with optional resizing; null for non-images
temporary_image_url_expires_at ISO 8601 expiry of temporary_image_url
alt_text Alternative text for accessibility
tags Free-form labels

In local mode, image URLs point to the original download; image transforms require the configured image proxy. Image proxy URLs accept optional width, height, fit, format, and quality query parameters for server-side image variants. The tokenized download_url always returns the original file.