Skip to the content
Menu: Subjects

Subjects

A portal's subjects and their content.

GET/v1/subjects/{id}

Get a subject

getSubjectcontent:readread: 600/min per key

One subject: its template and kind, its live version and pending draft, its languages and the portals it is on.

Path parameters

  • idstringrequired

    Subject id (sub_…); the id without its prefix is accepted too.

Returns

200 The subject. data is Subject.

Fields of data
  • idstringrequired

    Subject id (sub_…).

  • namestringrequired
  • templatestringrequired

    The template it was built from (custom for one written for the organisation).

  • kind"tutor" | "roleplay" | "assessor" | "service"required
  • group"learn" | "train" | "serve" | "assess"required
  • screeningbooleanrequired

    A candidate screening role (its invites and candidates are under /roles).

  • status"draft" | "published" | "archived"required

    draft until it (or its portal) is published.

  • liveVersionintegerrequired

    The version sessions run; 0 when there is none.

  • draftVersioninteger | nullrequired

    A pending draft to publish; null when there is none.

  • languagesstring[]required

    Language codes (GET /languages).

  • visitorLanguage"follow" | "listed" | "english" | nullrequired

    A website subject: whether it follows the visitor's language, keeps to languages, or speaks English. Null for any other.

  • unitIdstring | nullrequired
  • portalIdsstring[]required
  • createdAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • updatedAtstring (date-time)required

    ISO 8601 UTC timestamp.

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error

Request

curl "https://app.zentiqai.com/v1/subjects/sub_123" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY"
Example answer
Response
{
  "data": {
    "id": "sub_cmfq2b7x10000abcd1234",
    "name": "Zoe",
    "template": "…",
    "kind": "tutor",
    "group": "learn",
    "screening": true
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
PATCH/v1/subjects/{id}

Change a subject

updateSubjectcontent:writewrite: 120/min per key

Its name, its unit (unitId; null: the organisation level), and for a website subject the language rule for its visitors (visitorLanguage: follow the visitor's language, keep to its languages (listed), or english; null goes back to the organisation's own setting). Only the fields sent change, and what sessions run changes at once. visitorLanguage on any other subject is 409 conflict (details.reason: "not_a_website_subject"), and so is one the organisation has locked ("locked"). A website widget's subject changes with its avatar (PUT /personas/{id}/widget: 409 conflict, "widget").

Path parameters

  • idstringrequired

    Subject id (sub_…); the id without its prefix is accepted too.

Body

  • namestring

    1-80 characters

  • unitIdstring | null

    The unit it belongs to; null: the organisation level.

  • visitorLanguage"follow" | "listed" | "english" | null

    A website subject only: follow the visitor's language, keep to its languages (listed), or English only; null goes back to the organisation's own setting.

Returns

200 The subject as it now stands. data is Subject.

Fields of data
  • idstringrequired

    Subject id (sub_…).

  • namestringrequired
  • templatestringrequired

    The template it was built from (custom for one written for the organisation).

  • kind"tutor" | "roleplay" | "assessor" | "service"required
  • group"learn" | "train" | "serve" | "assess"required
  • screeningbooleanrequired

    A candidate screening role (its invites and candidates are under /roles).

  • status"draft" | "published" | "archived"required

    draft until it (or its portal) is published.

  • liveVersionintegerrequired

    The version sessions run; 0 when there is none.

  • draftVersioninteger | nullrequired

    A pending draft to publish; null when there is none.

  • languagesstring[]required

    Language codes (GET /languages).

  • visitorLanguage"follow" | "listed" | "english" | nullrequired

    A website subject: whether it follows the visitor's language, keeps to languages, or speaks English. Null for any other.

  • unitIdstring | nullrequired
  • portalIdsstring[]required
  • createdAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • updatedAtstring (date-time)required

    ISO 8601 UTC timestamp.

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundconflictrate_limitedserver_error

Events

setting.changed

Request

curl -X PATCH "https://app.zentiqai.com/v1/subjects/sub_123" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Example answer
Response
{
  "data": {
    "id": "sub_cmfq2b7x10000abcd1234",
    "name": "Zoe",
    "template": "…",
    "kind": "tutor",
    "group": "learn",
    "screening": true
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
GET/v1/subjects/{id}/content

Read a subject's content

getSubjectContentcontent:readread: 600/min per key

The content document of one version: facts, FAQs, opening hours, scenarios, questions, the rubric, a screening role... one section per thing a business edits, in camelCase. By default the pending draft, or the live version when there is none (what your next change builds on). The ETag is "v<version>": send it back as If-Match (or baseVersion) with your change.

Path parameters

  • idstringrequired

    Subject id (sub_…); the id without its prefix is accepted too.

Query parameters

  • version"live" | "draft" | string

    draft (the default: the pending draft, or the live version when there is none: what your next change builds on), live, or a version number.

Returns

200 The content. data is SubjectContentVersion.

Fields of data
  • versionintegerrequired

    The version shown; its ETag is "v<version>".

  • livebooleanrequired

    It is what sessions run.

  • basedOninteger | nullrequired

    The version it was saved over; null for the first.

  • contentSubjectContentrequired

    A subject's content, one section per thing a business edits. Sections that don't apply to the subject's kind are left out; a section that applies but isn't set is null (objects) or [] (lists).

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error

Request

curl "https://app.zentiqai.com/v1/subjects/sub_123/content" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY"
Example answer
Response
{
  "data": {
    "version": 1,
    "live": true,
    "basedOn": 1,
    "content": {
      "name": "Zoe",
      "audience": "…",
      "languages": [
        "en"
      ],
      "gradeBand": [],
      "facts": [
        {
          "key": "…",
          "label": "…",
          "value": "…",
          "help": "…",
          "keep": true,
          "perPerson": true
        }
      ],
      "topics": [
        {
          "id": "…",
          "title": "…",
          "description": "…",
          "grade": [],
          "skills": [
            "…"
          ],
          "prerequisites": [
            "…"
          ]
        }
      ]
    }
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
PATCH/v1/subjects/{id}/content

Change a subject's content (as a draft)

updateSubjectContentcontent:writewrite: 120/min per key

Saves the sections in set as a new draft version; what sessions run changes only when it is published (POST /subjects/{id}/publish). A list section replaces the whole list (an item without an id gets one); an object section changes only the fields sent (null clears one); facts also takes { key: value }. facts.assistant_name is always the name of the avatar that speaks, set on each portal with PUT /portals/{id}/persona: a changed value is 400 invalid_request (param: body.set.facts.assistant_name). A document read with GET can be sent back as it is. The content's own rules are checked (400 invalid_request, details.errors: [{ path, message }] in the document's paths), and the plan's features (403 upgrade_required). With If-Match or baseVersion, a newer version is 409 version_conflict (details.currentVersion); without, the change applies to the newest. The answer says whether the draft could go live (goLive). Sending the newest content again stores nothing.

Path parameters

  • idstringrequired

    Subject id (sub_…); the id without its prefix is accepted too.

Body

  • The sections to change. A list replaces the whole list; an object section changes only the fields sent (null clears an optional one); facts also takes { key: value }. A document read with GET can be sent back as it is.

  • notestring

    Kept with the version.

    at most 200 characters

  • baseVersioninteger

    The version your change is based on (or send If-Match: "v<n>"); a newer one is 409 version_conflict.

    at least 1

Returns

200 The draft. data is SubjectContentDraft.

Fields of data
  • versionintegerrequired

    The version shown; its ETag is "v<version>".

  • livebooleanrequired

    It is what sessions run.

  • basedOninteger | nullrequired

    The version it was saved over; null for the first.

  • contentSubjectContentrequired

    A subject's content, one section per thing a business edits. Sections that don't apply to the subject's kind are left out; a section that applies but isn't set is null (objects) or [] (lists).

  • goLiveobjectrequired

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundversion_conflictrate_limitedserver_error

Events

subject.draft_saved

Request

curl -X PATCH "https://app.zentiqai.com/v1/subjects/sub_123/content" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "set": {}
}'
Example answer
Response
{
  "data": {
    "version": 1,
    "live": true,
    "basedOn": 1,
    "content": {
      "name": "Zoe",
      "audience": "…",
      "languages": [
        "en"
      ],
      "gradeBand": [],
      "facts": [
        {
          "key": "…",
          "label": "…",
          "value": "…",
          "help": "…",
          "keep": true,
          "perPerson": true
        }
      ],
      "topics": [
        {
          "id": "…",
          "title": "…",
          "description": "…",
          "grade": [],
          "skills": [
            "…"
          ],
          "prerequisites": [
            "…"
          ]
        }
      ]
    },
    "goLive": {
      "ready": true,
      "problems": [
        {
          "field": "hours",
          "message": "…",
          "short": "…"
        }
      ],
      "warnings": [
        {
          "field": "hours",
          "message": "…",
          "short": "…"
        }
      ]
    }
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
GET/v1/subjects/{id}/go-live

Check a subject can go live

getSubjectGoLivecontent:readread: 600/min per key

Runs the go-live check on a version (by default the pending draft, or the live version): what to finish first, in the content document's fields. Changes nothing.

Path parameters

  • idstringrequired

    Subject id (sub_…); the id without its prefix is accepted too.

Query parameters

  • version"live" | "draft" | string

    draft (the default: the pending draft, or the live version when there is none: what your next change builds on), live, or a version number.

Returns

200 Whether it can go live. data is SubjectGoLive.

Fields of data
  • versionintegerrequired

    The version checked.

  • readybooleanrequired
  • problemsGoLiveProblem[]required
  • warningsGoLiveProblem[]required

    Worth fixing; never a refusal.

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error

Request

curl "https://app.zentiqai.com/v1/subjects/sub_123/go-live" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY"
Example answer
Response
{
  "data": {
    "version": 1,
    "ready": true,
    "problems": [
      {
        "field": "hours",
        "message": "…",
        "short": "…"
      }
    ],
    "warnings": [
      {
        "field": "hours",
        "message": "…",
        "short": "…"
      }
    ]
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
GET/v1/subjects/{id}/versions

List a subject's versions

listSubjectVersionscontent:readread: 600/min per key

Every stored version of the subject's content, newest first: which is live, which is the pending draft, and who saved each.

Path parameters

  • idstringrequired

    Subject id (sub_…); the id without its prefix is accepted too.

Query parameters

  • limitinteger

    Page size, 1-200 (default 50).

  • cursorstring

    The nextCursor of the previous page.

Returns

200 A page of versions. A page: data is a list of SubjectVersion, and nextCursor fetches the next page.

Fields of data
  • versionintegerrequired
  • livebooleanrequired
  • draftbooleanrequired

    The pending draft.

  • notestring | nullrequired
  • createdAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • createdByobjectrequired
    • type"user" | "api_key" | "system"required
    • idstring | nullrequired

      usr_… or key_…; null for the system.

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundrate_limitedserver_error

Request

curl "https://app.zentiqai.com/v1/subjects/sub_123/versions?limit=20" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY"
Example answer
Response
{
  "data": [
    {
      "version": 1,
      "live": true,
      "draft": true,
      "note": "…",
      "createdAt": "2026-09-25T12:00:00.000Z",
      "createdBy": {
        "type": "user",
        "id": "…"
      }
    }
  ],
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d",
  "nextCursor": null
}
POST/v1/subjects/{id}/publish

Publish a subject's content

publishSubjectportals:publishwrite: 120/min per keyIdempotency-Key

Makes a version (by default the pending draft) what sessions run, and publishes the subject. It must pass the go-live check (422 go_live_refused, details.problems), the plan's features (403 upgrade_required) and, for a screening role that keeps candidates, the round lock (409 conflict, details.reason: "round_locked": send newRound: true to start a new round). An older version is brought back as the newest. Already live and published: returned unchanged.

Path parameters

  • idstringrequired

    Subject id (sub_…); the id without its prefix is accepted too.

Body (optional)

  • versioninteger

    The version to publish (default: the newest, the pending draft). An older one is brought back as the newest.

    at least 1

  • newRoundboolean

    A screening role that keeps candidates: changes to how candidates compare start a new round (otherwise 409 conflict, reason round_locked).

Returns

200 The subject as it now stands. data is Subject.

Fields of data
  • idstringrequired

    Subject id (sub_…).

  • namestringrequired
  • templatestringrequired

    The template it was built from (custom for one written for the organisation).

  • kind"tutor" | "roleplay" | "assessor" | "service"required
  • group"learn" | "train" | "serve" | "assess"required
  • screeningbooleanrequired

    A candidate screening role (its invites and candidates are under /roles).

  • status"draft" | "published" | "archived"required

    draft until it (or its portal) is published.

  • liveVersionintegerrequired

    The version sessions run; 0 when there is none.

  • draftVersioninteger | nullrequired

    A pending draft to publish; null when there is none.

  • languagesstring[]required

    Language codes (GET /languages).

  • visitorLanguage"follow" | "listed" | "english" | nullrequired

    A website subject: whether it follows the visitor's language, keeps to languages, or speaks English. Null for any other.

  • unitIdstring | nullrequired
  • portalIdsstring[]required
  • createdAtstring (date-time)required

    ISO 8601 UTC timestamp.

  • updatedAtstring (date-time)required

    ISO 8601 UTC timestamp.

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundconflictidempotency_conflictidempotency_in_progressgo_live_refusedrate_limitedserver_error

Events

workspace.published

Request

curl -X POST "https://app.zentiqai.com/v1/subjects/sub_123/publish" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
Example answer
Response
{
  "data": {
    "id": "sub_cmfq2b7x10000abcd1234",
    "name": "Zoe",
    "template": "…",
    "kind": "tutor",
    "group": "learn",
    "screening": true
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}
POST/v1/subjects/{id}/translate-questions

Translate a subject's questions (as a draft)

translateSubjectQuestionscontent:writewrite: 120/min per key

Translates what is said of each question (the question, its options, follow-ups and complication) into language and saves it as a new draft, for you to check and publish (POST /subjects/{id}/publish); the guides for marking stay as written. A screening interview, an exam or an assessment asks each question word for word, so everyone is asked the same: translate once, here, rather than in each session. All or nothing: when a question can't be translated well enough (its numbers, its options and follow-ups kept), nothing is saved (503, try again). An unknown language, English, or a subject without questions is 400 invalid_request.

Path parameters

  • idstringrequired

    Subject id (sub_…); the id without its prefix is accepted too.

Body

  • languagestringrequired

    A name or code from GET /languages ("German", "es"): the portal's language.

    1-80 characters

Returns

200 The draft with the translated questions. data is SubjectQuestionsTranslated.

Fields of data
  • versionintegerrequired

    The draft that holds the translated questions.

  • languagestringrequired

    The language's name.

  • translatedintegerrequired

    How many questions were translated.

Errors

invalid_requestunauthorizedforbiddenupgrade_requirednot_foundversion_conflictrate_limitedserver_error

Events

subject.draft_saved

Request

curl -X POST "https://app.zentiqai.com/v1/subjects/sub_123/translate-questions" \
  -H "Authorization: Bearer $ZENTIQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "language": "en"
}'
Example answer
Response
{
  "data": {
    "version": 1,
    "language": "en",
    "translated": 1
  },
  "error": null,
  "requestId": "req_4f1c2e9a7b3d5e6f7a8b9c0d"
}