Skip to main content

Pagination & Filtering

Efficiently retrieve large datasets and filter results using pagination and filtering.


Pagination

Why Pagination?

Performance: Returning all results in one response is slow and wasteful.

Example:

  • 10,000 vaults × 5 KB each = 50 MB response (slow, expensive)
  • 100 vaults × 5 KB each = 500 KB response (fast, efficient)

All list endpoints support pagination:

  • GET /v1/vaults
  • GET /v1/documents
  • GET /v1/recipients
  • GET /v1/policies
  • etc.

Page-Based Pagination

How It Works

Request Specific Page:

GET /v1/vaults?page=1&limit=25

Response:

{
"vaults": [
{ "id": "vault_1", "name": "Vault 1" },
{ "id": "vault_2", "name": "Vault 2" },
...
],
"pagination": {
"page": 1,
"limit": 25,
"total": 250,
"total_pages": 10,
"has_more": true
}
}

Query Parameters

page (integer, default: 1):

  • Page number (1-indexed)
  • Minimum: 1
  • Maximum: No hard limit, but performance degrades for very high page numbers

limit (integer, default: 25):

  • Number of results per page
  • Minimum: 1
  • Maximum: 100
  • Recommended: 25-50 for best performance

Example:

GET /v1/vaults?page=2&limit=50

Returns vaults 51-100 (page 2, 50 per page).

Response Fields

pagination object:

{
"page": 2, // Current page number
"limit": 50, // Results per page
"total": 250, // Total number of results across all pages
"total_pages": 5, // Total number of pages
"has_more": true // Whether there are more pages after this one
}

Iterating Through Pages

Get All Results:

async function getAllVaults() {
const allVaults = [];
let page = 1;
let hasMore = true;

while (hasMore) {
const response = await fetch(
`https://api.torvussecurity.com/v1/vaults?page=${page}&limit=100`
);
const data = await response.json();

allVaults.push(...data.vaults);
hasMore = data.pagination.has_more;
page++;
}

return allVaults;
}

With Error Handling:

async function getAllVaults() {
const allVaults = [];
let page = 1;
let hasMore = true;

while (hasMore) {
try {
const response = await fetch(
`https://api.torvussecurity.com/v1/vaults?page=${page}&limit=100`,
{
headers: {
'Authorization': `Bearer ${apiKey}`
}
}
);

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

const data = await response.json();

allVaults.push(...data.vaults);
hasMore = data.pagination.has_more;
page++;

// Optional: Delay between pages to avoid rate limits
if (hasMore) {
await new Promise(resolve => setTimeout(resolve, 100));
}

} catch (error) {
console.error(`Error fetching page ${page}:`, error);
throw error;
}
}

return allVaults;
}

Advantages

Simple: Easy to understand and implement ✅ Random access: Jump to any page directly (page=5) ✅ Total count: Know total number of results upfront

Disadvantages

Cursor drift: If items are added/deleted during pagination, you might see duplicates or miss items ❌ Performance: Slow for very high page numbers (database must skip previous results)

When to use: Small to medium datasets (under 10,000 results), or when you need random page access.


Cursor-Based Pagination

How It Works

Request First Page:

GET /v1/vaults?limit=25

Response:

{
"vaults": [
{ "id": "vault_1", "name": "Vault 1" },
{ "id": "vault_2", "name": "Vault 2" },
...
],
"pagination": {
"limit": 25,
"next_cursor": "eyJpZCI6InZhdWx0XzI1IiwiY3JlYXRlZCI6MTcwMDAwMDAwMH0",
"has_more": true
}
}

Request Next Page (using next_cursor):

GET /v1/vaults?limit=25&cursor=eyJpZCI6InZhdWx0XzI1IiwiY3JlYXRlZCI6MTcwMDAwMDAwMH0

Query Parameters

cursor (string, optional):

  • Opaque cursor string from previous response
  • Don't construct manually (use value from next_cursor)
  • Omit for first page

limit (integer, default: 25):

  • Number of results per page
  • Minimum: 1
  • Maximum: 100

Response Fields

pagination object:

{
"limit": 25, // Results per page
"next_cursor": "eyJ...", // Cursor for next page (null if no more pages)
"has_more": true // Whether there are more results
}

Note: total is NOT included (not possible with cursor pagination).

Iterating Through Pages

Get All Results:

async function getAllVaults() {
const allVaults = [];
let cursor = null;

while (true) {
const url = cursor
? `https://api.torvussecurity.com/v1/vaults?cursor=${cursor}&limit=100`
: `https://api.torvussecurity.com/v1/vaults?limit=100`;

const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
const data = await response.json();

allVaults.push(...data.vaults);

if (!data.pagination.has_more) {
break;
}

cursor = data.pagination.next_cursor;
}

return allVaults;
}

Advantages

No cursor drift: Consistent results even if data changes during pagination ✅ Performance: Fast for large datasets (no skipping required) ✅ Real-time data: Best for live data that changes frequently

Disadvantages

No random access: Can't jump to specific page (must iterate from start) ❌ No total count: Don't know total number of results upfront

When to use: Large datasets (10,000+ results), real-time data, or when consistency is critical.


Choosing Pagination Method

FeaturePage-BasedCursor-Based
Simplicity✅ Simple⚠️ Slightly complex
Random access✅ Yes (page=5)❌ No
Total count✅ Yes❌ No
Performance (large datasets)⚠️ Slow✅ Fast
Consistency⚠️ Cursor drift possible✅ Consistent
Use caseSmall-medium datasets, UI paginationLarge datasets, bulk exports

Default: If unsure, use page-based (simpler, good for most use cases).

Switch to cursor-based if:

  • Dataset is very large (10,000+ results)
  • Data changes frequently during pagination
  • You're doing bulk exports or background processing

Filtering

Query Parameters

Filter by Field:

GET /v1/vaults?status=active

Returns only vaults with status=active.

Multiple Filters (AND logic):

GET /v1/vaults?status=active&owner_id=user_123

Returns vaults where status=active AND owner_id=user_123.

Common Filters

By Status:

GET /v1/vaults?status=active
GET /v1/vaults?status=archived

By Owner:

GET /v1/vaults?owner_id=user_123

By Date Range:

GET /v1/vaults?created_after=2025-01-01&created_before=2025-12-31

By Type:

GET /v1/documents?type=pdf
GET /v1/documents?type=image

Date Filters

Available Date Filters:

  • created_after - Results created after date (inclusive)
  • created_before - Results created before date (inclusive)
  • updated_after - Results updated after date
  • updated_before - Results updated before date

Date Format: ISO 8601 (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ)

Examples:

# Vaults created in 2025
GET /v1/vaults?created_after=2025-01-01&created_before=2025-12-31

# Documents updated in last 7 days
GET /v1/documents?updated_after=2025-10-01

# Recipients created this month
GET /v1/recipients?created_after=2025-10-01&created_before=2025-10-31

Field Selection

Select Specific Fields (reduces response size):

GET /v1/vaults?fields=id,name,created_at

Response:

{
"vaults": [
{
"id": "vault_1",
"name": "Vault 1",
"created_at": "2025-01-15T10:30:00Z"
// Other fields omitted
}
]
}

All Fields (default):

GET /v1/vaults

Returns all fields for each vault.

Benefits:

  • ✅ Smaller response size (faster)
  • ✅ Reduced bandwidth usage
  • ✅ Only retrieve data you actually need

Sorting

Query Parameters

Sort by Field:

GET /v1/vaults?sort=created_at

Default sort order: ascending (oldest first).

Sort Descending:

GET /v1/vaults?sort=created_at:desc

Sort Ascending (explicit):

GET /v1/vaults?sort=created_at:asc

Multiple Sort Fields

Sort by Multiple Fields (comma-separated):

GET /v1/vaults?sort=status:asc,created_at:desc

Sorts by status ascending, then created_at descending (for vaults with same status).

Sortable Fields

Common Sortable Fields:

  • id - Resource ID (alphabetical)
  • name - Resource name (alphabetical)
  • created_at - Creation date (chronological)
  • updated_at - Last update date (chronological)
  • status - Status (alphabetical)

Example Use Cases:

Newest vaults first:

GET /v1/vaults?sort=created_at:desc

Alphabetical by name:

GET /v1/vaults?sort=name:asc

Most recently updated:

GET /v1/vaults?sort=updated_at:desc

Search Across All Fields:

GET /v1/vaults?q=important

Searches vault name, description, and other text fields for "important".

Search Specific Fields:

GET /v1/vaults?q=name:important

Searches only name field.

Search Examples

Search Vaults:

# Find vaults containing "legal"
GET /v1/vaults?q=legal

# Find vaults with "2025" in name
GET /v1/vaults?q=name:2025

# Find vaults with "confidential" in description
GET /v1/vaults?q=description:confidential

Search Documents:

# Find documents containing "contract"
GET /v1/documents?q=contract

# Find PDF documents
GET /v1/documents?q=type:pdf

# Find documents with "invoice" in filename
GET /v1/documents?q=filename:invoice

Combine Search with Filters:

GET /v1/vaults?q=legal&status=active&sort=created_at:desc

Finds active vaults containing "legal", sorted by newest first.

Search with Pagination:

GET /v1/vaults?q=important&page=1&limit=50

Search results are paginated like regular list results.


Combining Features

Complete Example

Use Case: Find all active vaults created in 2025, containing "legal" in name or description, sorted by most recent, with only ID and name returned.

Request:

GET /v1/vaults?status=active&created_after=2025-01-01&created_before=2025-12-31&q=legal&sort=created_at:desc&fields=id,name&limit=50

Breakdown:

  • status=active - Filter: only active vaults
  • created_after=2025-01-01 - Filter: created in 2025 or later
  • created_before=2025-12-31 - Filter: created in 2025 or earlier
  • q=legal - Search: contains "legal"
  • sort=created_at:desc - Sort: newest first
  • fields=id,name - Select: only ID and name
  • limit=50 - Pagination: 50 results per page

Response:

{
"vaults": [
{
"id": "vault_123",
"name": "Legal Documents 2025"
},
{
"id": "vault_456",
"name": "Legal Contracts"
},
...
],
"pagination": {
"page": 1,
"limit": 50,
"total": 127,
"total_pages": 3,
"has_more": true
}
}

Best Practices

Always Use Pagination

Bad (retrieves all results at once):

const response = await fetch('https://api.torvussecurity.com/v1/vaults');
const { vaults } = await response.json();

If you have 10,000 vaults, this request will:

  • Take 30+ seconds
  • Return 50+ MB response
  • Likely hit timeout or memory limits

Good (use pagination):

const vaults = [];
let page = 1;
let hasMore = true;

while (hasMore) {
const response = await fetch(
`https://api.torvussecurity.com/v1/vaults?page=${page}&limit=100`
);
const data = await response.json();

vaults.push(...data.vaults);
hasMore = data.pagination.has_more;
page++;
}

Choose Appropriate Page Size

Too Small (limit=1):

  • Too many API calls (slow)
  • Hits rate limits faster

Too Large (limit=1000):

  • Slow responses
  • High memory usage
  • May hit timeout

Recommended: limit=25-100

  • Good balance of speed and efficiency
  • Default is limit=25

Use Field Selection

Only Request Fields You Need:

Bad (returns all fields):

GET /v1/vaults

Good (returns only needed fields):

GET /v1/vaults?fields=id,name

Benefits:

  • 10-50% smaller responses
  • Faster JSON parsing
  • Less bandwidth usage

Cache When Appropriate

Cache Paginated Results (if data doesn't change frequently):

const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function getCachedPage(page) {
const cacheKey = `vaults:page:${page}`;
const cached = cache.get(cacheKey);

if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}

const response = await fetch(
`https://api.torvussecurity.com/v1/vaults?page=${page}&limit=50`
);
const data = await response.json();

cache.set(cacheKey, { data, timestamp: Date.now() });
return data;
}

Invalidate Cache After Mutations:

async function createVault(vaultData) {
const vault = await fetch(/* create vault */);

// Invalidate cache (new vault added)
cache.clear();

return vault;
}

Handle Edge Cases

Empty Results:

const response = await fetch('https://api.torvussecurity.com/v1/vaults');
const { vaults, pagination } = await response.json();

if (vaults.length === 0) {
console.log('No vaults found');
}

Single Page of Results:

const { vaults, pagination } = await response.json();

if (!pagination.has_more) {
console.log('All results on one page');
}

Very Large Result Sets:

// Don't try to fetch ALL results if there are millions
// Instead, process in batches

async function processBatches(batchSize = 100) {
let page = 1;
let hasMore = true;

while (hasMore) {
const { vaults, pagination } = await fetchPage(page, batchSize);

// Process batch
await processBatch(vaults);

hasMore = pagination.has_more;
page++;

// Optional: limit total pages processed
if (page > 100) {
console.warn('Reached max pages (10,000 results)');
break;
}
}
}

Error Handling

Invalid Parameters

Invalid Page Number:

GET /v1/vaults?page=0

Response:

{
"error": "Validation failed",
"errors": [
{
"field": "page",
"message": "Page must be greater than 0"
}
]
}

Invalid Limit:

GET /v1/vaults?limit=1000

Response:

{
"error": "Validation failed",
"errors": [
{
"field": "limit",
"message": "Limit must not exceed 100"
}
]
}

Invalid Cursor

Malformed or Expired Cursor:

GET /v1/vaults?cursor=invalid_cursor

Response:

{
"error": "Invalid cursor",
"message": "Cursor is malformed or expired. Please start from the first page."
}

Solution: Start over from first page (omit cursor parameter).


Code Examples

Python

import requests

def get_all_vaults(api_key):
vaults = []
page = 1
has_more = True

while has_more:
response = requests.get(
'https://api.torvussecurity.com/v1/vaults',
headers={'Authorization': f'Bearer {api_key}'},
params={'page': page, 'limit': 100}
)
response.raise_for_status()
data = response.json()

vaults.extend(data['vaults'])
has_more = data['pagination']['has_more']
page += 1

return vaults

# Usage
vaults = get_all_vaults(api_key)
print(f'Total vaults: {len(vaults)}')

JavaScript/Node.js

const axios = require('axios');

async function getAllVaults(apiKey) {
const vaults = [];
let page = 1;
let hasMore = true;

while (hasMore) {
const { data } = await axios.get(
'https://api.torvussecurity.com/v1/vaults',
{
headers: { Authorization: `Bearer ${apiKey}` },
params: { page, limit: 100 }
}
);

vaults.push(...data.vaults);
hasMore = data.pagination.has_more;
page++;
}

return vaults;
}

// Usage
const vaults = await getAllVaults(apiKey);
console.log(`Total vaults: ${vaults.length}`);

Cursor-Based (JavaScript)

async function getAllVaultsCursor(apiKey) {
const vaults = [];
let cursor = null;

while (true) {
const params = { limit: 100 };
if (cursor) params.cursor = cursor;

const { data } = await axios.get(
'https://api.torvussecurity.com/v1/vaults',
{
headers: { Authorization: `Bearer ${apiKey}` },
params
}
);

vaults.push(...data.vaults);

if (!data.pagination.has_more) break;
cursor = data.pagination.next_cursor;
}

return vaults;
}

FAQ

Can I get all results in one request?

No. All list endpoints are paginated. Maximum limit is 100 results per page.

Why? Performance and efficiency. Returning thousands of results at once is slow and wasteful.

Solution: Use pagination to fetch all results across multiple requests.

What's the maximum number of results I can retrieve?

No limit. You can paginate through millions of results if needed (though it will take time).

Rate limits apply: Fetching 1,000,000 results at 100/page = 10,000 API calls. Plan accordingly.

How do I know the total count without fetching all pages?

Page-based pagination: The total field in pagination object tells you total count.

Cursor-based pagination: Total count is NOT available. You must iterate through all pages.

Can I jump to a specific result (e.g., result #5000)?

Page-based: Yes. Calculate page: page = Math.ceil(5000 / limit).

Cursor-based: No. Must iterate from start.

Do filters affect pagination?

Yes. Pagination applies AFTER filters.

Example:

GET /v1/vaults?status=active&page=1&limit=50

Returns first 50 active vaults (not first 50 of all vaults).

What happens if data changes during pagination?

Page-based: Cursor drift possible (may see duplicates or miss results).

Cursor-based: Consistent results (no duplicates or missed results).

Best practice: Use cursor-based for data that changes frequently.



Last Updated: October 8, 2025