TentoCMS
Api

Pages

Read and write endpoints for pages: list, get, create, update, publish, unpublish and delete.

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:

ParameterTypeDefaultDescription
pageTypestring-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.
fieldsstring-Comma-separated list of content fields to include
pagenumber1Page number (1-indexed)
limitnumber20Number of results per page (max 100)
sortstring-publishedAtSort field with optional - prefix for descending order. Comma-separate for multi-field sorting (e.g. -publishedAt,name). Allowed fields: createdAt, updatedAt, publishedAt, name, slug.
mediastringobjectMedia serialization: object (full { _url, _mimeType, _width, _height, _altText }) or url (bare URL string — lighter payload).
refTypesbooleantrueWhether 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 published
  • updatedAt - Last update timestamp
  • createdAt - When page was created
  • name - 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:

FieldTypeDescription
dataarrayArray of page objects
data[].idstringPage unique identifier
data[].namestringPage display name (clean title)
data[].slugstringURL-friendly page identifier
data[].typestringPage type slug
data[].fieldsobjectPage fields (defined by page type)
data[].seoobjectSEO metadata: metaTitle, metaDescription, metaRobots, ogTitle, ogDescription, ogImage, canonicalUrl
data[].publishedAtstringISO 8601 timestamp of publication
data[].updatedAtstringISO 8601 timestamp of last update
pagination.pagenumberCurrent page (1-indexed)
pagination.limitnumberItems per page
pagination.totalnumberTotal number of matching pages
pagination.totalPagesnumberTotal 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 ETag is returned on list responses, but list endpoints do not honour If-None-Match (no 304 Not Modified short-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 key
  • 400 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:

ParameterTypeDescription
slugstringRequired. Page slug (URL identifier)

Query Parameters:

ParameterTypeDefaultDescription
fieldsstring-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:

  1. The page data with the current slug
  2. A redirect object indicating the slug change
FieldTypeDescription
redirect.fromstringThe slug that was requested (old slug)
redirect.tostringThe current slug of the page
redirect.permanentbooleanAlways true (indicates 301 redirect)

Handling Redirects in Your Application:

If redirect is present, you should:

  1. Display the page content (already correct)
  2. Update the browser URL to the new slug (for SEO)
  3. 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 key
  • 404 Not Found - Page doesn't exist or not published
  • 500 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:

FieldTypeRequiredDescription
pageTypeSlugstringYesMust match an existing page type slug
namestringYesDisplay name (1-255 characters)
slugstringYesURL slug (lowercase, alphanumeric + hyphens, must be unique)
fieldsobjectNoContent fields, validated against page type schema, max 1 MB
seoobjectNoSEO 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.
statusstringNodraft (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 found
  • 409 — Slug already exists
  • 413 — Content fields exceed 1 MB
  • 400 — 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 found
  • 409 — Slug conflict or version conflict
  • 413 — Content fields exceed 1 MB
  • 400 — 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 found
  • 400 — 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 found
  • 500 — Unpublish operation failed

Delete Page

DELETE /api/v1/pages/:slug

Path Parameters:

  • slug — Page slug

Query Parameters:

  • force — Set to true to 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 found
  • 409 (HAS_REFERENCES) — Other content still references this page; retry with ?force=true or remove the references first
Copyright © 2026