HTTP API
Media
A media field gives you an asset id, not a URL. Two endpoints turn that id into something you can render: one redirects to the file, the other describes it.
Ids, not URLs
When an entry has a media field, its value is an opaque asset id:
"cover_image": "0c4d9e11-7b2a-4c38-9de5-1f60a7b8c9d0"
Putting that straight into an <img src> renders nothing. Build the media
URL from it instead. The pattern is stable and safe to construct by hand:
<!-- A media field's value is an id. This URL redirects to the file. -->
<img src="https://acme.sedgemark.app/api/v1/media/0c4d9e11-7b2a-4c38-9de5-1f60a7b8c9d0" alt="" /> The underlying storage location is an implementation detail that can change: a different bucket, a different provider, a CDN in front. Storing ids and resolving them at read time means none of that is a content migration. It is also what lets the file itself be served from a short-lived signed URL rather than a permanently public one.
Fetching the file
No authentication.
This does not return the file. It returns a 302 redirect to a signed storage
URL valid for about an hour. Browsers, image tags, CSS url() and fetch all follow it automatically, so in practice you use it exactly as if it
were the file.
| Status | Meaning |
|---|---|
| 302 | Redirect to the signed URL. Cache-Control: public, max-age=3000, s-maxage=3000. The shared-cache directive matters if you put a CDN in front. |
| 400 | { "error": "Invalid ID" }: the id is not a well-formed UUID. |
| 404 | { "error": "Not found" }: no asset with that id in this workspace. |
The signed URL you are redirected to expires in about an hour. The /api/v1/media/{id} URL does not expire: it is stable for the life of
the asset. Store and share that one. If you capture the resolved storage URL into a
build artifact or a database, your images will work in testing and start failing
roughly an hour later.
The max-age above is deliberately a little under the signing window, so a
browser refetches the redirect before what it points at goes stale.
A one-line helper is usually all you need:
const SEDGEMARK_BASE_URL = 'https://acme.sedgemark.app'
function resolveMedia(id) {
return `${SEDGEMARK_BASE_URL}/api/v1/media/${id}`
}
// post.cover_image is an id, or null when nothing was picked.
const coverUrl = post.cover_image ? resolveMedia(post.cover_image) : null
The generated TypeScript client exports a resolveMedia that does exactly
this. See TypeScript client.
Reading an asset’s metadata
No authentication.
Returns JSON rather than a redirect. Useful when you need the intrinsic dimensions before the image loads: reserving layout space, choosing an aspect ratio, or picking between renditions.
{
"width": 1600,
"height": 900,
"mimeType": "image/jpeg",
"size": 284713,
"filename": "harbour-at-dusk.jpg",
"alt_text": "Fishing boats moored at sunset"
} | Key | Type | Notes |
|---|---|---|
| width | number | null | Pixel width. Null for anything that is not a raster image, and for assets uploaded before dimension probing existed. |
| height | number | null | Pixel height. Same caveat as width. |
| mimeType | string | For example image/png. |
| size | number | Bytes. |
| filename | string | The original upload filename. |
| alt_text | string | null | Alt text set in the dashboard, when it was set. |
The mixed casing (mimeType in camel case beside alt_text in snake case) is the actual contract, not a typo in these docs.
const res = await fetch(
`https://acme.sedgemark.app/api/v1/media/${id}/meta`
)
const meta = await res.json()
// width/height are null for anything that is not a raster image.
if (meta.width && meta.height) {
// Reserve the box before the image loads, so the page does not jump.
}
Metadata does not change after upload, so this response is cacheable for a day (
Cache-Control: public, max-age=86400). The same 400 and 404 responses apply as above; see Errors & rate limits for the rest.
Unlike the redirect above, this endpoint returns JSON and sends no CORS headers, so a
cross-origin fetch is blocked, even though no key is involved. Call it from
a build step or a server. Loading the image itself as an <img src> is unaffected, because that is not a CORS-governed request.
Media is always public
Both endpoints on this page are unauthenticated for every asset, including assets
referenced only from private collections. Media is decoupled from collection
visibility and there is no per-asset access control.
In practice this means: anyone holding an asset id can fetch the file. Ids are UUIDs and are not enumerable, but they are not a permission either. Do not upload files whose contents would be harmful to disclose and rely on a private collection to protect them.
This is a deliberate trade. Images are the thing a static site needs to reference from
markup that never carries a credential, and making them public is what lets a <img src> point straight at Sedgemark with no key and no proxy in between.
What Sedgemark does not do
- No upload over this API. Assets
are uploaded from the dashboard’s media library, which enforces its own size and
file-type limits and shows them to you there.
/api/v1reads media; it does not accept it. - No on-demand transforms. There is
no
?w=,?h=or?fit=. You get the file as uploaded. Resize at build time, or put an image CDN in front of the media URL. - No format negotiation. Upload the format you want served.
-
Dimensions are best-effort, not guaranteed.
They are probed at upload for raster images; a file whose dimensions could not be read
still uploads successfully with
nullfor both. Always branch on null rather than assuming a number.