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.

Jade API

A stable API for content delivery and management.

Read published Jade content and manage collections, fields, entries, and assets from integrations using workspace-scoped API keys.

Quickstart

This is the fastest path from a fresh key to a real content response.

Auth

Bearer or X-API-Key

Format

JSON over HTTPS

Versions

/public/v1 + /management/v1

Fetch projects

curl \
  -H "Authorization: Bearer $JADE_API_KEY" \
  https://jade.kabeli.org/api/public/v1/projects

Fetch published posts

curl \
  -H "Authorization: Bearer $JADE_API_KEY" \
  "https://jade.kabeli.org/api/public/v1/projects/website/collections/posts/documents?limit=20&offset=0"

Create a collection

curl -X POST \
  -H "Authorization: Bearer $JADE_MANAGEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Posts","description":"Editorial posts"}' \
  https://jade.kabeli.org/api/management/v1/projects/{projectId}/collections

Authentication

API keys are created from workspace settings by owners or admins. Superadmins can lock keys when the key should not be rolled or deleted by workspace admins.

Supported key locations

Authorization: Bearer jade_live_...
X-API-Key: jade_live_...
?api_key=jade_live_...

Secret handling

Jade returns the full key only when the key is created or rolled. After that, settings show only the prefix. Store the full key in a secret manager or server-side environment variable.

Scopes

content:read       read published delivery content
forms:submit       submit public form collections
collections:read   list and fetch collection schemas
collections:write  create, update, delete, and archive collection schemas
content:write      create, update, and delete content documents
assets:read        list and search project assets
assets:write       upload and delete project assets

Domains and CORS

Allowed origins are enforced for browser requests. They do not replace API key secrecy, but they prevent unexpected browser domains from using a key.

Browser apps

Configure exact origins such as https://www.example.com. Public responses include CORS headers only when the request Origin matches the key.

Server apps

Server requests usually do not send an Origin header. Store keys in server-side environment variables and call the API from the server when possible.

Blank origin list

A blank allowed-origin list permits any browser origin. Use it for prototypes, then tighten it before production.

Endpoint reference

Delivery routes are safe for published content access. Management routes require explicit write scopes and are intended for trusted integrations.

Delivery

MethodPathScopeReturnsUse
GET/api/public/v1/projectscontent:readProject[]List all projects visible to the API key workspace.
GET/api/public/v1/projects/{projectSlug}/collectionscontent:readCollection[]List collections for a project slug.
GET/api/public/v1/projects/{projectSlug}/collections/{collectionSlug}content:readCollectionFetch collection metadata plus active field definitions.
GET/api/public/v1/projects/{projectSlug}/collections/{collectionSlug}/documentscontent:readDocument[]List published documents in the saved collection order. Supports limit and offset.
GET/api/public/v1/projects/{projectSlug}/collections/{collectionSlug}/documents/{documentId}content:readDocumentFetch one published document by UUID.
POST/api/public/v1/projects/{projectSlug}/forms/{formSlug}/submissionsforms:submitFormSubmissionSubmit a website form collection.

Management

MethodPathScopeReturnsUse
GET/POST/api/management/v1/projects/{projectId}/collectionscollections:read/writeCollection[] | CollectionList or create collections in a project.
GET/PATCH/DELETE/api/management/v1/collections/{collectionId}collections:read/writeCollectionFetch, update, or delete one collection.
GET/POST/api/management/v1/collections/{collectionId}/fieldscollections:read/writeCollectionField[] | CollectionFieldList or create active field definitions.
GET/PATCH/api/management/v1/collection-fields/{fieldId}collections:read/writeCollectionFieldFetch or update one field definition.
POST/api/management/v1/collection-fields/{fieldId}/archivecollections:writeCollectionFieldArchive one field definition.
GET/POST/api/management/v1/collections/{collectionId}/documentscontent:read/writeDocument[] | DocumentList documents in the saved collection order, or create one.
GET/PATCH/DELETE/api/management/v1/documents/{documentId}content:read/writeDocumentFetch, update, or delete one content document.
GET/api/management/v1/documents/{documentId}/translationscontent:readLocale[]List every project language and its translation status for a document.
GET/PUT/api/management/v1/documents/{documentId}/translations/{locale}content:read/writeDocumentRead or create/replace one translation on the existing document ID.
GET/api/management/v1/projects/{projectId}/assetsassets:readAsset[]Search project assets by filename, title, MIME type, or alt text.
POST/api/management/v1/projects/{projectId}/assetsassets:writeAssetUpload one raw file to a project's configured object storage.
DELETE/api/management/v1/assets/{assetId}assets:write204Delete one asset and its stored object.

Field value shapes

Collection field definitions determine the shape and validation rules for values inside document.data.

Structured lists

Use field_type: "list" for repeatable content. Setsettings.item_type to "text" or"key_value". A field uses one item format for its entire lifetime; create a new field if the shape needs to change. List fields are available for content collections, not public forms.

Create a text list field

POST /api/management/v1/collections/{collectionId}/fields
{
  "name": "Learning goals",
  "key": "learning_goals",
  "field_type": "list",
  "settings": {
    "item_type": "text"
  }
}

Create a title/key list field

POST /api/management/v1/collections/{collectionId}/fields
{
  "name": "Field modules",
  "key": "field_modules",
  "field_type": "list",
  "settings": {
    "item_type": "key_value"
  }
}

Document data

{
  "learning_goals": [
    "Understand agroecology principles",
    "Practice observation-led learning",
    "Plan a small farm or teaching action"
  ],
  "field_modules": [
    {
      "title": "Learning goals",
      "key": "learning_goals"
    },
    {
      "title": "Action planning",
      "key": "action_planning"
    }
  ]
}

Text items

Values must be an array of non-empty strings. Empty arrays are allowed for optional fields.

Title/key items

Values must be an array of objects with exactly{ title: string, key: string }. Both strings must be non-empty.

No mixed list shapes

Do not mix strings and objects in one list. The admin editor exposes add, remove, and reorder controls for the selected format.

Asset library and storage

The asset library has an authenticated CMS surface for the Jade UI and an API-key management surface for trusted integrations, both backed by workspace storage settings.

Cloudflare R2 setup

Configure the private S3-compatible endpoint and browser-facing asset base URL in workspace Settings → Storage. Use an R2 custom domain for production delivery and reserve r2.dev for development or low-volume temporary access. See the complete storage and asset setup guide guide.

Authenticated app-session routes

GET    /api/projects/{projectId}/assets?search=hero
POST   /api/projects/{projectId}/assets?filename=hero.png
DELETE /api/assets/{assetId}
GET    /api/workspaces/{workspaceId}/storage-settings
PATCH  /api/workspaces/{workspaceId}/storage-settings

API-key management 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}

Role boundaries

App-session users follow workspace member/editor roles. API-key integrations use assets:read and assets:write; a write scope also grants read access. Upload requests send raw file bytes with the file's Content-Type. Keep write-scoped keys server-side.

Localized content

A localized document keeps one stable ID and stores one translation per project language.

One document, many translations

Create the document once in the project's default language. Then use the same documentId with the explicit translations endpoint for Nepali or any other enabled language. Never create a second document just to represent another translation.

1. Create the English document

curl -X POST   -H "Authorization: Bearer $JADE_MANAGEMENT_API_KEY"   -H "Content-Type: application/json"   -d '{
    "locale": "en",
    "status": "published",
    "data": {
      "title": "Welcome",
      "body": "<p>English content</p>"
    }
  }'   https://jade.kabeli.org/api/management/v1/collections/{collectionId}/documents

2. Add Nepali to the same document

curl -X PUT   -H "Authorization: Bearer $JADE_MANAGEMENT_API_KEY"   -H "Content-Type: application/json"   -d '{
    "status": "published",
    "data": {
      "title": "स्वागत छ",
      "body": "<p>नेपाली सामग्री</p>"
    }
  }'   https://jade.kabeli.org/api/management/v1/documents/{documentId}/translations/ne-NP

3. Deliver Nepali with default fallback

curl   -H "Authorization: Bearer $JADE_API_KEY"   "https://jade.kabeli.org/api/public/v1/projects/website/collections/posts/documents?locale=ne-NP"

Management response shape

{
  "id": "same-document-id-for-en-and-ne-np",
  "locale": "ne-NP",
  "requested_locale": "ne-NP",
  "translation_status": "published",
  "data": {
    "title": "स्वागत छ",
    "body": "<p>नेपाली सामग्री</p>"
  },
  "available_locales": [
    { "code": "en", "is_default": true, "status": "published" },
    { "code": "ne-NP", "is_default": false, "status": "published" }
  ],
  "fallback_fields": []
}

Fallback is per field

If a Nepali localized field is empty, the public API uses the published value from the project's default language for that field. The management API reports those keys in fallback_fields. Non-localized fields always use the default-language value.

Response schemas

The API returns stable IDs and timestamps, while slugs keep request URLs readable.

Project

{
  "id": "0c3a...",
  "name": "Website",
  "slug": "website",
  "created_at": "2026-06-25T08:00:00Z",
  "updated_at": "2026-06-25T08:00:00Z"
}

Collection with fields

{
  "id": "9b14...",
  "project_id": "0c3a...",
  "name": "Posts",
  "slug": "posts",
  "description": "Editorial posts",
  "settings": {},
  "fields": [
    {
      "id": "a8f1...",
      "name": "Title",
      "key": "title",
      "field_type": "text",
      "required": true,
      "validations": {},
      "settings": {},
      "sort_order": 0
    }
  ]
}

Document

{
  "id": "72fd...",
  "collection_id": "9b14...",
  "status": "published",
  "sort_order": 0,
  "locale": "en",
  "requested_locale": "en",
  "translation_status": "published",
  "data": {
    "title": "Launch notes",
    "body": "..."
  },
  "available_locales": [
    { "code": "en", "name": "English", "native_name": "English", "is_default": true, "status": "published" },
    { "code": "ne-NP", "name": "Nepali", "native_name": "नेपाली", "is_default": false, "status": "missing" }
  ],
  "fallback_fields": [],
  "created_at": "2026-06-25T08:00:00Z",
  "updated_at": "2026-06-25T08:10:00Z"
}

Errors

Errors are intentionally simple so clients and agents can recover without guessing.

401API key missing or invalid.
403Request Origin is not allowed for this key.
404Project, collection, or published document was not found.
422Request parameters failed validation.

Error body

{
  "detail": "API key is invalid"
}

Integration examples

Prefer server-side fetching for production sites. Browser fetching is supported when allowed origins are configured.

Next.js server component

async function getPosts() {
  const response = await fetch(
    "https://jade.kabeli.org/api/public/v1/projects/website/collections/posts/documents?limit=20",
    {
      headers: {
        Authorization: `Bearer ${process.env.JADE_API_KEY}`,
      },
      next: { revalidate: 60 },
    },
  );

  if (!response.ok) {
    throw new Error("Could not fetch posts");
  }

  return response.json();
}

Browser client

const response = await fetch(
  "https://jade.kabeli.org/api/public/v1/projects/website/collections/posts/documents",
  {
    headers: {
      "X-API-Key": "jade_live_...",
    },
  },
);

Create a content document

const response = await fetch(
  "https://jade.kabeli.org/api/management/v1/collections/{collectionId}/documents",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.JADE_MANAGEMENT_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      status: "draft",
      data: {
        title: "Launch notes",
        body: "...",
      },
    }),
  },
);

Operational guidance

These rules keep production integrations easy to reason about.

Roll keys after accidental exposure or personnel changes.
Lock keys when only a superadmin should be able to roll or delete them.
Issue separate delivery and management keys with only the scopes each integration needs.
Use exact allowed origins for browser clients.
Keep draft previews on private authenticated routes.
Use collection metadata to generate TypeScript types or validation schemas.