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/projectsFetch 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}/collectionsAuthentication
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
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 assetsDomains 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
| Method | Path | Scope | Returns | Use |
|---|---|---|---|---|
| /api/public/v1/projects | content:read | Project[] | List all projects visible to the API key workspace. | |
| /api/public/v1/projects/{projectSlug}/collections | content:read | Collection[] | List collections for a project slug. | |
| /api/public/v1/projects/{projectSlug}/collections/{collectionSlug} | content:read | Collection | Fetch collection metadata plus active field definitions. | |
| /api/public/v1/projects/{projectSlug}/collections/{collectionSlug}/documents | content:read | Document[] | List published documents in the saved collection order. Supports limit and offset. | |
| /api/public/v1/projects/{projectSlug}/collections/{collectionSlug}/documents/{documentId} | content:read | Document | Fetch one published document by UUID. | |
| /api/public/v1/projects/{projectSlug}/forms/{formSlug}/submissions | forms:submit | FormSubmission | Submit a website form collection. |
Management
| Method | Path | Scope | Returns | Use |
|---|---|---|---|---|
| /api/management/v1/projects/{projectId}/collections | collections:read/write | Collection[] | Collection | List or create collections in a project. | |
| /api/management/v1/collections/{collectionId} | collections:read/write | Collection | Fetch, update, or delete one collection. | |
| /api/management/v1/collections/{collectionId}/fields | collections:read/write | CollectionField[] | CollectionField | List or create active field definitions. | |
| /api/management/v1/collection-fields/{fieldId} | collections:read/write | CollectionField | Fetch or update one field definition. | |
| /api/management/v1/collection-fields/{fieldId}/archive | collections:write | CollectionField | Archive one field definition. | |
| /api/management/v1/collections/{collectionId}/documents | content:read/write | Document[] | Document | List documents in the saved collection order, or create one. | |
| /api/management/v1/documents/{documentId} | content:read/write | Document | Fetch, update, or delete one content document. | |
| /api/management/v1/documents/{documentId}/translations | content:read | Locale[] | List every project language and its translation status for a document. | |
| /api/management/v1/documents/{documentId}/translations/{locale} | content:read/write | Document | Read or create/replace one translation on the existing document ID. | |
| /api/management/v1/projects/{projectId}/assets | assets:read | Asset[] | Search project assets by filename, title, MIME type, or alt text. | |
| /api/management/v1/projects/{projectId}/assets | assets:write | Asset | Upload one raw file to a project's configured object storage. | |
| /api/management/v1/assets/{assetId} | assets:write | 204 | Delete 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
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
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-settingsAPI-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
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
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}/documents2. 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-NP3. 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
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.
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.
