PagibleAI JSON:API Pages

Use /cms/pages to fetch the content and presentation data for a PagibleAI page. You can filter collections, retrieve one page by UUID, include navigation and reduce the response with sparse fieldsets.

The read-only endpoint returns pages as the primary resource and lightweight navs resources for navigation.

Request pages

Choose a request

Start with the request that matches your frontend task:

TaskRequest
Find the English root page/cms/pages?filter[tag]=root&filter[lang]=en
Retrieve one known page/cms/pages/{uuid}
Find a page by path/cms/pages?filter[path]=blog&filter[lang]=en
Render a page with its breadcrumb/cms/pages?filter[path]=article&include=ancestors
Render a page with the site menu/cms/pages?filter[path]=article&include=menu
Request only rendering fields/cms/pages/{uuid}?fields[pages]=lang,path,title,content,meta,config

Use URL and URLSearchParams in browser code instead of concatenating query strings. They encode bracketed JSON:API parameter names correctly and make optional filters easier to add.

Filter page collections

Use the collection endpoint to find pages. Use the resource endpoint when you already know the UUID:

GET /cms/pages
GET /cms/pages/{uuid}

Without a filter, the collection returns the current tenant's root pages. Add exact-match filters to narrow the result:

FilterSelects
filter[path]The stored URL path. Use an empty value for a root page.
filter[domain]Pages assigned to a domain in a multi-domain setup.
filter[tag]Pages identified by a tag such as root or blog.
filter[lang]Pages using an ISO language code such as en or en-US.
https://example.com/cms/pages?filter[tag]=root&filter[lang]=en

Combine filters as needed. Sorting is not supported; pages remain in page-tree order.

Include navigation

Use include to return navigation data with the page:

RelationshipReturns
parentThe direct parent page.
ancestorsAncestors ordered from the root page to the direct parent.
childrenDirect child pages.
menuThe menu subtree rooted at the top of the current page tree.
subtreeDescendants below the requested page, limited by the configured navigation depth.

Request several relationships as a comma-separated list:

https://example.com/cms/pages/{uuid}?include=ancestors,menu

Related resources use the navs type and appear in the top-level included array. The corresponding entries in relationships contain their type and ID.

The default maximum include-path depth is one. If CMS_JSONAPI_MAXDEPTH is greater than one, you can request nested paths such as children.children.

Paginate collections

Collection requests use page-based pagination. Both values accept integers from 1 through 100:

  • page[number] selects the result page.
  • page[size] sets the number of page resources returned. The default is 15.

This request returns the second result page with up to 25 resources:

https://example.com/cms/pages?page[number]=2&page[size]=25

The response describes the current slice in meta.page. Follow the first, last, prev, and next links when they are present instead of rebuilding pagination URLs.

Reduce response fields

Use fields[pages] to return only the page attributes and relationships your client needs. Use fields[navs] independently for included navigation resources.

https://example.com/cms/pages?filter[tag]=root&include=menu&fields[pages]=lang,path,name,menu&fields[navs]=parent_id,path,name

This response contains lang, path, and name for the page, plus parent_id, path, and name for each included navigation resource. JSON:API always retains the resource type and id outside attributes.

When you combine a sparse page fieldset with include, add the relationship name to fields[pages]. For example, include=parent requires parent in the page fieldset or the relationship cannot be serialized.

Read the response

A JSON:API response contains these top-level members as needed:

  • jsonapi identifies the JSON:API version.
  • meta contains PagibleAI metadata and collection pagination.
  • links contains server-generated resource or pagination URLs.
  • data contains one page resource, a collection, or null.
  • included contains the navigation resources requested through include.

Response metadata

meta.baseurl is the base URL for relative public file and preview paths:

{
  "meta": {
    "baseurl": "https://cdn.example.com/"
  }
}

Collection responses also contain meta.page with currentPage, from, lastPage, perPage, to, and total. total is the total number of resources matching the request, while lastPage depends on the selected page size.

Page attributes

Each pages resource can expose these attributes:

AttributeMeaning
parent_idUUID of the direct parent, or null for a root page.
langISO language code.
pathStored URL path value.
nameShort name used in navigation and the editor.
titlePage title.
themeAssigned theme name.
typePage template type.
toRedirect target, if configured.
domainAssigned domain in a multi-domain setup.
hasNumber of descendants in the stored page tree. This is an integer, not a boolean.
cacheConfigured rendered-page cache duration in minutes. It does not define JSON:API HTTP caching; follow the response's Cache-Control header.
createdAtPage creation time in ISO 8601 format.
updatedAtLast modification time in ISO 8601 format.
contentOrdered page content elements.
metaPage metadata grouped by schema type.
configPage configuration grouped by schema type.

tag can be used as a collection filter but is not serialized as a page attribute.

Follow response links

Each resource in data and included has a links.self URL. A collection response uses its top-level links member for pagination and can contain first, last, prev, and next.

Treat the returned links as authoritative. They retain the current filters, fieldsets, includes, page number, and page size.

Render page content

content is an ordered, flat array of content elements. Each item contains:

  • id: the element identifier within the page.
  • type: the configured content schema type.
  • data: values defined by that schema.
  • files: an optional object of resolved files keyed by file UUID.

Shared-element references are resolved before delivery, so your client receives the referenced element type and data rather than an internal reference item. The editor-only layout group is omitted.

{
  "id": "AiYfEC",
  "type": "image-text",
  "data": {
    "text": "Build the page from structured content.",
    "file": {
      "id": "01986b6d-69fb-71ac-8243-f8ac984519a8",
      "type": "file"
    }
  },
  "files": {
    "01986b6d-69fb-71ac-8243-f8ac984519a8": {"mime": "image/webp"}
  }
}

Resolve file URLs

A resolved file can contain disk, mime, lang, name, path, previews, description, and transcription. Descriptions and transcriptions are selected for the page language.

For a relative public path or preview path, prepend meta.baseurl. Private files use page-aware /cmsasset/... URLs; use those URLs exactly as returned because access is checked against the enclosing page.

You can resolve either form with new URL(file.path, payload.meta.baseurl).href: an absolute private URL remains unchanged, while a relative public path is resolved against baseurl.

"files": {
  "01986b6d-69fb-71ac-8243-f8ac984519a8": {
    "disk": "public",
    "mime": "image/webp",
    "lang": null,
    "name": "Team photo",
    "path": "cms/team.webp",
    "previews": {
      "480": "cms/team-480.webp",
      "960": "cms/team-960.webp"
    },
    "description": "Three developers working together",
    "transcription": null
  }
}

Use page metadata and configuration

meta and config are objects keyed by their schema type. Each entry contains its type, schema-defined data, and resolved files when it references media.

{
  "meta": {
    "meta-tags": {
      "type": "meta-tags",
      "data": {
        "description": "A concise search description"
      }
    }
  },
  "config": {
    "theme": {
      "type": "theme",
      "data": {
        "--color-primary": "#103050"
      }
    }
  }
}

Match related navigation resources

Every requested relationship appears below the page resource's relationships member. Its linkage identifies the related resource by type and id; the complete navs resource appears once in the top-level included array.

{
  "relationships": {
    "parent": {
      "data": {
        "type": "navs",
        "id": "0198d0f8-8c1e-75fc-b7fd-e5d9f3a7c1e3"
      }
    }
  },
  "included": [
    {
      "type": "navs",
      "id": "0198d0f8-8c1e-75fc-b7fd-e5d9f3a7c1e3",
      "attributes": {
        "path": "blog",
        "name": "Blog"
      }
    }
  ]
}

The included array is flat even when the relationship represents a tree. Use each navigation resource's parent_id to reconstruct the hierarchy.

Understand visibility and versions

The API evaluates the active Laravel user for every request:

  • Guests receive published pages without frontend access restrictions.
  • Authenticated frontend users can receive authentication-only pages and pages allowed by Laravel Gate.
  • CMS users with page:view can receive the latest page version, including unpublished changes.
  • Disabled pages are hidden from normal frontend requests.
  • Restricted pages are removed from collections and included relationships. A direct request for a page the user cannot access returns 404 Not Found.

This filtering prevents the response from revealing restricted page IDs through navigation relationships.

Handle errors

Unsuccessful requests return a JSON:API errors array. Common cases are:

  • 404 Not Found for a missing, disabled, or inaccessible page.
  • 400 Bad Request for invalid or unsupported query parameters.
  • 429 Too Many Requests after more than 60 requests per minute from one IP address.
  • 500 Internal Server Error for an unexpected server failure.

Read the error document before throwing an exception so you can surface its title or detail. Keep APP_DEBUG disabled in production.

Fetch and render a page

This example fetches the English root page and its menu, handles JSON:API errors, and renders basic heading and text content without inserting untrusted HTML.

<main id="app"></main>
const endpoint = new URL('/cms/pages', window.location.origin);
endpoint.searchParams.set('filter[tag]', 'root');
endpoint.searchParams.set(
  'filter[lang]',
  globalThis.document.documentElement.lang || 'en',
);
endpoint.searchParams.set('include', 'menu');
endpoint.searchParams.set('fields[pages]', 'title,content,menu');
endpoint.searchParams.set(
  'fields[navs]',
  'parent_id,path,name,title',
);

const response = await fetch(endpoint, {
  headers: {Accept: 'application/vnd.api+json'},
  credentials: 'same-origin',
});
const payload = await response.json();

if (!response.ok) {
  const message = payload.errors?.[0]?.detail
    ?? payload.errors?.[0]?.title
    ?? `Request failed with ${response.status}`;
  throw new Error(message);
}

const page = payload.data?.[0];

if (!page) {
  throw new Error('The root page was not found.');
}

const app = globalThis.document.querySelector('#app');
const pageTitle = globalThis.document.createElement('h1');
pageTitle.textContent = page.attributes.title;
app.append(pageTitle);

for (const element of page.attributes.content ?? []) {
  let node;

  if (element.type === 'heading') {
    const level = Math.min(
      6,
      Math.max(2, Number(element.data.level) || 2),
    );
    node = globalThis.document.createElement(`h${level}`);
    node.textContent = element.data.title ?? '';
  } else if (element.type === 'text') {
    node = globalThis.document.createElement('p');
    node.textContent = element.data.text ?? '';
  } else {
    continue;
  }

  app.append(node);
}

const included = new Map(
  (payload.included ?? []).map(resource => [
    `${resource.type}:${resource.id}`,
    resource,
  ]),
);
const menu = (page.relationships?.menu?.data ?? [])
  .map(reference => included.get(`${reference.type}:${reference.id}`))
  .filter(Boolean);

console.log('Navigation resources', menu);

In a real frontend, map every content type to a component. The text field can contain Markdown; use a Markdown renderer that fits your application and sanitize any HTML before inserting it into the DOM. See JSON:API Navigation to turn the flat menu resources into a tree.