Pagination
Long lists come in pages. Keep asking for the next page until you hit that list's stop rule.
- What it does
- Splits long lists, like an agent's videos, into pages.
- You send
limit(items per page) andpage(starting at 1). A few lists takeoffsetorcursorinstead.- You get back
- One page of items, plus fields that say whether there is another page.
- Cost
- Each page is its own request. Paid lists charge per page: trending sounds is $0.25 a page.
- When to stop
- See the table. A short page does not always mean the end.
Which parameters each list takes
| List | Send | limit default / max | Stop when |
|---|---|---|---|
| An agent's videos, slideshows, ads, and creator outliers | page | 50 / 100 | Empty page |
| An agent's hashtags, trend history, and analysis history | page | 50 / 100 | has_next_page is false |
| Sound and hook lists, an agent's sounds and hooks, tracked creators and videos | page | 20 / 100 | has_next_page is false |
| A tracked creator's posts | page | 50 / 200 (400 if over) | has_next_page is false |
Your agents (GET /v1/agents) and an agent's runs | page | 50 / 100 | Page shorter than the returned limit |
Your past lookups (GET /v1/satellite/runs) | offset | 25 / 100 | You have total items |
Videos from one lookup (.../runs/:run_id/videos) | offset | 25 / 200 (400 if over) | You have total items |
| A webhook's delivery log | cursor | 50 / 100 | next_cursor is null |
| Raw data lists (search, hashtag, comments, creator videos) | cursor | No limit: the platform sets the page size | has_more is false |
| Raw Meta ads lists (ad search, an advertiser's ads) | cursor | No limit: about 30 ads on page 1, then about 10 | has_more is false |
- Name
limit- Type
- integer
- Description
Items per page. Over the maximum, most lists use the maximum; those marked "
400if over" return a400error. Below 1 is an error on most lists.
- Name
offset- Type
- integer
- Description
How many items to skip. Page 2 with
limit=25isoffset=25.
Only the two lookup lists take offset, and they reject page. Many others show offset in responses but take page. Most other lists reject offset with a 400. Your agents, an agent's runs, and an agent's ads ignore it and return page 1 every time, so an offset loop never ends.
Lists with a total
Items sit in data (under videos, runs, and so on) beside total, limit, and offset.
On an agent's lists, stop only at an empty page. A page can come back short with more still to come. total is approximate and items can repeat across pages, so skip any id you already have.
GET /v1/agents/:id/videos?limit=50&page=2
{
"data": {
"agent_id": "2b1f9c3d-...",
"total": 283,
"limit": 50,
"offset": 50,
"videos": [
{ "id": "e30b40b1-...", "url": "https://...", "views": 11166267 }
]
}
}
Lists with a pagination block
A pagination object sits next to data. Keep going while has_next_page is true.
On hook trending and search, trending sounds (sorted by videos_7d, videos_30d or video_count) and breakout sounds, total and total_pages are null: Virlo doesn't count every match, and null means unknown, not zero or few. has_next_page is exact, so keep going while it is true. On an agent's sounds and hashtags, total can be too low (total: 21 just means "more than 20"); follow has_next_page there too. Hook trending and search end at result 1,000; a page past that returns 400.
GET /v1/sounds/trending
{
"data": [
{ "id": "4d06645e-...", "title": "PASSO BEM SOLTO (Slowed)", "platform": "youtube" }
],
"pagination": {
"page": 1,
"limit": 20,
"total": null,
"total_pages": null,
"has_next_page": true,
"has_prev_page": false
},
"sort": "videos_7d"
}
Lists with only a count
Only GET /v1/agents and GET /v1/agents/:id/runs work this way. The list sits in data.agents or data.runs, with count (items on this page) and no total. Stop at the first page shorter than the limit in the response, not the one you sent: a limit over 100 is lowered to 100.
GET /v1/agents
{
"data": {
"limit": 50,
"page": 1,
"count": 3,
"agents": [
{ "id": "2b1f9c3d-...", "name": "Skincare Routine Research", "is_recurring": true }
]
}
}
Lists with a cursor
A webhook's delivery log has no data wrapper. It returns endpoint_id, deliveries, limit, and next_cursor. Send next_cursor back as cursor until it comes back null. Replace its + with %2B (URL-encoding) or you get a 400. The last page can be empty. Full details.
GET /v1/webhooks/:id/deliveries?limit=2
{
"endpoint_id": "b64573fc-...",
"deliveries": [
{ "id": "718bdf28-...", "event_type": "content_research_agent.run.completed", "status": "pending" }
],
"limit": 2,
"next_cursor": "2026-09-24T17:32:27.503726+00:00"
}
Results that aren't paged
These return everything in one response. Some take limit, but none has a second page.
- An agent's benchmarks, audience affinity, activity, events, proposals, and similar creators. Activity, events, and proposals show a
countbut ignorepage, so don't loop on them. - Ranked lists of up to 100:
GET /v1/hashtags,GET /v1/videos/digest(both also per platform), andGET /v1/topic-trends. On hashtags,page=2repeats page 1 and is still charged. GET /v1/topic-trends/emerging:limitdefault 20, max 50 (400if over).- Snapshots of a tracked creator or video (default 30) and a sound's usage history (default 90):
limitmax 365 (400if over). GET /v1/webhooks: a plain array with nodatawrapper.

