Himalayan village and mountain landscape

Jade developer docs

Build with the same Jade product surface.

Public API references, integration guides, and agent-ready instructions using the same navigation and footer as the homepage.

AI agents

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

Use the management asset routes only from trusted integrations and do not expose write-scoped keys or R2 credentials. Workspace owners/admins configure storage in Jade; use an R2 custom domain for production delivery and reserve 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

A 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

A translation is not a second document. The document created by the first POST owns the stable ID; every language is a translation attached to that ID. Project settings define the enabled locales and the default fallback locale.

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.

Do not expose JADE_API_KEY in a client bundle unless the integration intentionally fetches from a browser and allowed origins are configured.
Do not assume collection fields are fixed. Fetch collection metadata or define local types for the collection being rendered.
Do not render draft content from the public API. It will not be returned.
Do not expose collections:write or content:write keys to browser bundles.
Do not hard-code document IDs for list pages. Use project and collection slugs, then paginate.
Do not retry 401 or 403 in a loop. Surface setup instructions to the developer.

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

Generate a server helper that reads 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

Concise product and docs index for AI tools.

/llms-full.txt

Expanded product summary, content API contract, endpoint map, and agent rules.