Skip to content

Pagination

All list endpoints share the same pagination, sorting, and search contract. Single-resource endpoints (e.g. GET /api/scans/{scanId}) do not paginate.

Query parameters

ParameterTypeDefaultDescription
pageinteger ≥ 11Page number (1-indexed)
limitinteger 1–20050Page size; values above 200 are rejected
sortstringendpoint-specificcolumn:direction, e.g. riskScore:desc
searchstringnoneFree-text filter; supported fields vary per endpoint

Endpoint-specific filters (such as severity, status, provider) layer on top of these. Each endpoint page documents its own filter set and the columns its sort parameter accepts.

Sortable columns

The set of columns you may pass to sort is fixed per endpoint and silently ignored if invalid:

EndpointSortable columns
GET /api/scansscannedAt, status, compliance, riskScore
GET /api/imagesname, createdAt, riskScore
GET /api/vulnerabilitiesseverity, cveId, detectedAt, cvss, triageStatus
GET /api/reportscreatedAt, type, status

Direction must be asc or desc. Unknown columns silently fall back to the endpoint's default ordering.

Response envelope

A paginated response wraps results in data, meta, and links:

{
  "data": [ /* array of items */ ],
  "meta": {
    "page": 2,
    "pageSize": 50,
    "total": 312,
    "totalPages": 7,
    "hasNext": true,
    "hasPrev": true
  },
  "links": {
    "self": "/api/scans?page=2&limit=50",
    "next": "/api/scans?page=3&limit=50",
    "prev": "/api/scans?page=1&limit=50"
  }
}

links always echoes the request path and serialized query string, making it safe to follow links.next directly without rebuilding URLs.

Examples

Fetch the second page of 100 scans, newest first:

curl "https://harborguard.co/api/scans?page=2&limit=100&sort=scannedAt:desc" \
  -H "Authorization: Bearer hg_ak_..."

Iterate every page (pseudo-code):

let url = "/api/scans?limit=200&sort=scannedAt:desc"
while (url) {
  const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } })
  const body = await res.json()
  for (const scan of body.data) yield scan
  url = body.links?.next ?? null
}

Errors

Invalid page, limit, or sort values return 400 BAD_REQUEST:

{ "error": { "code": "BAD_REQUEST", "message": "Invalid pagination parameters" } }

On this page