Pages
Reading requires any API key. Writing requires a secret key with content:write and an Idempotency-Key header — see Write Operations.
GET /api/v1/pages
Retrieve a paginated list of published pages with optional filtering and field selection.
Request:
GET /api/v1/pages?pageType=blog-post&limit=20&page=1&sort=-publishedAt&fields=title,excerpt
X-API-Key: tento_pk_your_key_here
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
pageType | string | - | Filter by page type slug (e.g., "blog-post", "landing-page"). The equivalent filter[pageTypeId]=<slug> is also accepted — its value is a page-type slug (resolved to an ID server-side), not a UUID. |
fields | string | - | Comma-separated list of content fields to include |
page | number | 1 | Page number (1-indexed) |
limit | number | 20 | Number of results per page (max 100) |
sort | string | -publishedAt | Sort field with optional - prefix for descending order. Comma-separate for multi-field sorting (e.g. -publishedAt,name). Allowed fields: createdAt, updatedAt, publishedAt, name, slug. |
media | string | object | Media serialization: object (full { _url, _mimeType, _width, _height, _altText }) or url (bare URL string — lighter payload). |
refTypes | boolean | true | Whether resolved collection-item references carry their _type discriminator (the collection-type slug). Set refTypes=false to omit it for a smaller payload when your code already knows each reference's type. Also honoured on the single-page and collection endpoints. It does not affect the _type on embedded components — see Component data shape. |
Supported Sort Fields:
publishedAt- When page was publishedupdatedAt- Last update timestampcreatedAt- When page was createdname- Page name (alphabetical)slug- Page slug (alphabetical)
Response (200 OK):
{
"data": [
{
"id": "page_abc123",
"name": "Hello World",
"slug": "hello-world",
"type": "blog-post",
"fields": {
"title": "Hello World",
"excerpt": "My first blog post",
"body": "Welcome to my blog...",
"author": "John Doe",
"publishDate": "2025-12-15"
},
"seo": {
"metaTitle": "Hello World",
"metaDescription": "My first blog post",
"metaRobots": "index",
"ogTitle": "Hello World",
"ogDescription": "My first blog post",
"ogImage": null,
"canonicalUrl": null
},
"publishedAt": "2025-12-15T10:00:00Z",
"updatedAt": "2025-12-15T10:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 45,
"totalPages": 3
}
}
Response Fields:
| Field | Type | Description |
|---|---|---|
data | array | Array of page objects |
data[].id | string | Page unique identifier |
data[].name | string | Page display name (clean title) |
data[].slug | string | URL-friendly page identifier |
data[].type | string | Page type slug |
data[].fields | object | Page fields (defined by page type) |
data[].seo | object | SEO metadata: metaTitle, metaDescription, metaRobots, ogTitle, ogDescription, ogImage, canonicalUrl |
data[].publishedAt | string | ISO 8601 timestamp of publication |
data[].updatedAt | string | ISO 8601 timestamp of last update |
pagination.page | number | Current page (1-indexed) |
pagination.limit | number | Items per page |
pagination.total | number | Total number of matching pages |
pagination.totalPages | number | Total number of pages |
Cache Headers:
Cache-Control: public, max-age=900, stale-while-revalidate=1800
ETag: "a1b2c3d4"
Vary: Accept-Encoding, X-Preview-Key, X-API-Key
The
ETagis returned on list responses, but list endpoints do not honourIf-None-Match(no304 Not Modifiedshort-circuit). Conditional requests are supported on the single-page endpoint below.
Example: Fetch blog posts with title and excerpt only
curl -H "X-API-Key: tento_pk_..." \
"https://tento-api.intelligentlending.co.uk/api/v1/pages?pageType=blog-post&fields=title,excerpt&limit=10"
Example: Paginate through all pages
# Page 1
curl -H "X-API-Key: tento_pk_..." \
"https://tento-api.intelligentlending.co.uk/api/v1/pages?limit=20&page=1"
# Page 2
curl -H "X-API-Key: tento_pk_..." \
"https://tento-api.intelligentlending.co.uk/api/v1/pages?limit=20&page=2"
# Page 3
curl -H "X-API-Key: tento_pk_..." \
"https://tento-api.intelligentlending.co.uk/api/v1/pages?limit=20&page=3"
Example: Sort by most recently updated
curl -H "X-API-Key: tento_pk_..." \
"https://tento-api.intelligentlending.co.uk/api/v1/pages?sort=-updatedAt"
Errors:
401 Unauthorized- Missing or invalid API key400 Bad Request- Invalid query parameters (e.g., limit > 100)500 Internal Server Error- Server error
GET /api/v1/pages/:slug
Retrieve a single published page by its slug. Automatically handles old slugs via redirect information.
Request:
GET /api/v1/pages/about-us
X-API-Key: tento_pk_your_key_here
URL Parameters:
| Parameter | Type | Description |
|---|---|---|
slug | string | Required. Page slug (URL identifier) |
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
fields | string | - | Comma-separated list of content fields to include |
Response (200 OK) - Current Slug:
{
"data": {
"id": "page_abc123",
"name": "About Us",
"slug": "about-us",
"type": "landing-page",
"fields": {
"title": "About Us",
"description": "Learn about our company",
"hero": {
"image": "media_123",
"title": "Welcome"
}
},
"seo": {
"metaTitle": "About Us",
"metaDescription": "Learn about our company",
"metaRobots": "index",
"ogTitle": "About Us",
"ogDescription": "Learn about our company",
"ogImage": null,
"canonicalUrl": null
},
"publishedAt": "2025-12-01T10:00:00Z",
"updatedAt": "2025-12-15T09:30:00Z"
},
"redirect": null
}
Response (200 OK) - Old Slug with Redirect:
{
"data": {
"id": "page_abc123",
"name": "About Us",
"slug": "about-us",
"type": "landing-page",
"fields": {
"title": "About Us",
"description": "Learn about our company"
},
"seo": {
"metaTitle": "About Us",
"metaDescription": "Learn about our company",
"metaRobots": "index",
"ogTitle": "About Us",
"ogDescription": "Learn about our company",
"ogImage": null,
"canonicalUrl": null
},
"publishedAt": "2025-12-01T10:00:00Z",
"updatedAt": "2025-12-15T09:30:00Z"
},
"redirect": {
"from": "about",
"to": "about-us",
"permanent": true
}
}
Redirect Object:
When a page is requested via an old slug, the API returns:
- The page data with the current slug
- A
redirectobject indicating the slug change
| Field | Type | Description |
|---|---|---|
redirect.from | string | The slug that was requested (old slug) |
redirect.to | string | The current slug of the page |
redirect.permanent | boolean | Always true (indicates 301 redirect) |
Handling Redirects in Your Application:
If redirect is present, you should:
- Display the page content (already correct)
- Update the browser URL to the new slug (for SEO)
- Optionally return a 301 status in your framework
Example: Next.js redirect handling
export async function getStaticProps({ params }) {
const response = await fetch(
`https://tento-api.intelligentlending.co.uk/api/v1/pages/${params.slug}`,
{
headers: { 'X-API-Key': process.env.CMS_API_KEY }
}
)
const data = await response.json()
// Handle redirects
if (data.redirect) {
return {
redirect: {
destination: `/${data.redirect.to}`,
permanent: true
}
}
}
return {
props: { page: data.data },
revalidate: 60
}
}
Cache Headers:
Cache-Control: public, max-age=900, stale-while-revalidate=1800
ETag: "x1y2z3a4"
Vary: Accept-Encoding, X-Preview-Key, X-API-Key
Conditional Requests:
This single-page endpoint supports conditional requests via the If-None-Match header, returning 304 Not Modified when the content is unchanged:
# First request
curl -H "X-API-Key: tento_pk_..." \
-v https://tento-api.intelligentlending.co.uk/api/v1/pages/about-us
# Returns: ETag: "a1b2c3d4"
# Subsequent request with ETag
curl -H "X-API-Key: tento_pk_..." \
-H "If-None-Match: \"a1b2c3d4\"" \
-v https://tento-api.intelligentlending.co.uk/api/v1/pages/about-us
# Returns: 304 Not Modified (if unchanged)
Example: Fetch page with specific fields
curl -H "X-API-Key: tento_pk_..." \
"https://tento-api.intelligentlending.co.uk/api/v1/pages/about-us?fields=title,description"
Response with field selection:
{
"data": {
"id": "page_abc123",
"name": "About Us",
"slug": "about-us",
"type": "landing-page",
"fields": {
"title": "About Us",
"description": "Learn about our company"
},
"seo": {
"metaTitle": "About Us",
"metaDescription": "Learn about our company",
"metaRobots": "index",
"ogTitle": "About Us",
"ogDescription": "Learn about our company",
"ogImage": null,
"canonicalUrl": null
},
"publishedAt": "2025-12-01T10:00:00Z",
"updatedAt": "2025-12-15T09:30:00Z"
},
"redirect": null
}
The fields parameter only filters the fields object — name and seo are always included regardless of field selection (see Field Selection below).
Errors:
401 Unauthorized- Missing or invalid API key404 Not Found- Page doesn't exist or not published500 Internal Server Error- Server error
Create Page
POST /api/v1/pages
Headers:
X-API-Key: tento_sk_abc123...
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
Request Body:
{
"pageTypeSlug": "blog-post",
"name": "My First Post",
"slug": "my-first-post",
"fields": {
"title": "My First Post",
"body": "<p>Hello world</p>",
"author": "John Doe"
},
"seo": {
"metaTitle": "My First Post",
"metaDescription": "An introduction to my blog"
},
"status": "draft"
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
pageTypeSlug | string | Yes | Must match an existing page type slug |
name | string | Yes | Display name (1-255 characters) |
slug | string | Yes | URL slug (lowercase, alphanumeric + hyphens, must be unique) |
fields | object | No | Content fields, validated against page type schema, max 1 MB |
seo | object | No | SEO metadata (metaTitle, metaDescription, metaRobots, ogTitle, ogDescription, ogImageId — a media UUID, not ogImage — canonicalUrl). Read responses return the resolved image under seo.ogImage, but that's not the field you write. |
status | string | No | draft (default) or published |
Response (201):
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"pageTypeId": "abc123...",
"name": "My First Post",
"slug": "my-first-post",
"status": "draft",
"draftContent": {
"title": "My First Post",
"body": "<p>Hello world</p>",
"author": "John Doe"
},
"publishedContent": null,
"seo": { ... },
"version": 1,
"createdAt": "2026-02-09T10:00:00Z",
"updatedAt": "2026-02-09T10:00:00Z",
"publishedAt": null
}
}
If status: "published", the page is created and immediately published (version snapshot created, cache invalidated, page.published webhook fired).
Errors:
404— Page type not found409— Slug already exists413— Content fields exceed 1 MB400— Content validation failed
Update Page
PUT /api/v1/pages/:slug
Path Parameters:
slug— Page slug
Request Body (Partial Update):
All fields are optional. Only provided fields will be updated.
{
"name": "Updated Title",
"slug": "updated-slug",
"fields": {
"title": "Updated Title"
},
"seo": {
"metaTitle": "Updated Title"
}
}
Response (200):
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Updated Title",
"slug": "updated-slug",
"version": 2,
...
}
}
Errors:
404— Page not found409— Slug conflict or version conflict413— Content fields exceed 1 MB400— Content validation failed
Publish Page
POST /api/v1/pages/:slug/publish
Path Parameters:
slug— Page slug
No request body required.
Validates all required fields before publishing. Creates a version snapshot and updates cache.
Response (200):
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "published",
"publishedAt": "2026-02-09T10:00:00Z",
"publishedContent": { ... },
...
}
}
Errors:
404— Page not found400— Content validation failed (required fields missing)500— Publish operation failed
Unpublish Page
POST /api/v1/pages/:slug/unpublish
Path Parameters:
slug— Page slug
No request body required.
Reverts page to draft status. Cache is invalidated.
Response (200):
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "draft",
"publishedAt": null,
...
}
}
Errors:
404— Page not found500— Unpublish operation failed
Delete Page
DELETE /api/v1/pages/:slug
Path Parameters:
slug— Page slug
Query Parameters:
force— Set totrueto bypass the reference check and delete anyway.
No request body required.
Soft-deletes the page (sets deleted_at), invalidates the page and page-list caches, and fires the page.deleted webhook. By default, deletion is blocked if other content references the page (e.g. via a reference field) — pass ?force=true to delete regardless.
Response (200):
{
"success": true
}
Errors:
404— Page not found409(HAS_REFERENCES) — Other content still references this page; retry with?force=trueor remove the references first

