A read returns scalars. Relations and media come back only when you ask, because the cost scales with the number of expanded fields and most reads want neither.
curl -G 'https://cms.example.com/api/v1/content/article/9c4b…' \
-H 'Authorization: Bearer apograph_…' \
-d 'relations=preview' -d 'relationFields=author,tags' \
-d 'media=preview' -d 'mediaFields=coverImage' const query = new URLSearchParams({
relations: 'preview',
relationFields: 'author,tags',
media: 'preview',
mediaFields: 'coverImage'
});
const response = await fetch(
'https://cms.example.com/api/v1/content/article/9c4b…?' + query,
{ headers: { Authorization: 'Bearer ' + process.env.APOGRAPH_TOKEN } }
);
const entry = await response.json();
const author = entry.relations.author.items[0];
const cover = entry.media.coverImage.items[0]; query Article($id: ID!) {
article(id: $id) {
id
title
author {
name
}
tags(pageSize: 20) {
total
items {
id
title
}
}
coverImage {
url
alt
}
}
} Relations
| Parameter | Effect |
|---|---|
relations=preview | Turns expansion on. Without it, no relation data is returned. |
relationFields | Comma-separated relation fields to expand. |
relationLimit | Links per expanded field, 1–100. Defaults to 20. |
Each expanded field returns one capped page of links plus the count of visible ones:
{
"id": "9c4b…",
"values": { "title": "…" },
"relations": {
"tags": {
"items": [{ "id": "1f9a…", "title": "Postgres" }],
"total": 34
}
}
}total is always the true count, not the number returned. Lowering
relationLimit therefore never hides the fact that more exist — which is what
lets a client decide whether to page without a second request to find out.
Only published targets are shown or counted. A draft or soft-deleted target is invisible to a public read, and it is not in the total either.
Naming a field that is not a relation, or one whose target type the workspace was not granted, is a 400.
Paging past the cap
When total exceeds what you got, page the field on its own route:
GET /api/v1/content/article/9c4b…/relations/tags?page=2&pageSize=50There is a group-addressed twin for localized types:
GET /api/v1/content/article/group/:groupId/relations/tags?locale=deMedia
| Parameter | Effect |
|---|---|
media=preview | Turns media expansion on. |
mediaFields | Comma-separated media fields to expand. |
mediaLimit | Assets per expanded field, 1–100. Defaults to 20. |
Expanded media returns asset metadata plus URLs under
/api/v1/media/assets/:id/raw, which take the same bearer token as the
request that produced them.
{
"media": {
"coverImage": {
"items": [
{
"id": "3f7a…",
"name": "hero.jpg",
"kind": "image",
"mimeType": "image/jpeg",
"url": "/api/v1/media/assets/3f7a…/raw",
"alt": "A lighthouse at dusk"
}
],
"total": 1
}
}
}total tells the truth here too.
An entry’s media can also be fetched on its own:
GET /api/v1/content/article/9c4b…/mediaAsset URLs are not public
/api/v1/media/assets/:id/raw requires the bearer token. These are not URLs you
can put in a public <img src> — a browser will not send your token, and you do
not want it to. Either proxy the bytes through your own server, or copy assets
to a CDN at build time. See the media API.
Sibling translations
?translations=previewAttaches the rest of the entry’s translation group. A 400 on a type that is not localized. See reading entries.
Cost
Each expansion is additional work per row. Expanding three relation fields on a
25-row page is meaningfully more expensive than the same page without them, and
relationLimit=100 on a listing is rarely what you want.
The shape that usually works: a listing read with fields= narrowing to
what the cards show and no expansion at all, then a detail read that expands
what that one page needs.
Expansion is one level deep
relationFields=author returns the author’s summary. It does not return the
author’s own relations, and there is no syntax for author.company. A second
read is the answer — or GraphQL, where you select the shape you want in one
query.
What to read next
- The media API — fetching the bytes behind an expanded media field, and why the URL needs the token.
- GraphQL — selecting a nested shape in one query, where REST expansion stops at one level.