Pagination
List endpoints return a page at a time. Ask for the next one with page, and size it with per_page.
The shape
curl "https://vm.viraldashboard.io/api/public/v1/brands/412/posts?per_page=20&page=2" \
-H "Authorization: Bearer $VD_API_KEY"
{
"data": [ … ],
"links": {
"first": "https://vm.viraldashboard.io/api/public/v1/brands/412/posts?page=1",
"last": "https://vm.viraldashboard.io/api/public/v1/brands/412/posts?page=3",
"prev": "https://vm.viraldashboard.io/api/public/v1/brands/412/posts?page=1",
"next": "https://vm.viraldashboard.io/api/public/v1/brands/412/posts?page=3"
},
"meta": {
"current_page": 2,
"per_page": 20,
"total": 43,
"last_page": 3,
"from": 21,
"to": 40,
"path": "https://vm.viraldashboard.io/api/public/v1/brands/412/posts",
"links": [ … ]
}
}
Two different links. The top-level one is the useful one:
next is null on the last page, which makes it a natural loop terminator. The one
inside meta is a list of page-number descriptors for rendering a numbered pager, and you
can ignore it unless you are drawing one.
| Parameter | Default | Notes |
|---|---|---|
per_page | 20 | Capped at 100. Asking for more gives you 100, not an error. |
page | 1 | Past the last page you get an empty data array — not a 404. |
meta is only present on genuinely paginated endpoints — posts, media,
webhook deliveries. Short collections that are never large (brands, platforms, link-in-bio pages) return a
plain data array with no meta block. Do not assume meta exists;
check for it.
Walking every page
Follow links.next until it is null — you never have to compute a page number:
async function* allPosts(brand) {
let url = `${process.env.VD_API}/brands/${brand}/posts?per_page=100`
while (url) {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.VD_API_KEY}` },
})
const { data, links } = await res.json()
yield* data
url = links?.next ?? null // null on the last page — the loop ends itself
}
}
for await (const post of allPosts(412)) {
console.log(post.id, post.status)
}
Use per_page=100 for bulk reads: the same data in a fifth of the requests, which keeps you well
inside your rate limit.
Filtering first
The cheapest page is the one you never fetch. /posts takes a status filter, so if you
only care about failures, ask only for failures rather than paging the whole history and filtering client-side.
curl "https://vm.viraldashboard.io/api/public/v1/brands/412/posts?status=failed&per_page=100" \
-H "Authorization: Bearer $VD_API_KEY"
Lists are ordered newest-first, so a busy brand can shift rows between requests: a post created while you are
on page 2 pushes an older one onto page 3, where you may see it twice or miss it. If exactness matters,
de-duplicate by id as you go.