Skip to content

Files & media URLs

Files that users upload (documents, spreadsheets, images) and files that workflows and apps produce are stored under the API host's /media/ tree and served by the API itself. Because an <img> tag or a download link cannot carry an Authorization header, media is fetched with a signed URL instead of the session token.

How signed URLs work

  1. Ask the API to sign a media path: GET /api/v1/media/sign/?path=<media path>.
  2. The response contains a url of the form /media/<path>?expires_at=<unix seconds>&user_id=<id>&signature=<hmac>.
  3. Fetch that URL (no headers needed) before it expires. The default lifetime is 1 hour; you may request up to 24 hours with ttl.

A signed URL is a stateless HMAC — it cannot be revoked before it expires, which is why the lifetime is capped. For a link that must never expire (for example a dashboard embedded in a wiki) create a permanent link with POST /artifacts/links/ (see Studio); permanent links are database rows and can be deleted at any time.

Media paths

Media paths are relative to the media root and always start with the organisation name, then the module, then the username, for example Acme/semantic-search/jane/report.pdf. Endpoints that create files return the path (or a full /media/... URL) in their responses; pass that value to the signing endpoint as-is — percent-encoded or not, it is normalised.

Legacy access modes

The media server also accepts ?token=<access_token> (deprecated — it leaks the session token into logs and browser history) and ?link=<token> for permanent links.

Endpoints

MethodPathPurpose
GET/api/v1/media/sign/Get a signed URL for a media file
GET/media/{path}Download a media file

GET /api/v1/media/sign/

Get a signed URL for a media file

Returns a URL that fetches the file without an Authorization header. The default lifetime is 1 hour (MEDIA_SIGNED_URL_TTL_SECONDS); ttl can raise it to at most 24 hours. Signing does not check that the file exists.

Auth: Session token · In the app: Every image, document preview and download link

Query parameters

FieldTypeRequiredDescription
pathstringyesMedia path — with or without a leading /media/, encoded or not.
ttlintegernoLifetime in seconds. Values above the ceiling are clamped; invalid values fall back to the default.

Response 200 — Signed URL.

FieldTypeDescription
pathstringNormalised media path.
urlstringRelative URL to fetch (prefix with the API host).
expires_atintegerUnix time (seconds).
ttlintegerSeconds until expiry.
json
{
  "path": "Acme/semantic-search/jane/report.pdf",
  "url": "/media/Acme/semantic-search/jane/report.pdf?expires_at=1758535272&user_id=42&signature=k8Fh2…",
  "expires_at": 1758535272,
  "ttl": 3600
}

Response 400path missing.

json
{
  "detail": "path is required"
}

Example

bash
TOKEN=...
SIGNED=$(curl -s "https://api.example.com/api/v1/media/sign/?path=Acme/semantic-search/jane/report.pdf" \
  -H "Authorization: Token $TOKEN" | python -c "import json,sys; print(json.load(sys.stdin)['url'])")
curl -o report.pdf "https://api.example.com$SIGNED"

GET /media/{path}

Download a media file

Serves a stored file. Exactly one of the following must be present:

ModeQuery parametersNotes
Signed URLexpires_at, user_id, signatureIssued by GET /api/v1/media/sign/. Preferred.
Permanent linklinkToken from POST /artifacts/links/. Public until the link is deleted.
LegacytokenAn access token. Deprecated; the file must live under the caller's own username folder.

The response is the raw file with its content type; range requests are not supported.

Auth: Signed URL, permanent link, or legacy token (see description) · In the app: Every embedded image, viewer and download

Path parameters

FieldTypeRequiredDescription
pathstringyesMedia path (organisation/module/username/…).

Query parameters

FieldTypeRequiredDescription
expires_atintegernoFrom the signing response.
user_idintegernoFrom the signing response.
signaturestringnoFrom the signing response.
linkstringnoPermanent-link token.
tokenstringnoLegacy — an access token.

Response 200 — The file.

Content type: application/octet-stream

Response 403 — Missing or invalid credentials, expired signature, deleted link, or (legacy mode) a path outside the caller's folder.

Content type: text/html

text
Invalid or expired signed URL

Response 404 — No such file.

Example

bash
curl -o report.pdf "https://api.example.com/media/Acme/semantic-search/jane/report.pdf?expires_at=1758535272&user_id=42&signature=k8Fh2..."

Finblade documentation