Start with the delivery API#
The Contoprix delivery API is how a website reads the pages, content, navigation, and media that editors have published. The easiest path is the JavaScript SDK, but the same published data is also available through REST and GraphQL.
Use delivery APIs to read published content. Use the admin application for modeling, editing, publishing, and media administration.
Choose the right tool#
| Need | Recommended starting point | Required API-client scope |
|---|---|---|
| Render a page, list content, navigation, search, or sitemap | @contoprix/client or REST delivery routes | delivery:read |
| Fetch one standalone media item by ID | SDK or GET /api/delivery/media/{id} | media:read |
| Select a custom GraphQL response shape | GraphQL at POST /graphql | graphql:read |
| Download or push the content model | Contoprix CLI / SDK schema routes | schema:read, plus schema:write to push |
| Show draft content in a protected preview | SDK preview methods | preview:read |
Scopes are deliberately separate. A REST-only delivery key does not grant GraphQL access, and a GraphQL-only key does not grant normal REST delivery access.
Warning
Treat delivery keys, client secrets, preview keys, and API tokens as server-side secrets. Do not put them in NEXT_PUBLIC_ variables, browser bundles, screenshots, or source control.
First request with the SDK#
Install the core client:
npm install @contoprix/client @contoprix/typesStore the API origin and a scoped delivery key in server-side environment variables:
CONTOPRIX_BASE_URL=https://cms.example.com
CONTOPRIX_DELIVERY_KEY=your-delivery-keyCreate one shared client at a server-side integration boundary:
import { ContoprixClient } from "@contoprix/client";
export const contoprix = new ContoprixClient({
baseUrl: process.env.CONTOPRIX_BASE_URL!,
auth: {
type: "deliveryKey",
deliveryKey: process.env.CONTOPRIX_DELIVERY_KEY!,
},
languageCode: "en",
});Now a server component, route handler, or data loader can read a published page:
import { contoprix } from "@/lib/contoprix";
const page = await contoprix.pages.getBySlug("/about");
console.log(page.name, page.slug, page.blocks);Pass ordinary application paths such as /about or /products/widgets. The SDK encodes page paths for the delivery route.
Common SDK calls#
const home = await contoprix.pages.get();
const about = await contoprix.pages.getBySlug("/about");
const children = await contoprix.pages.getAllChildPages("/products");
const result = await contoprix.content.list({
contentType: "article",
take: 10,
skip: 0,
sort: "newest",
});
const article = await contoprix.content.getBySlug("article", "hello-contoprix");
const navigation = await contoprix.navigation.get();
const searchResults = await contoprix.search.query("pricing", 10);Content fields are schema-defined, so a content entry stores them in data, not as fixed properties on the entry itself:
const first = result.items[0];
const title = String(first?.data.title ?? first?.slug ?? "Untitled");Do not assume every content type has a title field. Use the field codes from the content model or generated types from the CLI.
REST routes at a glance#
All routes below are relative to CONTOPRIX_BASE_URL. The value should be the API origin, such as https://cms.example.com, not a URL ending in /api.
| Route | Purpose | Scope |
|---|---|---|
GET /api/delivery/pages?languageCode=en | The published / page | delivery:read |
GET /api/delivery/pages/{path}?languageCode=en | A published page by path | delivery:read |
GET /api/delivery/pages/children?slug=/products | Published child-page summaries | delivery:read |
GET /api/delivery/content/{contentType} | A paginated published content list | delivery:read |
GET /api/delivery/content/{contentType}/{entryId} | One entry by ID | delivery:read |
GET /api/delivery/content/{contentType}/slug/{slug} | One entry by slug | delivery:read |
GET /api/delivery/navigation?languageCode=en | Navigation tree | delivery:read |
GET /api/delivery/search?q=pricing | Search pages and content | delivery:read |
GET /api/delivery/sitemap?languageCode=en | Sitemap entries | delivery:read |
GET /api/delivery/media/{mediaId} | One media record and its variants | media:read |
The SDK uses these routes for you. Use raw REST only when you are integrating from a language or runtime that cannot use the JavaScript SDK.
Raw REST example#
This example fetches a page on the server and handles an error before parsing its JSON.
export async function getPage(path: string) {
const url = new URL(
`/api/delivery/pages/${path.replace(/^\/+/, "")}`,
process.env.CONTOPRIX_BASE_URL!,
);
url.searchParams.set("languageCode", "en");
const response = await fetch(url, {
headers: {
"x-contoprix-delivery-key": process.env.CONTOPRIX_DELIVERY_KEY!,
},
});
if (response.status === 404) return null;
if (!response.ok) throw new Error(`Contoprix request failed: ${response.status}`);
return response.json();
}For a page at /, call GET /api/delivery/pages instead of appending an empty path segment.
Language and pagination#
Most delivery routes accept languageCode. Set a default on the client, then override only when a request needs another language:
const frenchArticles = await contoprix.content.list({
contentType: "article",
languageCode: "fr",
take: 12,
skip: 0,
sort: "newest",
});Content lists return items and pagination. Use take, skip, and pagination.hasNext rather than requesting every record at once.
Delivery-key checklist#
- Create an API client for the intended website.
- Grant only the scopes the application needs.
- Give the server
CONTOPRIX_BASE_URLand the credential as private environment variables. - Make calls from a server component, route handler, backend, or worker.
- Test with a published page or entry in the correct language.
- Add a webhook or suitable cache policy so published changes reach visitors promptly.
Common problems#
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 or "missing delivery key" | No credential reached the API. | Set the correct header or use the SDK. |
| 403 | The key lacks the route's scope. | Add delivery:read, media:read, or the exact required scope. |
| 404 for content that exists in Admin | The entry is not published, the key belongs to another website, or the language differs. | Check publication status, website, and languageCode. |
| Browser CORS or secret exposure | A privileged request is running in browser code. | Move it to the server. |
| Wrong content shape | The code assumed a field that is not in the model. | Read entry.data using the actual schema field code or generate types. |
Continue with the detailed SDK guide, use GraphQL when you need a custom response shape, or set up Webhooks for cache refreshes.