Agent-ready instructions for content integrations.
Give coding agents a narrow, explicit contract so they can integrate Jade content without guessing, leaking secrets, or calling private admin endpoints.
Agent contract
This is the compact contract a coding agent should follow when building with Jade.
Use public routes for delivery
Content delivery uses /api/public/v1. Do not use app session endpoints for public rendering.
Pass a scoped workspace key
Use Authorization: Bearer $JADE_API_KEY or X-API-Key. Use only the scopes the integration needs.
Read published content
Public document endpoints return published documents only. Draft previews must stay behind authenticated app routes.
Mutate only when trusted
Collection and document writes use /api/management/v1 with collections:write or content:write. Do not expose write-scoped keys in public clients.
Asset and media workflow
Asset management is a trusted integration workflow, while public sites consume the asset URL stored in published document data.
Keep storage credentials private
r2.dev for development or low-volume temporary access.Authenticated app-session asset routes
GET /api/projects/{projectId}/assets?search=hero
POST /api/projects/{projectId}/assets?filename=hero.png
DELETE /api/assets/{assetId}API-key management asset routes
GET /api/management/v1/projects/{projectId}/assets?search=hero
POST /api/management/v1/projects/{projectId}/assets?filename=hero.png
DELETE /api/management/v1/assets/{assetId}Media field rule
media field selects an asset from the same project library. Store the returned asset object in document data and use itsid, url, filename, and metadata when rendering. API-key integrations need assets:read to list/search and assets:write to upload/delete. The public API does not provide signed private URLs yet.Prompt pack
Paste this block into an LLM or coding agent before asking it to build a Jade integration.
Agent setup prompt
You are integrating Jade CMS public content.
Rules:
- Use https://jade.kabeli.org/api/public/v1 for content delivery.
- Use https://jade.kabeli.org/api/management/v1 only for trusted collection, field, document, or asset management.
- Authenticate with Authorization: Bearer $JADE_API_KEY.
- Never call private /api/workspaces, /api/collections, /api/documents, or session endpoints from the public site.
- Use assets:read to list/search assets and assets:write to upload/delete them.
- Never expose assets:write, collections:write, or content:write keys in public browser bundles.
- Fetch published documents only from:
GET /projects/{projectSlug}/collections/{collectionSlug}/documents?limit=20&offset=0
- Store JADE_API_KEY on the server when possible.
- If browser fetching is required, the Jade API key must have exact allowed origins configured.
- Use content:read for delivery, forms:submit for public form posts, collections:write for schema changes, content:write for document changes, and assets:read/assets:write for asset management.
- Treat data as JSON and keep unknown custom fields under document.data.
- For localized content, create one document in the project's default locale, then use the same document ID with PUT /documents/{documentId}/translations/{locale} for each additional language.
- Send only one language's values in each translation data object. Do not create a second document for Nepali.
- Request public content with ?locale=ne-NP or another enabled locale. Missing published localized fields fall back per field to the project's default locale.
- On 401, report missing or invalid key. On 403, report blocked Origin. On 404, report missing published content.
Build the smallest reliable integration first, then add caching, pagination, and type guards.Localized content workflow
Use this deterministic workflow whenever a collection has English, Nepali, or any other project languages.
The document ID is shared
Write English, then Nepali
// 1. Create the document once.
const english = await jadeManagementFetch(
"/collections/{collectionId}/documents",
{
method: "POST",
body: {
locale: "en",
status: "published",
data: { title: "Welcome", body: "<p>English content</p>" },
},
},
);
// 2. Attach Nepali to the same document ID.
await jadeManagementFetch(
"/documents/" + english.id + "/translations/ne-NP",
{
method: "PUT",
body: {
status: "published",
data: { title: "स्वागत छ", body: "<p>नेपाली सामग्री</p>" },
},
},
);Read with locale and fallback
const posts = await jadeFetch(
"/projects/website/collections/posts/documents?locale=ne-NP",
);
// A public response uses a published Nepali value when present.
// An empty/missing localized field uses the published default value.
// Management responses expose the missing keys in fallback_fields.Avoid the common mistake
POST .../documents with locale: "ne-NP" creates a new document identity. That is only correct for a separate content item, not a translation. Use PUT .../translations/ne-NP for the existing document.Generation rules
These rules prevent the most common agent-generated mistakes.
TypeScript contract
Use these types as the starting point for generated clients.
Types
type JadeProject = {
id: string;
name: string;
slug: string;
created_at: string;
updated_at: string;
};
type JadeField = {
id: string;
name: string;
key: string;
field_type:
| "text"
| "textarea"
| "rich_text"
| "number"
| "boolean"
| "date"
| "select"
| "relation"
| "media"
| "json";
required: boolean;
validations: Record<string, unknown>;
settings: Record<string, unknown>;
sort_order: number;
};
type JadeDocument<TData extends Record<string, unknown> = Record<string, unknown>> = {
id: string;
collection_id: string;
status: "published";
locale?: string;
requested_locale?: string;
translation_status?: "missing" | "draft" | "published";
data: TData;
available_locales?: Array<{
code: string;
name: string;
native_name: string;
is_default: boolean;
status: "missing" | "draft" | "published";
updated_at?: string;
}>;
fallback_fields?: string[];
created_at: string;
updated_at: string;
};Agent workflows
Use these as reference implementations for generated code.
Server-rendered website
JADE_API_KEY from the environment, fetches published documents, validates the fields the page needs, and caches for a short interval.Minimal fetch helper
const JADE_BASE_URL = "https://jade.kabeli.org/api/public/v1";
export async function jadeFetch<T>(path: string): Promise<T> {
const response = await fetch(`${JADE_BASE_URL}${path}`, {
headers: {
Authorization: `Bearer ${process.env.JADE_API_KEY}`,
},
next: { revalidate: 60 },
});
if (!response.ok) {
throw new Error(`Jade request failed: ${response.status}`);
}
return response.json() as Promise<T>;
}List posts
type PostData = {
title?: string;
excerpt?: string;
body?: string;
};
export function listPosts() {
return jadeFetch<Array<JadeDocument<PostData>>>(
"/projects/website/collections/posts/documents?limit=20&offset=0",
);
}LLM files
Jade exposes LLM files so agents can discover the public contract without scraping the whole site.
/llms.txt
/llms-full.txt
