TentoCMS
Api

Query Parameters

Field selection, media serialization, pagination, sorting and filtering, supported on every list endpoint.

Field Selection

The fields parameter allows you to request only specific content fields, reducing payload size and improving performance.

Syntax:

?fields=field1,field2,field3      # include only these fields
?fields=-field1,-field2           # include everything except these fields

A - prefix excludes that field. For example, ?fields=-body returns every field except body. Include and exclude entries are parsed from the same comma-separated list; if any plain (include) field is present, the include list takes precedence and exclusions are ignored — so combine one style or the other, not both.

Behavior:

  • Always includes: id, name, slug, type, seo, publishedAt, updatedAt
  • Filters only the fields object
  • Unknown field names are silently ignored
  • Empty fields parameter returns all fields
  • - prefix excludes a field (e.g. ?fields=-body)

Example: everything except the body field

GET /api/v1/pages/hello-world?fields=-body

Example: Full fields (default)

GET /api/v1/pages/hello-world

Response fields:

{
  "fields": {
    "title": "Hello World",
    "excerpt": "My first post",
    "body": "Lorem ipsum dolor sit amet...",
    "author": "John Doe",
    "tags": ["tech", "blog"],
    "publishDate": "2025-12-15"
  }
}

Example: Title and excerpt only

GET /api/v1/pages/hello-world?fields=title,excerpt

Response fields:

{
  "fields": {
    "title": "Hello World",
    "excerpt": "My first post"
  }
}

Performance Impact:

ScenarioPayload SizePerformance Gain
Full blog post (all fields)~15 KBBaseline
Title + excerpt only~2 KB86% smaller
Title only~1 KB93% smaller

Use Cases:

  • Card views: Request title, excerpt, image only
  • Navigation menus: Request title, slug only
  • Search results: Request title, excerpt, publishDate only
  • Full page: Request all fields (omit parameter)

Media Serialization

The media parameter controls how media references (images, files) are serialized in the response.

Syntax:

?media=object   # default — full media object
?media=url      # just the media URL as a string
ModeA media field looks likeWhen to use
object (default){ "_url": "...", "_mimeType": "image/webp", "_width": 112, "_height": 128, "_altText": "..." }You need dimensions (responsive srcset, layout-shift avoidance) or alt text (accessibility).
url"https://tento-api.intelligentlending.co.uk/api/v1/media/{project}/{file}"You only need <img src>. Lighter payload.

Behavior:

  • Applies uniformly to media anywhere in the response — pages, collections, and blog featuredImage (across single-item and list endpoints).
  • object mode omits _width/_height/_altText when the media has no such value (they are absent, not null).
  • Any value other than url is treated as the default object.
  • Each mode is cached separately at the edge, so both stay fast.

Example:

GET /api/v1/pages/hello-world?media=url
{ "fields": { "hero": { "icon": "https://tento-api.intelligentlending.co.uk/api/v1/media/proj-1/abc.webp" } } }

Pagination

The page and limit parameters enable pagination through large result sets. Pagination is page-number based (1-indexed) — there is no offset parameter.

Syntax:

?page={page-number}&limit={page-size}

Parameters:

ParameterTypeDefaultMinMaxDescription
pagenumber11Page number (1-indexed)
limitnumber201100Number of results per page

Response:

{
  "data": [...],
  "pagination": {
    "total": 145,
    "page": 3,
    "limit": 20,
    "totalPages": 8
  }
}

Pagination Fields:

FieldTypeDescription
totalnumberTotal number of results (across all pages)
pagenumberCurrent page (1-indexed)
limitnumberCurrent page size
totalPagesnumberTotal number of pages (ceil(total / limit))

Example: Fetch pages 1-3

# Page 1 (items 1-20)
GET /api/v1/pages?page=1&limit=20

# Page 2 (items 21-40)
GET /api/v1/pages?page=2&limit=20

# Page 3 (items 41-60)
GET /api/v1/pages?page=3&limit=20

Example: Client-side pagination logic

async function fetchPage(pageNumber, pageSize = 20) {
  const response = await fetch(
    `https://tento-api.intelligentlending.co.uk/api/v1/pages?page=${pageNumber}&limit=${pageSize}`,
    { headers: { 'X-API-Key': process.env.CMS_API_KEY } }
  )

  const data = await response.json()

  return {
    items: data.data,
    total: data.pagination.total,
    currentPage: data.pagination.page,
    totalPages: data.pagination.totalPages,
    hasNextPage: data.pagination.page < data.pagination.totalPages,
    hasPreviousPage: data.pagination.page > 1
  }
}

// Usage
const page1 = await fetchPage(1)
const page2 = await fetchPage(2)

Best Practices:

  • Use page < totalPages to determine if a next page exists
  • Keep limit small for list views (20-50)
  • Use limit=100 only for bulk operations
  • Cache paginated results client-side to reduce API calls

Sorting

The sort parameter controls the order of results.

Syntax:

?sort={field}        # Ascending
?sort=-{field}       # Descending (prefix with -)

Supported Fields (pages):

FieldDescriptionExample Values
publishedAtPublication timestamp2025-12-15T10:00:00Z
updatedAtLast update timestamp2025-12-15T14:30:00Z
createdAtCreation timestamp2025-12-10T08:00:00Z
namePage name (alphabetical)"About Us", "Contact"
slugPage slug (alphabetical)"about-us", "contact"

Collections support the same fields plus _sortOrder for authored order. Blog supports createdAt, updatedAt, publishedAt, title, slug.

Example: Most recent first (default)

GET /api/v1/pages?sort=-publishedAt

Example: Oldest first

GET /api/v1/pages?sort=publishedAt

Example: Alphabetical by name

GET /api/v1/pages?sort=name

Example: Reverse alphabetical

GET /api/v1/pages?sort=-name

Example: Multi-field sort (primary then secondary)

GET /api/v1/pages?sort=-publishedAt,name

Combining with Other Parameters:

# Blog posts, newest first, 10 per page, title only
GET /api/v1/pages?pageType=blog-post&sort=-publishedAt&limit=10&fields=title

Filtering

Filtering via filter[field][operator]=value is collections-onlyGET /api/v1/collections/:type supports it; the pages and blog list endpoints do not accept filter[...] params at all.

Syntax:

?filter[field][operator]=value

field is either a core field (name, slug) or a custom content field prefixed with fields. (e.g. fields.price, or fields.meta.author for a nested JSON path). Omitting [operator] defaults to eq.

Operators:

OperatorDescriptionExample
eqEquals (the default when [operator] is omitted)filter[fields.category][eq]=tech
neNot equalfilter[fields.status][ne]=draft
gtGreater than (value cast to a number before comparing)filter[fields.price][gt]=50
ltLess than (value cast to a number before comparing)filter[fields.price][lt]=100
gteGreater than or equal (value cast to a number before comparing)filter[fields.price][gte]=50
lteLess than or equal (value cast to a number before comparing)filter[fields.price][lte]=100
containsSubstring match (LIKE '%value%'); %, _, and \ in the value are escaped so they match literally rather than acting as SQL wildcardsfilter[fields.title][contains]=guide
inValue is one of a comma-separated listfilter[fields.category][in]=tech,news,tutorial

Example: numeric range + equality

GET /api/v1/collections/products?filter[fields.price][gte]=10&filter[fields.price][lte]=100&filter[fields.category][eq]=electronics

Example: in operator

GET /api/v1/collections/products?filter[fields.category][in]=electronics,home,garden

Limits & restrictions:

  • Maximum 20 filter conditions per request; any beyond that are silently dropped
  • Core (non-fields.) filterable fields are limited to a small whitelist (name, slug today) — internal columns (ownership, audit fields, secrets) are never filterable through this API, even if you guess the right underlying column name
  • fields. JSON paths accept only alphanumerics, underscores, and dots, with no leading/trailing/consecutive dots — a malformed path returns INVALID_JSON_FIELD_PATH (see Error Codes)
  • An unrecognised core filter field returns INVALID_FILTER_FIELD (see Error Codes)

Copyright © 2026