Platform
Learn
Developer docs User guide Quickstart Blog
Company
Services About Contact Links Get started

Content items API

Updated

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: false publishes the item immediately. true creates 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 typeSendResponse value
Single line text, long text, rich text, colorA string. Color is #rrggbb.The string
NumberA JSON numberA number
Checkboxtrue or falseA boolean
DateAn ISO date such as "2026-01-14"An ISO date and time
Dropdown, radioOne choice developer nameThe developer name
Multiple selectAn array of choice developer namesAn array
AttachmentThe media item's object key. See Media.The object key
RelationshipThe related item's idThe related item as an object, or an empty string if unset
RepeaterAn array of objects keyed by sub-field developer nameCheck 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]}'
  • search matches the primary field, as a contains search. With a viewId, it searches the columns the view defines.
  • viewId applies a saved view's filter, sort and page size. If the view ignores client filters, your filter and orderBy are ignored. Without a view, the default order is newest first.
  • Use curl -G with --data-urlencode so 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: false publishes the new content, marks the item published, and keeps the previous published content as a revision.
  • saveAsDraft: true saves only the draft. The item stays published with its old content until you save with false.
  • Both outcomes return result set to the item id.

Publish state, route path and template

GoalRequest
Publish a draftPUT /{type}/{id} with saveAsDraft: false and the content to publish. There is no separate publish endpoint.
UnpublishPUT /{type}/{id}/unpublish, no body
Discard unpublished editsPUT /{type}/{id}/discard-draft, no body
Change route path or templatePUT /{type}/{id}/settings with {"routePath": "blog/hello", "templateId": "…"}. Both fields are required.
Make it the home pagePUT /{type}/{id}/set-as-home-page, no body
Move many items to one templatePOST /{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

GoalRequestNeeds
Move to the trashDELETE /{type}/{id}edit
Trash severalDELETE /{type}/items with {"ids": ["…", "…"]}edit
List the trashGET /{type}/trashconfig
RestorePUT /{type}/{id}/restore, using the id from the trash listconfig
Delete for goodDELETE /{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 publishedContent until you save with saveAsDraft: 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 filter until it is published.
  • Creating and editing raise content events. Active content event functions and webhooks run for items you write through the API.

Next steps

  • Filtering: the filter and order syntax.
  • Media: upload a file and attach it to an item.
  • Errors: what the failure responses look like.
  • Recipes: pagination, upserts and static builds.