Amba

Content

Content libraries, items, bulk import, and scheduled delivery via Temporal Schedules.

Content is organized as libraries (groupings) containing items (the actual content rows). Schedules run on Temporal Schedules — one schedule row owns exactly one Temporal Schedule under the deterministic id content-schedule-<row-id>. The CONTENT_DELIVERY workflow picks up the next item on each tick.

Source: apps/api/src/routes/admin/content.ts.

Endpoints

MethodPathDescription
POST/admin/projects/:projectId/content/librariesCreate a library.
GET/admin/projects/:projectId/content/librariesList libraries with item count.
POST/admin/projects/:projectId/content/libraries/:libraryId/itemsInsert items (serialized via advisory lock for stable sort_order).
GET/admin/projects/:projectId/content/libraries/:libraryId/itemsPaginated item list.
PATCH/admin/projects/:projectId/content/items/:itemIdPartial update of a single item.
DELETE/admin/projects/:projectId/content/items/:itemIdHard-delete an item.
POST/admin/projects/:projectId/content/libraries/:libraryId/bulkBulk import (id-only response).
POST/admin/projects/:projectId/content/schedulesCreate a schedule (validates cron first, then creates Temporal schedule).
GET/admin/projects/:projectId/content/schedulesList schedules with library name.
PATCH/admin/projects/:projectId/content/schedules/:scheduleIdUpdate a schedule (serialized under row lock; Temporal-rollback-aware).
DELETE/admin/projects/:projectId/content/schedules/:scheduleIdDelete a schedule + its Temporal handle.

POST /admin/projects/:projectId/content/libraries

Request

FieldTypeRequired
namestringyes
descriptionstringno

Response 201

{
  "data": { "id": "…", "name": "…", "description": null, "content_schema": null, "created_at": "…" }
}

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/content/libraries
developer auth
curl -X POST 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/libraries'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/content/libraries' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

GET /admin/projects/:projectId/content/libraries

Response 200

{ "data": [{ "id": "…", "name": "…", "item_count": 42 }] }

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D/content/libraries
developer auth
curl -X GET 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/libraries'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}/content/libraries' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

POST /admin/projects/:projectId/content/libraries/:libraryId/items

Insert one or more items. sort_order is computed from MAX+1 inside a per-library advisory lock so concurrent POSTs never collide.

Request

{ "items": [{ "body": "…", "title": null, "tags": [], "metadata": {}, "is_premium": false }] }

Per-item fields (from CreateContentItemInput):

FieldTypeRequired
titlestringno
bodystringyes
media_urlstringno
categorystringno
tagsstring[]no
metadataobjectno
is_premiumbooleanno

Response 201

{
  "data": [
    { "id": "…", "library_id": "…", "title": "…", "body": "…", "sort_order": 0, "is_active": true }
  ],
  "total": 1
}

Errors

  • 500 CREATE_FAILED.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/content/libraries/%7B%7BlibraryId%7D%7D/items
developer auth
curl -X POST 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/libraries/%7B%7BlibraryId%7D%7D/items'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/content/libraries/{libraryId}/items' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

GET /admin/projects/:projectId/content/libraries/:libraryId/items

Paginated, is_active = true only.

Query

ParamDefault
limit50
offset0

Response 200

{ "data": [  ], "total": 123, "offset": 0, "limit": 50 }

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D/content/libraries/%7B%7BlibraryId%7D%7D/items
developer auth
curl -X GET 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/libraries/%7B%7BlibraryId%7D%7D/items'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}/content/libraries/{libraryId}/items' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

PATCH /admin/projects/:projectId/content/items/:itemId

Partial update. Allowed fields: title, body, media_url, category, tags, metadata, is_premium, is_active, sort_order.

Response 200

Updated row.

Errors

  • 404 NOT_FOUND.
  • 500 UPDATE_FAILED.

Try it:

PATCH/admin/projects/%7B%7BprojectId%7D%7D/content/items/%7B%7BitemId%7D%7D
developer auth
curl -X PATCH 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/items/%7B%7BitemId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X PATCH '${BASE_URL}/admin/projects/{projectId}/content/items/{itemId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

DELETE /admin/projects/:projectId/content/items/:itemId

Response 200

{ "data": { "deleted": true } }

Try it:

DELETE/admin/projects/%7B%7BprojectId%7D%7D/content/items/%7B%7BitemId%7D%7D
developer auth
curl -X DELETE 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/items/%7B%7BitemId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X DELETE '${BASE_URL}/admin/projects/{projectId}/content/items/{itemId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

POST /admin/projects/:projectId/content/libraries/:libraryId/bulk

Same sort-order behavior as the items POST, but returns only the count.

Response 201

{ "data": { "imported": 123 } }

Errors

  • 500 BULK_FAILED.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/content/libraries/%7B%7BlibraryId%7D%7D/bulk
developer auth
curl -X POST 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/libraries/%7B%7BlibraryId%7D%7D/bulk'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/content/libraries/{libraryId}/bulk' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

POST /admin/projects/:projectId/content/schedules

Create a schedule and its backing Temporal Schedule in a single tx. A cron field in config always wins; otherwise daily_rotation defaults to 0 9 * * *, weekly defaults to 0 9 * * MON, and random / sequential require an explicit cron.

Request

FieldTypeRequiredDescription
library_iduuidyesSource library.
namestringyesDisplay name.
schedule_type"daily_rotation" | "weekly" | "random" | "sequential"yes
config.cronstringconditionalOverrides defaults; required for random/sequential.
config.timezonestringnoIANA zone; defaults to "UTC".

Response 201

{
  "data": {
    "id": "…",
    "library_id": "…",
    "name": "…",
    "schedule_type": "daily_rotation",
    "config": { "cron": "0 9 * * *", "timezone": "UTC" },
    "is_active": true,
    "config_key": "content_<uuid>",
    "temporal_schedule_id": "content-schedule-…"
  }
}

Errors

  • 400 INVALID_SCHEDULE_CONFIG — bad cron or missing required cron.
  • 500 CREATE_FAILED.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/content/schedules
developer auth
curl -X POST 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/schedules'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/content/schedules' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

GET /admin/projects/:projectId/content/schedules

Response 200

{
  "data": [
    {
      "id": "…",
      "library_name": "…",
      "schedule_type": "daily_rotation",
      "config": {},
      "is_active": true,
      "config_key": "content_…",
      "temporal_schedule_id": "…"
    }
  ]
}

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D/content/schedules
developer auth
curl -X GET 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/schedules'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}/content/schedules' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

PATCH /admin/projects/:projectId/content/schedules/:scheduleId

Update name, schedule_type, config, and/or is_active. Row is locked FOR UPDATE so concurrent PATCHes serialize; Temporal mutations are compensated if the DB commit later fails.

Request

FieldType
namestring
schedule_typestring
configobject
is_activeboolean

Response 200

Updated row.

Errors

  • 400 INVALID_SCHEDULE_CONFIG — proposed cron invalid.
  • 404 NOT_FOUND.
  • 502 SCHEDULE_UPDATE_FAILED — Temporal refused the mutation; DB rolled back.
  • 500 UPDATE_FAILED.

Try it:

PATCH/admin/projects/%7B%7BprojectId%7D%7D/content/schedules/%7B%7BscheduleId%7D%7D
developer auth
curl -X PATCH 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/schedules/%7B%7BscheduleId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X PATCH '${BASE_URL}/admin/projects/{projectId}/content/schedules/{scheduleId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

DELETE /admin/projects/:projectId/content/schedules/:scheduleId

Delete the Temporal handle first, then the row. Wrapped in a tx with FOR UPDATE so concurrent PATCHes can't recreate a Temporal schedule mid-delete.

Response 200

{ "data": { "deleted": true } }

Errors

  • 404 NOT_FOUND.
  • 502 SCHEDULE_DELETE_FAILED — Temporal teardown failed; row still exists.
  • 500 DELETE_FAILED.

Try it:

DELETE/admin/projects/%7B%7BprojectId%7D%7D/content/schedules/%7B%7BscheduleId%7D%7D
developer auth
curl -X DELETE 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/content/schedules/%7B%7BscheduleId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X DELETE '${BASE_URL}/admin/projects/{projectId}/content/schedules/{scheduleId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'