Concepts
Media and images
Your bucket, your bytes. Public and private projects, and why a static build must ask for permanent URLs.
ZevContent does not host your images. Media lives in your object storage: ZevCloud Storage, S3, R2, or anything S3-compatible. No byte of it passes through us.
Uploads go browser → your bucket, using a URL we sign. Reads go browser → your bucket, using a URL we resolve. We store a record of the asset, never the file.
What an image looks like in a response
Every image field, gallery entry, featured image, avatar and og image is the
same object:
{
"url": "https://cdn.example.com/media/hero.jpg",
"alt": "The team at the launch",
"width": 1600,
"height": 900,
"content_type": "image/jpeg"
}
width and height are there so you can reserve space and avoid layout shift
without measuring the file. They are null when we could not determine them.
You never receive a raw med_… id in a content response. Resolution happens
server-side, in one batched pass per request, so an article with a dozen
pictures costs the same round trip as one without.
Public and private projects
Set per project, in the dashboard, and it changes the shape of every image URL.
Public bucket. URLs are permanent. They keep working forever, they cache well, and they are safe to bake into static HTML.
Private bucket. URLs are presigned GETs with an expiry, and the object
gains one extra key:
{
"url": "https://…?X-Amz-Signature=…",
"url_expires_at": "2026-09-02T11:30:00.000Z",
"…": "…"
}
The response also carries a short Cache-Control, because caching a URL past
its own expiry serves a broken image.
Static builds: ask for permanent URLs
A static build bakes URLs into HTML that outlives the build. A presigned URL dies hours later, and the failure appears long after the deploy that caused it, on a page nobody was looking at.
So say what you need:
curl "https://api.zevcontent.net/v1/blog/articles?media_urls=permanent" \
-H "Authorization: Bearer $ZEVCONTENT_KEY"
On a public project this changes nothing. On a private project the request is
refused with permanent_media_urls_unavailable, rather than handing you
links that will break:
This project’s media bucket is private, so every image URL is signed and expires, so there are no permanent URLs to give you. Either make the bucket publicly readable and switch the project to public media access in the dashboard, or fetch this content at request time and re-fetch before
url_expires_at, or download each image at build time and serve it yourself.
That is a build failure instead of a silent breakage weeks later, which is the whole reason the parameter exists.
Knowing before you fetch
/v1/introspection reports the project’s media policy once, next to the schema
that mentions it:
"media": { "access": "private", "urls": "signed", "url_ttl_seconds": 3600 }
so a client does not have to infer it from the presence of a key inside one field’s shape.
Images inside rich text
The same rules apply to image nodes in a rich text document, with one
addition: a node may reference an external URL instead of a library asset.
An external image never expires and is never fetched by us. See
Rich text.
Updated at, Wednesday, September 2, 2026