Pagination
Navigate through large result sets with cursor-based and page-based pagination
The ViewRoyal.ai API uses two pagination systems depending on the endpoint family:
- v1 API endpoints (meetings, people, matters, motions, bylaws) use cursor-based pagination
- OCD API endpoints (people, organizations, events, bills, votes, jurisdictions) use page-based pagination
- Search endpoint is a hybrid -- it accepts page-based parameters but returns the v1 response envelope
Cursor-Based Pagination (v1 API)
All v1 list endpoints use cursor-based pagination with two query parameters:
| Parameter | Default | Max | Description |
|---|---|---|---|
per_page | 20 | 100 | Number of items per page |
cursor | (omit for first page) | -- | Opaque cursor from previous response |
Response Shape
{
"data": [
{ "id": 42, "slug": "2024-01-15-regular-council", "title": "Regular Council Meeting", "date": "2024-01-15" },
{ "id": 41, "slug": "2024-01-08-committee-of-the-whole", "title": "Committee of the Whole Meeting", "date": "2024-01-08" }
],
"pagination": {
"has_more": true,
"next_cursor": "eyJ2IjoiMjAyNC0wMS0wOCIsImlkIjo0MX0=",
"per_page": 2
},
"meta": {
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}How to Paginate
- Make your first request without a
cursorparameter - Check
pagination.has_morein the response - If
true, passpagination.next_cursoras thecursorquery parameter in your next request - If
false, you have reached the last page
Example: Fetching Two Pages
# Page 1: no cursor
curl -H "X-API-Key: YOUR_API_KEY" \
"https://viewroyal.ai/api/v1/view-royal/meetings?per_page=5"
# Page 2: use next_cursor from page 1 response
curl -H "X-API-Key: YOUR_API_KEY" \
"https://viewroyal.ai/api/v1/view-royal/meetings?per_page=5&cursor=eyJ2IjoiMjAyNC0wMS0wOCIsImlkIjo0MX0="const API_KEY = 'YOUR_API_KEY';
const headers = { 'X-API-Key': API_KEY };
// Page 1
const page1 = await fetch(
'https://viewroyal.ai/api/v1/view-royal/meetings?per_page=5',
{ headers }
);
const json1 = await page1.json();
console.log(`Page 1: ${json1.data.length} meetings`);
// Page 2 (if more results exist)
if (json1.pagination.has_more) {
const page2 = await fetch(
`https://viewroyal.ai/api/v1/view-royal/meetings?per_page=5&cursor=${json1.pagination.next_cursor}`,
{ headers }
);
const json2 = await page2.json();
console.log(`Page 2: ${json2.data.length} meetings`);
}import requests
API_KEY = 'YOUR_API_KEY'
headers = {'X-API-Key': API_KEY}
# Page 1
resp1 = requests.get(
'https://viewroyal.ai/api/v1/view-royal/meetings',
headers=headers,
params={'per_page': 5}
)
json1 = resp1.json()
print(f"Page 1: {len(json1['data'])} meetings")
# Page 2 (if more results exist)
if json1['pagination']['has_more']:
resp2 = requests.get(
'https://viewroyal.ai/api/v1/view-royal/meetings',
headers=headers,
params={'per_page': 5, 'cursor': json1['pagination']['next_cursor']}
)
json2 = resp2.json()
print(f"Page 2: {len(json2['data'])} meetings")Cursors are opaque tokens. Never attempt to decode, construct, or modify them. Always use the exact next_cursor value from the previous response.
Full Iteration: Fetching All Pages
To retrieve every item from a list endpoint, loop until has_more is false:
async function fetchAll(endpoint, apiKey) {
const headers = { 'X-API-Key': apiKey };
let cursor = null;
let allItems = [];
do {
const url = new URL(`https://viewroyal.ai${endpoint}`);
url.searchParams.set('per_page', '20');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers });
const json = await res.json();
allItems.push(...json.data);
cursor = json.pagination.next_cursor;
} while (cursor);
return allItems;
}
// Fetch all meetings
const meetings = await fetchAll('/api/v1/view-royal/meetings', 'YOUR_API_KEY');
console.log(`Total meetings: ${meetings.length}`);import requests
def fetch_all(endpoint, api_key):
headers = {'X-API-Key': api_key}
cursor = None
all_items = []
while True:
params = {'per_page': 20}
if cursor:
params['cursor'] = cursor
resp = requests.get(
f'https://viewroyal.ai{endpoint}',
headers=headers,
params=params
)
json_data = resp.json()
all_items.extend(json_data['data'])
cursor = json_data['pagination']['next_cursor']
if not cursor:
break
return all_items
# Fetch all meetings
meetings = fetch_all('/api/v1/view-royal/meetings', 'YOUR_API_KEY')
print(f"Total meetings: {len(meetings)}")Page-Based Pagination (OCD API)
All OCD list endpoints use traditional page-based pagination with two query parameters:
| Parameter | Default | Max | Description |
|---|---|---|---|
page | 1 | -- | Page number (1-based) |
per_page | 20 | 100 | Number of items per page |
Response Shape
{
"results": [
{ "id": "ocd-person/abc123", "name": "Jane Councillor", "current_role": "Councillor" },
{ "id": "ocd-person/def456", "name": "John Mayor", "current_role": "Mayor" }
],
"pagination": {
"page": 1,
"per_page": 20,
"max_page": 3,
"total_items": 47
}
}How to Paginate
- Start with
page=1(or omit it -- defaults to 1) - Check
pagination.max_pageto know the total number of pages - Increment
pageuntilpage >= max_page - Use
total_itemsto show progress to users (e.g., "Showing 1-20 of 47")
Example
# Page 1
curl -H "X-API-Key: YOUR_API_KEY" \
"https://viewroyal.ai/api/ocd/view-royal/people?page=1&per_page=10"
# Page 2
curl -H "X-API-Key: YOUR_API_KEY" \
"https://viewroyal.ai/api/ocd/view-royal/people?page=2&per_page=10"const API_KEY = 'YOUR_API_KEY';
const headers = { 'X-API-Key': API_KEY };
const res = await fetch(
'https://viewroyal.ai/api/ocd/view-royal/people?page=1&per_page=10',
{ headers }
);
const json = await res.json();
console.log(`Page ${json.pagination.page} of ${json.pagination.max_page}`);
console.log(`Showing ${json.results.length} of ${json.pagination.total_items} total`);import requests
headers = {'X-API-Key': 'YOUR_API_KEY'}
resp = requests.get(
'https://viewroyal.ai/api/ocd/view-royal/people',
headers=headers,
params={'page': 1, 'per_page': 10}
)
json_data = resp.json()
page = json_data['pagination']['page']
max_page = json_data['pagination']['max_page']
total = json_data['pagination']['total_items']
print(f"Page {page} of {max_page} ({total} total items)")OCD detail endpoints (e.g., GET /api/ocd/view-royal/people/{id}) are not paginated. They return the full entity directly at the top level with no wrapper object.
Search Endpoint (Hybrid)
The search endpoint (GET /api/v1/view-royal/search) uses a hybrid approach:
- Parameters: Page-based (
pageandper_page), same as OCD endpoints - Response envelope: v1-style (
data,pagination,meta), same as v1 endpoints - Cursor: The
next_cursorfield is alwaysnull-- use thepageparameter to navigate
curl -H "X-API-Key: YOUR_API_KEY" \
"https://viewroyal.ai/api/v1/view-royal/search?q=budget&page=1&per_page=10"{
"data": [
{ "type": "motion", "score": 0.1, "title": "Budget Approval Motion", "snippet": "..." }
],
"pagination": {
"has_more": true,
"next_cursor": null,
"per_page": 10,
"page": 1
},
"meta": {
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}To paginate search results, increment the page parameter and check has_more. See the Search endpoint reference for full query parameters and response fields.
Quick Reference
| Feature | v1 Endpoints | OCD Endpoints | Search |
|---|---|---|---|
| Style | Cursor-based | Page-based | Page-based |
| Parameters | cursor, per_page | page, per_page | page, per_page |
| Default per_page | 20 | 20 | 20 |
| Max per_page | 100 | 100 | 100 |
| Envelope | data + pagination + meta | results + pagination | data + pagination + meta |
| Next page signal | has_more + next_cursor | page < max_page | has_more |