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.

ParameterDefaultNotes
per_page20Capped at 100. Asking for more gives you 100, not an error.
page1Past 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.