Content API

The content API is how you deliver SleekCMS content to your website, app, or frontend. It exposes your entire site's published content — pages, entries, images, option sets, and configuration — as structured JSON through a single authenticated endpoint. You fetch content by environment, and the response includes everything your frontend needs to render the site.

This page covers the API endpoint, authentication, response structure, the official client library and its methods, caching, internationalization, and framework integration patterns.


Endpoint and Authentication

The content API uses a single endpoint per site. You authenticate with a site token passed as the Authorization header and specify which environment to fetch from in the URL path.

GET https://pub.sleekcms.com/{site-id}/{environment}

{site-id} — Your site's unique identifier, assigned when you create the site in SleekCMS.

{environment} — The environment alias to fetch content from. Use latest for the current working state, or a named environment like production or staging for a specific content snapshot.

Authentication

Every request requires a site token in the Authorization header. You can find your site token in the SleekCMS admin interface under site settings.

curl -H "Authorization: YOUR_SITE_TOKEN" \
  "https://pub.sleekcms.com/YOUR_SITE_ID/latest"

The site token is a read-only credential — it grants access to published content but cannot modify content, models, or settings. Treat it as a secret in server-side environments but be aware that it is necessarily exposed in client-side applications that call the API directly from the browser.

Environments & Content Versions


Response Structure

The API returns your entire site's content as a single JSON payload. This full-payload approach means your frontend receives everything in one request — no pagination, no follow-up queries, no resolving references.

{
  "pages": [ ... ],
  "entries": { ... },
  "images": { ... },
  "videos": { ... },
  "options": { ... },
  "config": { ... },
  "_tag": "..."
}

pages

An array of all page records across all page models. Each page object contains its field data plus system properties:

_path — The page's URL path. Static pages have a fixed path like /about. Collection pages have a path that includes the slug, like /blog/hello-world.

_slug — Present on collection pages only. The slug portion of the path, used for route generation.

_meta — Metadata including key (the internal record identifier), created_at, and updated_at timestamps.

All content fields — text, images, references, groups, collections, dynamic blocks — are resolved inline. Referenced entries are embedded as full objects, not IDs. Markdown and rich text fields are delivered as rendered HTML. Image fields are delivered as objects with url, raw, alt, and variant properties.

{
  "_slug": "italian",
  "_path": "/blog/italian",
  "title": "Italian inspiration on a budget",
  "published_date": "2025-03-03",
  "image": {
    "alt": "Fresh Italian ingredients on a rustic table",
    "url": "https://img.sleekcms.com/az41/m7urgy6z.webp",
    "raw": "https://img.sleekcms.com/az41/m7urgy6z",
    "source": null,
    "light": null,
    "dark": null
  },
  "category": ["veg", "vegan"],
  "content": "<p>Create a blog post subtitle...</p>",
  "_meta": {
    "key": "cms:r2s2t1",
    "updated_at": "2026-01-11T23:32:48.894Z",
    "created_at": "2026-01-11T23:32:48.894Z"
  }
}

entries

An object keyed by entry handle. Single entries (like a footer or site settings) resolve to an object. Entry collections (like authors or categories) resolve to an array of objects.

{
  "entries": {
    "footer": {
      "copyright_text": "© 2025 Salt & Pepper. All rights reserved.",
      "socials": [
        { "icon": "fab fa-facebook-f", "link": "/" },
        { "icon": "fab fa-instagram", "link": "/" }
      ]
    }
  }
}

When a page references an entry through a reference field, the entry's full data is embedded inline in the page object. You don't need to look up entries separately — they're already resolved in the page data.

images

Named images registered at the site level, keyed by name. Each image object follows the same structure as image fields on content records: url, raw, alt, source, and optional light/dark theme variants.

options

Option sets with assigned handles, keyed by handle name. Each option set is an array of { label, value } pairs.

config

Site-level configuration:

title — The site title, if set.

origin — The origin URL for the site, used for generating absolute URLs.

subdirectory — The subdirectory path, if the site is deployed under a subpath.

_tag

A version tag for the content snapshot. This value changes whenever content is published to the requested environment. Use it for cache invalidation — if the tag hasn't changed, the content hasn't changed.


Client Library

The official @sleekcms/client library wraps the API with typed methods for querying pages, entries, images, and option sets. It handles authentication, caching, environment resolution, and provides both synchronous and asynchronous client modes.

Installation

npm install @sleekcms/client

Or include directly from a CDN for browser usage:

<script src="https://unpkg.com/@sleekcms/client"></script>

When loaded from a CDN, the library is available globally as SleekCMS.

Creating a Client

The library provides two client constructors. Both accept the same configuration options but differ in how they fetch and return content.

createSyncClient() — Fetches all content upfront during initialization. After initialization, all methods return data synchronously. Best for static site generation where you want instant access to the full content at build time.

import { createSyncClient } from '@sleekcms/client';

const client = await createSyncClient({
  siteToken: 'your-site-token',
  env: 'latest'
});

// All methods return data immediately — no await needed
const page = client.getPage('/about');
const posts = client.getPages('/blog');

createAsyncClient() — Fetches content on demand. Every method returns a Promise. Best for server-side rendering where you want fresh content per request without loading the entire payload upfront.

import { createAsyncClient } from '@sleekcms/client';

const client = createAsyncClient({
  siteToken: 'your-site-token',
  resolveEnv: true
});

// All methods are async
const page = await client.getPage('/pricing');
const posts = await client.getPages('/blog');

Configuration Options

Option Type Default Description
siteToken string required Your site token from SleekCMS.
env string 'latest' Environment alias to fetch content from.
resolveEnv boolean false Resolves the environment alias to its underlying version tag. Useful for CDN cache invalidation — produces a different cache key when content changes. Adds some latency to each request.
lang string Language code for internationalized content. When set, the client returns translations for the specified locale.
cache SyncCacheAdapter | AsyncCacheAdapter In-memory Custom cache adapter for storing fetched content.
cacheMinutes number Cache expiration time in minutes. If not set, cached content never expires.

Client Methods

All methods are available on both the sync and async clients. On the sync client, they return values directly. On the async client, they return Promises.

getContent(search?)

Returns the full content payload, or a subset filtered using a JMESPath query. This is the most flexible method — JMESPath expressions can filter, project, and transform the content data.

// Full content payload
const content = client.getContent();

// All pages
const pages = client.getContent('pages');

// A specific entry
const footer = client.getContent('entries.footer');

// Site title from config
const title = client.getContent('config.title');

// Filter pages with JMESPath
const aboutPage = client.getContent('pages[?_path==`/about`] | [0]');
const featuredPosts = client.getContent('pages[?featured==`true`]');

JMESPath is a query language for JSON. It supports filtering ([?field==\value`]`), projections, slicing, and multi-select expressions. For full syntax, see jmespath.org.

getPage(path)

Returns a single page by exact path match. Returns null if no page exists at the given path.

const about = client.getPage('/about');
const post = client.getPage('/blog/hello-world');
const home = client.getPage('/');

The path must match exactly. /about matches the about page but not /about/team. For prefix matching, use getPages().

getPages(path, options?)

Returns all pages whose path starts with the given prefix. Use this to retrieve all pages within a section of your site.

const posts = client.getPages('/blog');
const products = client.getPages('/shop/products');

Options:

Option Type Description
collection boolean When true, only returns pages that belong to a collection (pages with a _slug). Excludes static index pages.
// All pages under /blog, including the /blog index page
const allBlogPages = client.getPages('/blog');

// Only blog post collection items (excludes the /blog index)
const posts = client.getPages('/blog', { collection: true });

getEntry(handle)

Returns an entry by its handle. Single entries return an object. Entry collections return an array of objects.

// Single entry (e.g., admin-only site settings)
const header = client.getEntry('header');
// { logo: { url: '...' }, links: [...] }

// Entry collection
const team = client.getEntry('team-members');
// [{ name: 'Jane', role: 'Engineer', ... }, { name: 'Alex', role: 'Designer', ... }]

getSlugs(path)

Extracts slug values from all collection pages under a given path. Returns an array of strings. This is the primary method for generating static routes in frameworks like Next.js, Astro, and 11ty.

const slugs = client.getSlugs('/blog');
// ['hello-world', 'nextjs-tips', 'typescript-guide']

const productSlugs = client.getSlugs('/products');
// ['basic-plan', 'pro-plan', 'enterprise']

getImage(name)

Returns a named image registered at the site level.

const logo = client.getImage('logo');
// { url: 'https://...', alt: 'Company logo', raw: '...', ... }

getOptions(name)

Returns an option set by handle as an array of label-value pairs.

const categories = client.getOptions('categories');
// [{ label: 'Technology', value: 'tech' }, { label: 'Design', value: 'design' }, ...]

This is useful for building filter interfaces, navigation menus, or client-side dropdowns that need the full list of available options rather than just the selected value on a content record.


Caching

The client includes built-in caching to reduce API calls and improve performance. By default, fetched content is stored in an in-memory cache. You can provide your own cache adapter and control expiration.

Default Behavior

With no cache configuration, the client uses an in-memory cache that persists for the lifetime of the process. Content is fetched once and served from memory on subsequent calls.

Custom Cache Adapters

Any object that implements getItem and setItem works as a cache adapter. The browser's localStorage is a natural fit for client-side applications:

const client = await createSyncClient({
  siteToken: 'your-site-token',
  cache: localStorage,
  cacheMinutes: 60 * 24  // Cache expires after 1 day
});

For custom storage backends, implement the adapter interface:

// Synchronous adapter
interface SyncCacheAdapter {
  getItem(key: string): string | null;
  setItem(key: string, value: string): void;
}

// Asynchronous adapter (for IndexedDB, Redis, remote caches)
interface AsyncCacheAdapter {
  getItem(key: string): Promise<string | null>;
  setItem(key: string, value: string): Promise<void>;
}

Cache Expiration

The cacheMinutes option controls how long cached content remains valid. When the cache expires, the next request fetches fresh content from the API and repopulates the cache.

If cacheMinutes is not set, cached content never expires — it persists until the cache is manually cleared or the process restarts (for in-memory caches).


Internationalization

The client supports multi-language content through the lang configuration option. When set, the API returns translated content for the specified locale.

const client = await createSyncClient({
  siteToken: 'your-site-token',
  lang: 'es'
});

const page = client.getPage('/about');
// Returns the Spanish translation of the about page

Language codes correspond to the locales configured in your SleekCMS site settings. If a translation doesn't exist for the requested locale, the API falls back to the default language content.

To serve multiple languages, create a separate client instance per locale:

const clients = {
  en: await createSyncClient({ siteToken: 'your-site-token', lang: 'en' }),
  es: await createSyncClient({ siteToken: 'your-site-token', lang: 'es' }),
  de: await createSyncClient({ siteToken: 'your-site-token', lang: 'de' }),
};

const page = clients[userLocale].getPage('/about');

Supporting Multiple Languages


Content Data Shapes

Understanding the shape of content objects in API responses helps you build typed frontends and render content correctly.

Page Objects

Every page object includes its content fields plus system properties:

Property Type Description
_path string The full URL path for the page.
_slug string The slug segment (collection pages only).
_meta.key string Internal record identifier.
_meta.created_at string ISO 8601 creation timestamp.
_meta.updated_at string ISO 8601 last update timestamp.

All other properties are the page's content fields, named by their handle.

Image Objects

Image fields and named images share the same object structure:

Property Type Description
url string The optimized image URL (typically WebP format).
raw string The base image URL without format conversion. Used for building transformed URLs with query parameters.
alt string | null Alt text for the image.
source string | null The image source provider (e.g., "unsplash", null for uploaded images).
light object | null Light theme variant of the image, if set.
dark object | null Dark theme variant of the image, if set.

Entry Objects

Entry objects contain their content fields plus a _meta block. The shape depends on the entry model's field configuration. When referenced from a page, the full entry object is embedded inline.

Dynamic Block Arrays

Dynamic block fields return an array of block objects. Each block includes a _type property identifying the block model handle, plus the block's content fields:

const page = client.getPage('/');
// page.sections → [
//   { _type: 'hero', heading: 'Welcome', image: { url: '...' } },
//   { _type: 'features', items: [...] },
//   { _type: 'cta', heading: 'Get Started', buttonLabel: 'Sign Up' }
// ]

TypeScript

The client library is fully typed. Import the type definitions to annotate your code:

import type {
  SleekClient,
  SleekAsyncClient,
  Page,
  Entry,
  Image,
  Options
} from '@sleekcms/client';

Framework Integration

The client works with any JavaScript framework. The choice between sync and async clients maps to the rendering strategy your framework uses.

Next.js (Static Site Generation)

Use the sync client in generateStaticParams and page components to fetch content at build time:

// app/blog/[slug]/page.tsx
import { createSyncClient } from '@sleekcms/client';

export async function generateStaticParams() {
  const client = await createSyncClient({
    siteToken: process.env.SLEEKCMS_SITE_TOKEN!
  });
  return client.getSlugs('/blog').map((slug) => ({ slug }));
}

export default async function Post({ params }: { params: { slug: string } }) {
  const client = await createSyncClient({
    siteToken: process.env.SLEEKCMS_SITE_TOKEN!
  });
  const post = client.getPage(`/blog/${params.slug}`);
  return <h1>{post?.title}</h1>;
}

Next.js (Server-Side Rendering)

Use the async client with resolveEnv to ensure fresh content on each request:

// app/blog/page.tsx
import { createAsyncClient } from '@sleekcms/client';

const client = createAsyncClient({
  siteToken: process.env.SLEEKCMS_SITE_TOKEN!,
  resolveEnv: true
});

export default async function BlogPage() {
  const posts = await client.getPages('/blog');
  return (
    <div>
      {posts?.map((post) => (
        <article key={post._path}>
          <h2>{post.title}</h2>
        </article>
      ))}
    </div>
  );
}

SvelteKit

// +page.server.ts
import { createAsyncClient } from '@sleekcms/client';

const client = createAsyncClient({
  siteToken: process.env.SLEEKCMS_SITE_TOKEN,
  resolveEnv: true
});

export async function load() {
  const posts = await client.getPages('/blog');
  return { posts };
}

Astro

---
// src/pages/blog/index.astro
import { createSyncClient } from '@sleekcms/client';

const client = await createSyncClient({
  siteToken: import.meta.env.SLEEKCMS_SITE_TOKEN
});

const posts = client.getPages('/blog');
---

<div>
  {posts?.map((post) => (
    <article>
      <h2>{post.title}</h2>
    </article>
  ))}
</div>

React (Client-Side)

For React SPAs, use the @sleekcms/react package which provides hooks with built-in loading states and error handling:

import { SleekCMSProvider, usePage, usePages, useEntry } from '@sleekcms/react';

function App() {
  return (
    <SleekCMSProvider siteToken="your-site-token">
      <BlogPage />
    </SleekCMSProvider>
  );
}

function BlogPage() {
  const { data: posts, loading } = usePages('/blog');
  if (loading) return <p>Loading...</p>;
  return (
    <div>
      {posts?.map((post) => (
        <article key={post._path}>
          <h2>{post.title}</h2>
        </article>
      ))}
    </div>
  );
}

@sleekcms/react


What's Next