Downloads
Resolve time-limited download URLs for file assets — one asset at a time, or many in a single batched call.
Download endpoints return presigned GET URLs for file assets. For stream (video) assets they
return playback information instead — a single endpoint covers both
kinds so you don't need to branch before calling.
Both endpoints require authentication and can return
400 / 401 / 403 / 404 / 500.
Create a download URL
POST /api/v1/media/{id}/download-url → 200 OK
What it is — Returns a time-limited link for one asset: a presigned GET URL for a file, or playback info for a video. One endpoint handles both kinds.
When to use it — Serving a single stored item to a client: display a place photo, download a note's file attachment, or start playing a feedback video.
How to use it — POST (optionally set expires_in). Then:
- File asset → open/download
download_url(a presigned R2GET). - Stream asset → use
playback(whenstream_statusisready); on-prem streams also include adownload_urlfor the source file in R2.
Scope — Token owner or service key. The asset must be uploaded.
Request body
Optional:
| Field | Type | Notes |
|---|---|---|
expires_in | integer (60–86400) | URL lifetime in seconds. Defaults to R2_SIGNED_URL_EXPIRY (3600). |
curl -X POST https://media.example.com/api/v1/media/550e8400-e29b-41d4-a716-446655440000/download-url \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "expires_in": 900 }'Batch download URLs
POST /api/v1/media/download-urls → 200 OK
What it is — Resolves download URLs / playback for many assets in one round-trip, with partial success.
When to use it — Rendering a gallery or feed: every photo of a Maps place, all attachments on a post, or a mixed list of files and videos — without a request per item.
How to use it — POST asset_ids (≤ 50). The call returns 200 even if some assets fail —
inspect each item's error. Items come back in input order.
Scope — User token (own assets only) or service key (owner_user_id required).
This is partial-success: the call returns 200 even if some assets fail, and each item carries
its own error.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
asset_ids | uuid[] | ✅ | 1–100 ids (deduped). Capped by BATCH_MEDIA_MAX_ASSETS (default 50). |
owner_user_id | string (1–255) | ⚠️ | Required for service-key callers. |
expires_in | integer (60–86400) | — | Applied to every resolved URL. |
Each item mirrors the single-download shape plus an error field, one of:
not_found, forbidden, not_ready, not_stream, or null on success.
curl -X POST https://media.example.com/api/v1/media/download-urls \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"asset_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"770e8400-e29b-41d4-a716-4466554400ff"
],
"expires_in": 900
}'Always inspect each item's error — a 200 on the envelope does not mean every asset
resolved. not_ready means a stream asset is still transcoding; retry after its webhook fires.