Content items API
A content item is one record of a content type, such as a post or a product. This page shows how to create, read, edit, publish and delete items through /raytha/api/v1/ContentItems, how field values are shaped in requests and responses, and how to create up to 500 items in one call.
Create an item
POST to the content type's developer name. You need templateId, the id of the web template that renders the item, and a content object keyed by field developer name.
curl -s -X POST "$RAYTHA_URL/raytha/api/v1/ContentItems/posts" \
-H "X-API-KEY: $RAYTHA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "REPLACE_WITH_TEMPLATE_ID",
"saveAsDraft": false,
"content": {
"title": "Hello from the API",
"rank": 3,
"featured": true,
"tags": ["news", "launch"]
}
}'
Raytha answers 201 with the new id as result, and a Location header pointing at the item:
{
"result": "h5anxM6u4UW80v09zzQjXg",
"error": "",
"success": true
}
Find a template id
Either ask for templates, which needs the templates permission, or copy webTemplateId from an existing item of the same type.
curl -s "$RAYTHA_URL/raytha/api/v1/WebTemplates?themeDeveloperName=raytha_default_theme&pageSize=100" \
-H "X-API-KEY: $RAYTHA_API_KEY" | jq '.result.items[] | {id, developerName, label}'
curl -s "$RAYTHA_URL/raytha/api/v1/ContentItems/posts?pageSize=1" \
-H "X-API-KEY: $RAYTHA_API_KEY" | jq -r '.result.items[0].webTemplateId'
Use a template from the active theme that has access to the content type. Without access, the settings endpoint rejects it.
Rules for create
saveAsDraft: falsepublishes the item immediately.truecreates an unpublished draft.- An unknown field name fails with
x is not a recognized field for content type: Post. - A required field is enforced when you publish, not when you save a draft. Raytha checks only the fields you send, so leaving a required field out is not caught. Send every required field.
- You cannot set the route path on create. Raytha builds it from the content type's route template, which is the primary field by default, and prefixes the item id if the path is taken. Change it afterwards with the settings endpoint below.
Field values
Requests take plain values. Responses wrap each value as {"value", "text", "hasValue"}. Never send a response back unchanged.
| Field type | Send | Response value |
|---|---|---|
| Single line text, long text, rich text, color | A string. Color is #rrggbb. | The string |
| Number | A JSON number | A number |
| Checkbox | true or false | A boolean |
| Date | An ISO date such as "2026-01-14" | An ISO date and time |
| Dropdown, radio | One choice developer name | The developer name |
| Multiple select | An array of choice developer names | An array |
| Attachment | The media item's object key. See Media. | The object key |
| Relationship | The related item's id | The related item as an object, or an empty string if unset |
| Repeater | An array of objects keyed by sub-field developer name | Check the shape on your own data |
Read items
One item
curl -s "$RAYTHA_URL/raytha/api/v1/ContentItems/posts/h5anxM6u4UW80v09zzQjXg" \
-H "X-API-KEY: $RAYTHA_API_KEY"
{
"result": {
"id": "h5anxM6u4UW80v09zzQjXg",
"creatorUser": {
"id": "7v8Ji16pRENDaQja6rdvJQ",
"firstName": "Ada",
"lastName": "Lovelace",
"emailAddress": "[email protected]",
"fullName": "Ada Lovelace"
},
"lastModifierUser": null,
"creationTime": "2026-01-14T09:30:00.123456Z",
"lastModificationTime": null,
"isPublished": true,
"isDraft": false,
"contentTypeId": "VbWfEFPY4UeoqbfHTCvxlg",
"routePath": "hello-from-the-api",
"primaryField": "Hello from the API",
"webTemplateId": "9jSQNEGeRkm_kagUng4OFw",
"publishedContent": {
"title": {
"value": "Hello from the API",
"text": "Hello from the API",
"hasValue": true
},
"rank": {
"value": 3,
"text": "3",
"hasValue": true
},
"featured": {
"value": true,
"text": "True",
"hasValue": true
},
"tags": {
"value": [
"news",
"launch"
],
"text": "news, launch",
"hasValue": true
},
"author": {
"id": "5qZGgMciiI-hQUnfKku_rk",
"routePath": "authors/ada",
"primaryField": "Ada Lovelace",
"isPublished": true
}
}
},
"error": "",
"success": true
}
The relationship field author arrives as the related item. For a published item, publishedContent is what visitors see. draftContent is the latest saved state, including unpublished edits. The two are equal unless you saved a draft over a published item.
A list
Lists take filter, search, orderBy, pageNumber, pageSize (default 50, at most 1000, or the view's own maximum) and viewId. The result is {items, totalCount}.
curl -s -G "$RAYTHA_URL/raytha/api/v1/ContentItems/posts" \
-H "X-API-KEY: $RAYTHA_API_KEY" \
--data-urlencode "filter=IsPublished eq 'true' and featured eq 'true'" \
--data-urlencode "orderBy=CreationTime desc" \
--data-urlencode "pageSize=10" | jq '.result | {totalCount, titles: [.items[].primaryField]}'
searchmatches the primary field, as a contains search. With aviewId, it searches the columns the view defines.viewIdapplies a saved view's filter, sort and page size. If the view ignores client filters, yourfilterandorderByare ignored. Without a view, the default order is newest first.- Use
curl -Gwith--data-urlencodeso spaces and quotes in filters are escaped. See Filtering.
By route path
GET /ContentItems/{type}/route/{routePath} returns the route record, not the item. Read contentItemId from it and fetch the item. The route path is one URL segment, so encode or avoid slashes. To find items with multi-segment paths, store a unique key in a field and filter on it.
{
"result": {
"id": "k3dQ0mWmV0q1xJc1b8vH5w",
"path": "hello-from-the-api",
"viewId": null,
"contentItemId": "h5anxM6u4UW80v09zzQjXg",
"sitePageId": null,
"raythaFunctionId": null,
"pathType": "ContentItem"
},
"error": "",
"success": true
}
Edit an item
PUT /ContentItems/{type}/{id} replaces the whole field set. A field you leave out is cleared. To change one field, read the item, copy its values, change the one you need and send everything back:
ID="h5anxM6u4UW80v09zzQjXg"
URL="$RAYTHA_URL/raytha/api/v1/ContentItems/posts/$ID"
curl -s "$URL" -H "X-API-KEY: $RAYTHA_API_KEY" \
| jq '{
saveAsDraft: false,
content: (
.result.draftContent
| with_entries(
if (.value | type) == "object" and (.value | has("hasValue")) then
(if .value.hasValue then .value |= .value else empty end)
elif (.value | type) == "object" and (.value | has("id")) then
.value |= .id
else . end
)
+ { summary: "Edited with the API" }
)
}' \
| curl -s -X PUT "$URL" \
-H "X-API-KEY: $RAYTHA_API_KEY" \
-H "Content-Type: application/json" \
-d @-
The filter unwraps each value, turns a related item back into its id, and drops fields that have no value.
saveAsDraft: falsepublishes the new content, marks the item published, and keeps the previous published content as a revision.saveAsDraft: truesaves only the draft. The item stays published with its old content until you save withfalse.- Both outcomes return
resultset to the item id.
Publish state, route path and template
| Goal | Request |
|---|---|
| Publish a draft | PUT /{type}/{id} with saveAsDraft: false and the content to publish. There is no separate publish endpoint. |
| Unpublish | PUT /{type}/{id}/unpublish, no body |
| Discard unpublished edits | PUT /{type}/{id}/discard-draft, no body |
| Change route path or template | PUT /{type}/{id}/settings with {"routePath": "blog/hello", "templateId": "…"}. Both fields are required. |
| Make it the home page | PUT /{type}/{id}/set-as-home-page, no body |
| Move many items to one template | POST /{type}/template with {"templateId": "…", "contentItemIds": ["…"]} |
A route path is slugified. It can use letters, numbers and dashes, with slashes between segments. A dot is allowed only in the last segment, for names such as robots.txt. It can be at most 200 characters.
Delete, trash and restore
| Goal | Request | Needs |
|---|---|---|
| Move to the trash | DELETE /{type}/{id} | edit |
| Trash several | DELETE /{type}/items with {"ids": ["…", "…"]} | edit |
| List the trash | GET /{type}/trash | config |
| Restore | PUT /{type}/{id}/restore, using the id from the trash list | config |
| Delete for good | DELETE /{type}/trash/{id} | config |
A restored item comes back unpublished. Publish it with an edit.
Create many items
POST /ContentItems/{type}/batch queues up to 500 items and answers 202 with a background task id. Each item is created as a single create would be. One failing item does not stop the others.
curl -s -X POST "$RAYTHA_URL/raytha/api/v1/ContentItems/posts/batch" \
-H "X-API-KEY: $RAYTHA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "REPLACE_WITH_TEMPLATE_ID",
"items": [
{ "content": { "title": "First" } },
{ "content": { "title": "Second" }, "saveAsDraft": true }
]
}'
Poll the task until status.developerName is complete or error. When it completes, statusInfo is a JSON string with the outcome for each item, by position:
curl -s "$RAYTHA_URL/raytha/api/v1/BackgroundTasks/TASK_ID" \
-H "X-API-KEY: $RAYTHA_API_KEY" \
| jq '.result | {status: .status.developerName, percentComplete, info: (.statusInfo | try fromjson catch .)}'
{
"total": 2,
"created": 1,
"failed": 1,
"items": [
{ "index": 0, "success": true, "id": "h5anxM6u4UW80v09zzQjXg", "errors": [] },
{ "index": 1, "success": false, "id": null, "errors": [{ "field": "title", "message": "'Title' field is required." }] }
]
}
A relationship field in a batch may hold an item id, a route path, or the related item's primary field value. An item can refer to another item in the same batch by primary field value, and Raytha creates that one first. An item may set its own templateId, which overrides the batch one.
Gotchas
- Lists include drafts and unpublished items. Filter on
IsPublished eq 'true'for anything public. - Edit is a replace. Omitted fields are cleared, and the response does not warn you.
- A draft save does not publish. A visitor sees
publishedContentuntil you save withsaveAsDraft: false. - The content type name in the URL is its developer name, not its label. A wrong name returns
404. - Filters read published content. A draft-only change is not visible to
filteruntil it is published. - Creating and editing raise content events. Active content event functions and webhooks run for items you write through the API.