Skip to content

Pagination

List endpoints in the Mantis API support pagination to handle large result sets efficiently. This guide covers how to request paginated data and navigate through pages.

All list endpoints accept these optional query parameters:

ParameterTypeDefaultRangeDescription
pageinteger1>= 1Page number (1-indexed)
limitinteger501-100Items per page
Terminal window
GET /api/v1/deployments?page=2&limit=25
Authorization: Bearer <token>

Query breakdown:

  • page=2 - Get the second page of results
  • limit=25 - Return 25 items per page

Paginated endpoints return results in a consistent envelope structure:

{
"data": [
{ "id": "...", ... },
{ "id": "...", ... },
// ... up to 'limit' items
],
"meta": {
"total": 247,
"page": 2,
"limit": 25,
"total_pages": 10,
"has_next": true,
"has_prev": true
}
}

data (array)

  • The actual items for the current page
  • Array may be empty if page exceeds available data
  • Maximum length is the requested limit

meta (object)

  • total - Total number of items across all pages
  • page - Current page number (matches request)
  • limit - Items per page (matches request)
  • total_pages - Total number of pages
  • has_next - true if there are more pages after this one
  • has_prev - true if there are pages before this one
total_pages = ceil(total / limit)

Example:

  • Total items: 247
  • Limit: 25
  • Total pages: ceil(247 / 25) = 10 pages
  • Pages are 1-indexed (first page is page=1, not page=0)
  • Requesting page=0 is treated as page=1
  • Requesting a page beyond total_pages returns empty data array

The API internally converts page numbers to database offsets:

offset = (page - 1) * limit

Example for page 3 with limit 25:

offset = (3 - 1) * 25 = 50

This skips the first 50 items and returns items 51-75.

Terminal window
GET /api/v1/deployments?page=1&limit=50
# Or omit both parameters (defaults to page=1, limit=50)
GET /api/v1/deployments

Use the has_next field to check if there’s a next page:

const response = await fetch('/api/v1/deployments?page=1&limit=50');
const json = await response.json();
if (json.meta.has_next) {
const nextPage = json.meta.page + 1;
const nextResponse = await fetch(
`/api/v1/deployments?page=${nextPage}&limit=50`
);
}

Use the has_prev field to check if there’s a previous page:

if (json.meta.has_prev) {
const prevPage = json.meta.page - 1;
const prevResponse = await fetch(
`/api/v1/deployments?page=${prevPage}&limit=50`
);
}

Jump to the last page using total_pages:

Terminal window
GET /api/v1/deployments?page=10&limit=50
# Where page=10 is from meta.total_pages

When pagination parameters are omitted, the API uses these defaults:

Terminal window
# These are equivalent:
GET /api/v1/deployments
GET /api/v1/deployments?page=1&limit=50

Default values:

  • page: 1 (first page)
  • limit: 50 items per page

The maximum limit is 100 items per page. Requests exceeding this are clamped:

Terminal window
# Request limit=500
GET /api/v1/deployments?limit=500
# API automatically adjusts to limit=100
{
"meta": {
"limit": 100, # Clamped to maximum
...
}
}

The minimum limit is 1 item per page. Values less than 1 are adjusted:

Terminal window
# Request limit=0
GET /api/v1/deployments?limit=0
# API adjusts to limit=1
{
"meta": {
"limit": 1, # Adjusted to minimum
...
}
}

These endpoints support pagination:

EndpointResource
GET /api/v1/deploymentsDeployments
GET /api/v1/actionsActions
GET /api/v1/sequencesSequences
GET /api/v1/solutionsSolutions
GET /api/v1/targetsTargets
GET /api/v1/environmentsEnvironments
GET /api/v1/tenantsTenants
GET /api/v1/admin/usersUsers
GET /api/v1/admin/rolesRoles
GET /api/v1/tagsTags
GET /api/v1/deployment-freezesDeployment freezes
GET /api/v1/audit/logsAudit logs

To retrieve all items across multiple pages:

async function fetchAllDeployments(baseUrl, token) {
const allDeployments = [];
let page = 1;
let hasNext = true;
while (hasNext) {
const response = await fetch(
`${baseUrl}/api/v1/deployments?page=${page}&limit=100`,
{
headers: { Authorization: `Bearer ${token}` },
}
);
const json = await response.json();
allDeployments.push(...json.data);
hasNext = json.meta.has_next;
page++;
}
return allDeployments;
}
import requests
from typing import List, Dict, Any
def fetch_all_deployments(base_url: str, token: str) -> List[Dict[str, Any]]:
"""Fetch all deployments across all pages."""
all_deployments = []
page = 1
has_next = True
while has_next:
response = requests.get(
f"{base_url}/api/v1/deployments",
params={"page": page, "limit": 100},
headers={"Authorization": f"Bearer {token}"}
)
response.raise_for_status()
data = response.json()
all_deployments.extend(data["data"])
has_next = data["meta"]["has_next"]
page += 1
return all_deployments
import React from 'react';
interface PaginationProps {
meta: {
page: number;
total_pages: number;
has_next: boolean;
has_prev: boolean;
};
onPageChange: (page: number) => void;
}
export const Pagination: React.FC<PaginationProps> = ({ meta, onPageChange }) => {
return (
<div className="pagination">
<button
disabled={!meta.has_prev}
onClick={() => onPageChange(meta.page - 1)}
>
Previous
</button>
<span>
Page {meta.page} of {meta.total_pages}
</span>
<button
disabled={!meta.has_next}
onClick={() => onPageChange(meta.page + 1)}
>
Next
</button>
</div>
);
};

For very large result sets (10,000+ items):

  1. Use filters - Most list endpoints support filtering to reduce total items
  2. Increase page size - Use limit=100 to minimize requests
  3. Cache results - Cache pages client-side to avoid repeated requests
  4. Consider streaming - For real-time data, use SSE endpoints instead

Fetching very high page numbers (e.g., page 1000) can be slow because the database must skip many rows. Performance degrades with offset size.

Better approaches:

  • Use filters to reduce result set
  • Implement cursor-based pagination (if your use case requires it, contact support)
  • Consider if you need all pages (users rarely browse beyond page 10)

Many endpoints support filtering parameters in addition to pagination:

Terminal window
# Get page 2 of deployments, text-filtered (search matches name + status)
GET /api/v1/deployments?page=2&limit=25&search=running
# Get page 1 of targets, text-filtered (search matches name + hostname)
GET /api/v1/targets?page=1&limit=50&search=web

Note: Filters reduce the total count in meta. The pagination applies to the filtered subset, not all items.

Requesting a page beyond the available data returns an empty array:

Terminal window
GET /api/v1/deployments?page=999&limit=50

Response:

{
"data": [],
"meta": {
"total": 247,
"page": 999,
"limit": 50,
"total_pages": 5,
"has_next": false,
"has_prev": true
}
}

Don’t blindly increment page numbers. Always check the has_next field:

// ❌ Bad - may fetch empty pages
for (let page = 1; page <= 100; page++) {
await fetchPage(page);
}
// ✅ Good - stops at last page
let page = 1;
do {
const response = await fetchPage(page);
// Process response.data
if (!response.meta.has_next) break;
page++;
} while (true);
  • UI pagination: limit=25-50 (fast page loads)
  • Batch processing: limit=100 (fewer requests)
  • Initial load: limit=10 (quick first paint)

Always handle the case where data is empty:

const response = await fetch('/api/v1/deployments?page=5');
const json = await response.json();
if (json.data.length === 0) {
console.log('No deployments on this page');
}

When navigating pages, maintain any active filters:

function buildUrl(page, filters) {
const params = new URLSearchParams({
page: page.toString(),
limit: '50',
...filters,
});
return `/api/v1/deployments?${params}`;
}

Symptom: Always getting the first page regardless of page parameter.

Cause: Query parameter not properly encoded or parsed.

Solution: Verify URL encoding:

const url = new URL('https://mantis.example.com/api/v1/deployments');
url.searchParams.set('page', '2');
url.searchParams.set('limit', '50');
fetch(url);

Symptom: total changes between requests.

Cause: Data is being added/deleted between pagination requests.

Solution: This is expected behavior. For consistent pagination snapshots, consider implementing cursor-based pagination for your use case.

Symptom: Response doesn’t include meta field.

Cause: Endpoint doesn’t support pagination, or you’re hitting a detail endpoint (not a list endpoint).

Solution: Verify you’re calling a list endpoint (e.g., /deployments not /deployments/{id}).