@content-island/api-client
v0.23.0
Published
Content Island - REST API Client
Readme
@content-island/api-client
Installation
npm install @content-island/api-clientAuthentication
The client is configured with a single accessToken option that is sent on every
request as Authorization: Bearer <accessToken>. Content Island supports two kinds
of tokens:
- Read token — grants read-only access to the project. Use it for any
get*method (fetching content, listing, sizing, project metadata). - Write token — grants both read and write access. Required for any method that creates, updates, or uploads data.
A write token is a strict superset of a read token: it works for everything. A
read token only works for the read methods. Calling a write method with a read
token returns 403 Forbidden.
import { createClient } from '@content-island/api-client';
// Read-only consumer (e.g. SSG/SSR site fetching content):
const reader = createClient({ accessToken: '<your-read-token>' });
// Authoring tools, ingestion pipelines, admin scripts:
const writer = createClient({ accessToken: '<your-write-token>' });Token required per method
| Method | Token required |
| ------------------------- | ----------------- |
| getProject | Read or Write |
| getContentList | Read or Write |
| getContent | Read or Write |
| getRawContentList | Read or Write |
| getRawContent | Read or Write |
| getContentListSize | Read or Write |
| createContent | Write |
| updateContentFieldValue | Write |
| uploadMedia | Write |
Examples
Basic Usage
// Your model
interface Post {
id: string;
title: string;
body: string;
order: number;
language: 'es' | 'en';
}import { createClient } from '@content-island/api-client';
const client = createClient({ accessToken: <your-token>});
const postsWithDefaultLanguage = client.getContentList<Post>({ contentType: 'post'}); // Retrieve the list of contents in the project filtered by content type, for example 'post' in the
const englishPosts = client.getContentList<Post>({ contentType: 'post', language: 'en'}); // Get english posts
const spanishPosts = client.getContentList<Post>({ contentType: 'post', language: 'es'}); // Get spanish posts
// Or you can retrieve a content by id
const postById = client.getContent<Post>({ id: 'content-id', language: 'es' }); // Retrieve a content by id
const postBySomeField = client.getContent<Post>({ 'fields.title': 'post-title', language: 'es' }); // Retrieve a content by field value
Snapshot mode
The client can serve content reads from a content snapshot — a single JSON document exported from your project — instead of hitting the network on every request. This is the recommended way to consume content from a static site generator (Astro, Next.js, Gatsby, …): you export the snapshot once at build time and the build reads from it with zero API round-trips.
There are two modes:
'api'(default) — every read is a network request to the B2B API, exactly as before.'snapshot'— reads are served from a snapshot file loaded from disk, with no network request. The snapshot engine reproduces the live API's filtering, sorting, pagination, language fallback, and related-content resolution, so the results are identical to api mode over the same data.
Configuration
import { createClient } from '@content-island/api-client';
const client = createClient({
accessToken: '<your-read-token>',
mode: 'snapshot',
// snapshotPath omitted — defaults to DEFAULT_SNAPSHOT_PATH ('./content-island-snapshot.json')
});accessTokenis required in all modes. (In snapshot mode it still identifies the client; reads do not use the network, but the token is part of the client contract.)modedefaults to'api'. Set it to'snapshot'to serve reads from the snapshot.snapshotPathis client-level and independent ofmode: it points at the snapshot file. It is optional — when omitted it defaults toDEFAULT_SNAPSHOT_PATH('./content-island-snapshot.json'), a constant exported from the package and shared with the CLI's--snapshot-pathdefault. An'api'-mode client may still setsnapshotPath(for example, to exposegetSnapshotInfo()while reading live).
import { createClient, DEFAULT_SNAPSHOT_PATH } from '@content-island/api-client';
// Zero-config: export to the default location, then read it back with no path.
// npx content-island export --access-token <your-read-token>
// writes ./content-island-snapshot.json, which this client resolves automatically:
const client = createClient({ accessToken: '<your-read-token>', mode: 'snapshot' });snapshotPath (and the DEFAULT_SNAPSHOT_PATH default) is cwd-relative — it is
resolved against the process working directory at read time. In an SSR/serverless
runtime where the cwd is not the snapshot's location, pass an absolute path, e.g.
snapshotPath: path.resolve(process.cwd(), 'content-island-snapshot.json').
If a read resolves to 'snapshot' mode on a client created without
snapshotPath, it loads DEFAULT_SNAPSHOT_PATH — there is no "snapshotPath
required" error. If no readable, valid snapshot exists at the resolved path, the
load rejects with an ApiClientError whose message names that path.
Per-query mode override
The five content reads — getContentList, getContent, getRawContentList,
getRawContent, and getContentListSize — accept a per-query mode that takes
precedence over the client-level mode for that call only:
effective mode = per-query mode ?? client-level mode ?? 'api'// An api-mode client that occasionally reads from a snapshot:
const client = createClient({
accessToken: '<your-read-token>',
snapshotPath: './content-island-snapshot.json',
});
const live = await client.getContentList<Post>({ contentType: 'post' }); // network
const fromSnapshot = await client.getContentList<Post>({
contentType: 'post',
mode: 'snapshot', // this call only — served from the snapshot, no network
});// A snapshot-mode client that occasionally needs fresh data:
const client = createClient({
accessToken: '<your-read-token>',
mode: 'snapshot',
snapshotPath: './content-island-snapshot.json',
});
const fromSnapshot = await client.getContentList<Post>({ contentType: 'post' }); // snapshot
const live = await client.getContentList<Post>({
contentType: 'post',
mode: 'api', // this call only — hits the network
});getProject is routed by the client-level mode only — it takes no query
params, so there is no per-query override.
mode is a client-only key: it is never serialized into the request query string
in api mode.
Related-content metadata: onRelatedContentMeta
The five content reads also accept an optional per-query callback,
onRelatedContentMeta, invoked exactly once with the related-content resolution
metadata for that call:
await client.getContentList<Post>({
contentType: 'post',
includeRelatedContent: 'all',
onRelatedContentMeta: ({ resolvedDepth, partial }) => {
// resolvedDepth: how deep the related-content BFS actually resolved
// partial: true when a depth or budget cap left part of the graph unresolved
console.log({ resolvedDepth, partial });
},
});It works in both modes — sourced from the X-Related-Content-Resolved-Depth /
X-Related-Content-Partial response headers in api mode, and from the engine's
BFS result in snapshot mode, with identical values for the same data and query.
When the callback is omitted, behavior and return shapes are unchanged. Like
mode, it is never serialized into the request query string.
Writes are not available in snapshot mode
A snapshot-mode client serves reads only. Every write/management method
(createContent, publishContent, updateContentFieldValue, uploadMedia,
createModel, updateModel, deleteModel, createEnum, updateEnum,
deleteEnum) rejects with an ApiClientError (code SNAPSHOT_MODE) and performs
no network call. There is no per-query override for writes — on a snapshot-mode client
they always reject. If you need to read from a snapshot and write, create a
second, separate api-mode client for the writes:
const reader = createClient({
accessToken: '<your-read-token>',
mode: 'snapshot',
snapshotPath: './content-island-snapshot.json',
});
const writer = createClient({ accessToken: '<your-write-token>' }); // api modegetSnapshotInfo()
getSnapshotInfo() resolves with the snapshot's meta block — useful for
freshness checks (e.g. logging when the snapshot was produced during a build):
const meta = await client.getSnapshotInfo();
// {
// schemaVersion: 1,
// exportedAt: '2026-06-12T10:00:00.000Z', // ISO-8601
// projectId: '...',
// view: 'published' | 'preview',
// }It works on any client, regardless of the client-level mode (so an api-mode
client can report on the snapshot it has on disk). On a client without
snapshotPath, it loads DEFAULT_SNAPSHOT_PATH; if no readable, valid snapshot
exists at the resolved path, it rejects with an ApiClientError whose message
names that path.
Exporting a snapshot
CLI: content-island export
The package ships a content-island binary that downloads a snapshot and writes
it to disk as plain JSON:
npx content-island export --access-token <your-read-token> --snapshot-path ./content-island-snapshot.jsonThe flags map 1:1 (kebab-case) onto the matching exportSnapshot option:
| Flag | exportSnapshot option | Type | Description | Default |
| ---------------------- | ----------------------- | ------- | ---------------------------------------------------------------------------------------------- | -------------------------------- |
| --access-token | accessToken | string | Access token (required). Falls back to the CONTENT_ISLAND_ACCESS_TOKEN environment variable. | — |
| --snapshot-path | snapshotPath | string | Path to write the snapshot JSON. | ./content-island-snapshot.json |
| --domain | domain | string | Target domain (self-hosted/staging). Respects the client's default-domain resolution. | client default |
| --no-secure-protocol | secureProtocol | boolean | Use HTTP instead of HTTPS (for local/self-hosted http targets). | HTTPS (secure) |
| --secure-protocol | secureProtocol | boolean | Use HTTPS explicitly (redundant — this is already the default). | HTTPS (secure) |
| --api-version | apiVersion | string | API version segment to target. | client default |
The request is HTTPS by default. Pass --no-secure-protocol to use plain
HTTP when targeting a local or self-hosted http endpoint:
# Export from a local http instance (e.g. a dev server on localhost):
npx content-island export \
--access-token <your-read-token> \
--domain localhost:8082 \
--no-secure-protocol \
--snapshot-path ./content-island-snapshot.jsonThe exported view is token-driven: a PREVIEW_-prefixed token exports the
preview (draft) view, any other token exports the published view. There is no
view flag.
On success the CLI prints a summary and exits 0:
Content snapshot exported successfully.
Output: ./content-island-snapshot.json
Size: 1.42 MB
Exported: 2026-06-12T10:00:00.000Z
View: publishedIf the token is missing, or the request/validation fails, the CLI writes the
error to stderr and exits non-zero — and because the write goes through a
temp-file-then-rename, no partial or invalid file is left at --snapshot-path.
Programmatic: exportSnapshot
exportSnapshot is the function the CLI is built on; use it directly from a Node
script when you need more control:
import { exportSnapshot } from '@content-island/api-client';
const snapshot = await exportSnapshot({
accessToken: process.env.CONTENT_ISLAND_ACCESS_TOKEN!,
snapshotPath: './content-island-snapshot.json', // optional — omit to just get the parsed snapshot
// domain, secureProtocol, apiVersion are also accepted
});
console.log(snapshot.meta.exportedAt, snapshot.contents.length);It fetches the snapshot, validates its shape and schema version, optionally writes
it to snapshotPath (same safe temp-then-rename strategy as the CLI), and resolves
with the parsed ContentSnapshot. A non-2xx response (including 429, which maps
to code RATE_LIMITED) rejects with the standard ApiClientError.
Size guidance
The snapshot is a single JSON document loaded fully into memory by the snapshot-mode
client. The practical limit is around 20 MB of uncompressed JSON on disk.
Above that threshold the CLI prints a warning (it still exits 0): snapshots this
large are loaded entirely into memory and may approach upstream request/body and
timeout limits. If you cross it, consider narrowing the exported content or
keeping the snapshot well under the threshold.
Recommended workflow
Use mode: 'api' in local development (always fresh, no export step) and
mode: 'snapshot' in production builds (fast, no per-request network calls).
Switch between them with an environment variable so the same code runs in both:
import { createClient } from '@content-island/api-client';
const client = createClient({
accessToken: process.env.CONTENT_ISLAND_ACCESS_TOKEN!,
mode: process.env.NODE_ENV === 'production' ? 'snapshot' : 'api',
snapshotPath: './content-island-snapshot.json',
});In production builds, generate the snapshot first (e.g. as a build step) and then run the build, which reads from it in snapshot mode.
GitHub Action snippet
This step exports the snapshot with the CLI before building the site:
name: Build site
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- name: Install dependencies
run: npm ci
- name: Export content snapshot
env:
CONTENT_ISLAND_ACCESS_TOKEN: ${{ secrets.CONTENT_ISLAND_ACCESS_TOKEN }}
run: npx content-island export
- name: Build (snapshot mode)
env:
NODE_ENV: production
run: npm run buildThe token is read from CONTENT_ISLAND_ACCESS_TOKEN, so it never appears in the command
line or logs. The build then reads from ./content-island-snapshot.json in snapshot mode.
Documentation
For more detailed documentation, please refer to the Content Island API Client documentation.
