{
  "components": {
    "schemas": {
      "ActivityCompletionDto": {
        "description": "A record that a user completed an activity. Completions are append-only: completing\nagain adds a new record. \u0060activityVersionId\u0060 is the activity version completed.\n\u0060placementId\u0060 is the activity placement it was completed through, or \u0060null\u0060\nfor a standalone completion; \u0060enrolmentId\u0060 is set when a placed completion matched\nexactly one active enrolment, else \u0060null\u0060. \u0060recordedBy\u0060 is \u0060null\u0060 when\nthe user recorded it themselves, and \u0060triggeredByRecordId\u0060 names the learning\nrecord that produced it, if any. \u0060responses\u0060 is returned by\n\u0060GET /v1/activity-completions/{id}\u0060 and is \u0060null\u0060 in lists.",
        "example": {
          "id": "019cbe57-5380-704a-9f16-595215e71aa0",
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "activityVersionId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
          "placementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
          "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
          "score": 0.8,
          "completedAt": "2026-03-05T14:12:00\u002B00:00",
          "createdAt": "2026-03-05T14:12:00\u002B00:00",
          "recordedBy": null,
          "triggeredByRecordId": null,
          "responses": [
            {
              "id": "019cbe57-5380-7068-9012-eb773f7b168b",
              "questionPlacementId": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
              "questionVersionId": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
              "isCorrect": true,
              "score": 1,
              "response": [
                "019bb1c1-6440-7eb0-ba7d-398bbc7e264c"
              ],
              "createdAt": "2026-03-05T14:12:00\u002B00:00"
            }
          ],
          "_links": {
            "self": "/v1/activity-completions/019cbe57-5380-704a-9f16-595215e71aa0",
            "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "placement": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "activityVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "completedAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "enrolmentId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "placementId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "recordedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "responses": {
            "items": {
              "$ref": "#/components/schemas/QuestionResponseDto"
            },
            "type": [
              "null",
              "array"
            ]
          },
          "score": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "triggeredByRecordId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "userId",
          "activityVersionId",
          "placementId",
          "enrolmentId",
          "score",
          "completedAt",
          "createdAt",
          "recordedBy",
          "triggeredByRecordId",
          "responses",
          "_links"
        ],
        "type": "object"
      },
      "ActivityDto": {
        "description": "An activity: a reusable piece of learning, such as a quiz or a video, whose kind is\nnamed by \u0060componentUri\u0060. \u0060definition\u0060 is the activity\u0027s body and\n\u0060presentation\u0060 the display settings around it; both are returned as the JSON\nobjects that were stored, and the API never interprets them. \u0060status\u0060 is read-only\nhere and changes only through the \u0060:publish\u0060, \u0060:unpublish\u0060 and \u0060:archive\u0060\nactions. \u0060familyId\u0060 is shared by every version of the activity, while \u0060id\u0060\nidentifies this version. \u0060managedTags\u0060 is always empty and \u0060customFields\u0060\nalways \u0060{}\u0060 for now.",
        "example": {
          "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
          "title": "Ladder safety check",
          "description": "Five questions on inspecting a ladder before use.",
          "componentUri": "https://serenapp.io/activities/quiz",
          "producesScore": true,
          "carriesQuestions": true,
          "status": "published",
          "definition": {},
          "customFields": {},
          "presentation": {},
          "managedTags": [],
          "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
          "versionNumber": 1,
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "list": "/v1/activities",
            "update": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "delete": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "latestPublished": "/v1/activities/families/019bb1c1-6440-7a1b-89fb-2ecc8c0ad419/latest-published",
            "unpublish": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:unpublish",
            "archive": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:archive"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "carriesQuestions": {
            "type": "boolean"
          },
          "componentUri": {
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "definition": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "managedTags": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "type": "array"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "producesScore": {
            "type": "boolean"
          },
          "status": {
            "$ref": "#/components/schemas/ContentStatus"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "componentUri",
          "producesScore",
          "carriesQuestions",
          "status",
          "definition",
          "customFields",
          "presentation",
          "managedTags",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "ActivityMergePatch": {
        "description": "The body of \u0060PATCH /v1/activities/{id}\u0060, a JSON Merge Patch (RFC 7396): a field\nleft out is unchanged, a field sent is set, and \u0060null\u0060 clears a field that may be\nempty. \u0060componentUri\u0060 cannot be changed, and sending it is rejected with 422.\n\u0060title\u0060, \u0060producesScore\u0060, \u0060carriesQuestions\u0060 and \u0060definition\u0060 cannot\nbe set to \u0060null\u0060, and \u0060null\u0060 for \u0060presentation\u0060 resets it to \u0060{}\u0060.\n\u0060carriesQuestions\u0060 cannot be set to \u0060false\u0060 while questions are placed in the\nactivity. \u0060status\u0060 is not a field here; it changes only through the \u0060:publish\u0060,\n\u0060:unpublish\u0060 and \u0060:archive\u0060 actions.",
        "example": {
          "description": "Six questions on inspecting a ladder before use."
        },
        "properties": {
          "carriesQuestions": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "componentUri": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "definition": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "producesScore": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "title": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "ActivityPlacementDto": {
        "description": "An activity placed in a lesson. \u0060activityId\u0060 names the specific activity version\nplaced, and \u0060order\u0060 is its position among the lesson\u0027s placements, set by the\nserver: new placements are appended, and \u0060:reorder\u0060 moves them. \u0060required\u0060 is\n\u0060true\u0060 unless the activity is optional practice that does not count towards\ncompletion. \u0060presentation\u0060 holds display settings for this placement only;\n\u0060customFields\u0060 is always \u0060{}\u0060 for now.",
        "example": {
          "id": "019c2416-cd00-7f88-9800-5cdd08a0d146",
          "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
          "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
          "order": 1,
          "required": true,
          "customFields": {},
          "presentation": {},
          "familyId": "019c2416-cd00-7453-9c0b-92ffc149261b",
          "versionNumber": 1,
          "createdAt": "2026-02-03T15:20:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
            "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "update": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
            "delete": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
            "completions": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions",
            "submitCompletion": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "activityId": {
            "format": "uuid",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lessonId": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "required": {
            "type": "boolean"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "lessonId",
          "activityId",
          "order",
          "required",
          "customFields",
          "presentation",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "ActivityPlacementMergePatch": {
        "description": "The body of \u0060PATCH /v1/activity-placements/{id}\u0060, a JSON Merge Patch (RFC 7396): a\nfield left out is unchanged and a field sent is set. \u0060null\u0060 for \u0060presentation\u0060\nresets it to \u0060{}\u0060, and \u0060customFields\u0060 accepts only an empty object for now.\nThe position is not a field here; use \u0060:reorder\u0060 on the lesson\u0027s placements.",
        "example": {
          "required": false
        },
        "properties": {
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "required": {
            "type": [
              "null",
              "boolean"
            ]
          }
        },
        "type": "object"
      },
      "ActivityProgressDto": {
        "description": "A user\u0027s completion state for one placed activity, included in a structure or outline\nread that names a user. \u0060completed\u0060 says whether the user has completed the\nactivity in this placement at all. \u0060completionId\u0060, \u0060score\u0060 and\n\u0060completedAt\u0060 come from the latest attempt, not the best one, and are \u0060null\u0060\nwhen there is none. Follow \u0060completionId\u0060 to\n\u0060GET /v1/activity-completions/{id}\u0060 for the saved question responses.",
        "example": {
          "completed": true,
          "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
          "score": 0.8,
          "completedAt": "2026-03-05T14:12:00\u002B00:00"
        },
        "properties": {
          "completed": {
            "type": "boolean"
          },
          "completedAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "completionId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "score": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          }
        },
        "required": [
          "completed",
          "completionId",
          "score",
          "completedAt"
        ],
        "type": "object"
      },
      "AgentAuthorityDto": {
        "description": "The current authority of an agent\u0027s standing delegation, returned by\n\u0060POST /v1/agents/authority:read\u0060 to a system caller that issues agent\ntokens. \u0060userId\u0060 is the id of the user the agent acts for,\n\u0060permissions\u0060 the permissions the agent may use right now (sorted), and\n\u0060expiresAt\u0060 when the delegation lapses, which caps the lifetime of any\ntoken issued from it. It carries no \u0060_links\u0060.",
        "example": {
          "userId": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "agentId": "019c2416-cd00-7767-a377-bd13a3d1cedb",
          "delegationId": "019c2416-cd00-7631-a7a9-795318683c22",
          "expiresAt": "2026-03-05T15:20:00Z",
          "permissions": [
            "content:read",
            "content:write"
          ]
        },
        "properties": {
          "agentId": {
            "format": "uuid",
            "type": "string"
          },
          "delegationId": {
            "format": "uuid",
            "type": "string"
          },
          "expiresAt": {
            "format": "date-time",
            "type": "string"
          },
          "permissions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "userId",
          "agentId",
          "delegationId",
          "expiresAt",
          "permissions"
        ],
        "type": "object"
      },
      "AgentDelegationDto": {
        "example": {
          "id": "019c2416-cd00-7631-a7a9-795318683c22",
          "agentId": "019c2416-cd00-7767-a377-bd13a3d1cedb",
          "mode": "delegated",
          "scope": [
            "content:read",
            "content:write"
          ],
          "draftOnly": true,
          "grantedByActorId": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "authorisedByActorId": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "grantedAt": "2026-02-03T15:20:00\u002B00:00",
          "expiresAt": "2026-03-05T15:20:00\u002B00:00",
          "reconfirmationDueAt": "2026-02-10T15:20:00\u002B00:00",
          "status": "active",
          "createdAt": "2026-02-03T15:20:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/agents/019c2416-cd00-7767-a377-bd13a3d1cedb/delegations/019c2416-cd00-7631-a7a9-795318683c22",
            "revoke": "/v1/agents/019c2416-cd00-7767-a377-bd13a3d1cedb/delegations/019c2416-cd00-7631-a7a9-795318683c22:revoke"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "agentId": {
            "format": "uuid",
            "type": "string"
          },
          "authorisedByActorId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "draftOnly": {
            "type": "boolean"
          },
          "expiresAt": {
            "format": "date-time",
            "type": "string"
          },
          "grantedAt": {
            "format": "date-time",
            "type": "string"
          },
          "grantedByActorId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "mode": {
            "type": "string"
          },
          "reconfirmationDueAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "scope": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "status": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "id",
          "agentId",
          "mode",
          "scope",
          "draftOnly",
          "grantedByActorId",
          "authorisedByActorId",
          "grantedAt",
          "expiresAt",
          "reconfirmationDueAt",
          "status",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "AgentDto": {
        "example": {
          "id": "019c2416-cd00-7767-a377-bd13a3d1cedb",
          "name": "Course drafting assistant",
          "ownerActorId": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "ownership": "external",
          "mode": "delegated",
          "status": "active",
          "createdAt": "2026-02-03T15:20:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/agents/019c2416-cd00-7767-a377-bd13a3d1cedb",
            "delegations": "/v1/agents/019c2416-cd00-7767-a377-bd13a3d1cedb/delegations"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "mode": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "ownerActorId": {
            "format": "uuid",
            "type": "string"
          },
          "ownership": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "ownerActorId",
          "ownership",
          "mode",
          "status",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "AmendPermissionsBody": {
        "description": "The body of \u0060PATCH /v1/users/{id}/permissions\u0060: permissions to grant in \u0060add\u0060\nand to revoke in \u0060remove\u0060. Either may be left out, so a call may grant only, revoke\nonly, or both, but together they must name at least one permission; permissions not\nnamed are unchanged. Each list may hold no more entries than there are assignable\npermissions.",
        "example": {
          "add": [
            "enrolments:write"
          ],
          "remove": [
            "progress:write"
          ]
        },
        "properties": {
          "add": {
            "items": {
              "type": "string"
            },
            "type": [
              "null",
              "array"
            ]
          },
          "remove": {
            "items": {
              "type": "string"
            },
            "type": [
              "null",
              "array"
            ]
          }
        },
        "required": [
          "add",
          "remove"
        ],
        "type": "object"
      },
      "BulkEnrolItem": {
        "description": "One pair in a bulk enrolment: \u0060userId\u0060 names the learner and \u0060courseId\u0060 the\ncourse, both required. \u0060deadline\u0060 and \u0060isMandatory\u0060 optionally set the\nenrolment\u0027s compliance attributes.",
        "example": {
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "deadline": "2026-03-31T17:00:00\u002B00:00",
          "isMandatory": true
        },
        "properties": {
          "courseId": {
            "type": [
              "null",
              "string"
            ]
          },
          "deadline": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "isMandatory": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "userId": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "userId",
          "courseId"
        ],
        "type": "object"
      },
      "BulkEnrolItemResult": {
        "description": "The outcome of one bulk-enrolment item, in request order: \u0060enrolment\u0060 is the\nenrolment, and \u0060created\u0060 is \u0060true\u0060 when this item created it or \u0060false\u0060\nwhen it returned the user\u0027s existing active enrolment.",
        "example": {
          "enrolment": {
            "id": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
            "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
            "courseFamilyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
            "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "deadline": "2026-03-31T17:00:00\u002B00:00",
            "isMandatory": true,
            "method": "admin",
            "deletedAt": null,
            "createdAt": "2026-02-16T09:00:00\u002B00:00",
            "updatedAt": "2026-02-16T09:00:00\u002B00:00",
            "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "_links": {
              "self": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
              "completions": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/completions",
              "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
              "update": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
              "withdraw": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
            }
          },
          "created": true
        },
        "properties": {
          "created": {
            "type": "boolean"
          },
          "enrolment": {
            "$ref": "#/components/schemas/EnrolmentDto"
          }
        },
        "required": [
          "enrolment",
          "created"
        ],
        "type": "object"
      },
      "BulkEnrolRequest": {
        "description": "The body of \u0060POST /v1/enrolments:bulk\u0060: between 1 and 200 \u0060items\u0060, each\nenrolling one user on one course. It needs \u0060enrolments:write:tenant\u0060. The batch is\nall or nothing: if any item is invalid, nothing is enrolled and the 422 names each\nfailing item by its position (for example \u0060items[3].courseId\u0060). A batch of more\nthan 200 is refused with that one error alone, and no item is checked. An item whose user\nalready holds an active enrolment on the course succeeds and returns that enrolment.",
        "example": {
          "items": [
            {
              "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "deadline": "2026-03-31T17:00:00\u002B00:00",
              "isMandatory": true
            }
          ]
        },
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/BulkEnrolItem"
            },
            "type": [
              "null",
              "array"
            ]
          }
        },
        "required": [
          "items"
        ],
        "type": "object"
      },
      "CapabilitiesDto": {
        "example": {
          "operations": [
            {
              "operationId": "CreateCourse",
              "variant": null,
              "state": "available",
              "reach": [],
              "excludesSelf": false
            },
            {
              "operationId": "GetCapabilities",
              "variant": null,
              "state": "available",
              "reach": [],
              "excludesSelf": false
            },
            {
              "operationId": "ListEnrolments",
              "variant": null,
              "state": "conditional",
              "reach": [
                "tenant",
                "self"
              ],
              "excludesSelf": false
            },
            {
              "operationId": "SuspendUserMembership",
              "variant": null,
              "state": "unavailable",
              "reach": [],
              "excludesSelf": false
            }
          ]
        },
        "properties": {
          "operations": {
            "items": {
              "$ref": "#/components/schemas/CapabilityOperationDto"
            },
            "type": "array"
          }
        },
        "required": [
          "operations"
        ],
        "type": "object"
      },
      "CapabilityOperationDto": {
        "description": "One operation the caller\u0027s token is eligible to call, as far as its permissions go. It\nis not a promise that a particular request will succeed: the resource\u0027s state and the\ncaller\u0027s reach are still checked when the request is made.",
        "example": {
          "operationId": "ListEnrolments",
          "variant": null,
          "state": "conditional",
          "reach": [
            "tenant",
            "self"
          ],
          "excludesSelf": false
        },
        "properties": {
          "excludesSelf": {
            "type": "boolean"
          },
          "operationId": {
            "type": "string"
          },
          "reach": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "state": {
            "type": "string"
          },
          "variant": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "operationId",
          "variant",
          "state",
          "reach",
          "excludesSelf"
        ],
        "type": "object"
      },
      "CaptureLearningRecordRequest": {
        "description": "The body of \u0060POST /v1/learning-records\u0060, which appends an xAPI-style statement:\n\u0060verb\u0060 (required) says what the learner did to \u0060object\u0060, and \u0060result\u0060 and\n\u0060context\u0060 carry any further detail as JSON. \u0060actorUserId\u0060 names the learner\n(or \u0060me\u0060) and defaults to the caller. \u0060id\u0060 optionally supplies the statement\u0027s\nid. A statement with the verb \u0060http://adlnet.gov/expapi/verbs/completed\u0060 about an\nobject of type \u0060activity\u0060 named by \u0060id\u0060 also records an activity completion,\nscored from \u0060result.score.raw\u0060 when that is a number. The 201 response carries\nonly the new record\u0027s id.",
        "example": {
          "actorUserId": null,
          "verb": "http://adlnet.gov/expapi/verbs/completed",
          "object": {
            "type": "activity",
            "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "iri": null
          },
          "result": {
            "score": {
              "scaled": 0.8
            },
            "success": true,
            "completion": true
          },
          "context": {
            "registration": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
          },
          "id": null
        },
        "properties": {
          "actorUserId": {
            "type": [
              "null",
              "string"
            ]
          },
          "context": {},
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "object": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/LearningObjectInput"
              }
            ]
          },
          "result": {},
          "verb": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "type": "object"
      },
      "CompletionModel": {
        "description": "How a course\u0027s completion is decided: \u0060all_activities_completed\u0060 when every\nrequired activity placed in the course is complete, \u0060all_lessons_completed\u0060 when\nevery lesson placed in the course is complete, \u0060minimum_assessment_score\u0060 when the\nbest score on a placed activity reaches the course\u0027s \u0060minimumAssessmentScore\u0060, and\n\u0060manual_instructor_sign_off\u0060 only when the learner is signed off by hand. A module\nmay also state one, as a hint.",
        "enum": [
          "all_activities_completed",
          "all_lessons_completed",
          "minimum_assessment_score",
          "manual_instructor_sign_off"
        ],
        "example": "all_activities_completed"
      },
      "ContentStatus": {
        "description": "Where a course, lesson or activity version is in its lifecycle: \u0060draft\u0060 while it is\nbeing authored, \u0060published\u0060 once it is live, \u0060archived\u0060 once retired. It\nchanges only through actions: \u0060:publish\u0060 moves a draft to \u0060published\u0060,\n\u0060:unpublish\u0060 moves a published or archived version back to \u0060draft\u0060, and\n\u0060:archive\u0060 moves a draft or published version to \u0060archived\u0060.",
        "enum": [
          "draft",
          "published",
          "archived"
        ],
        "example": "draft"
      },
      "CourseCompletionDto": {
        "description": "A record that a user completed a course within an enrolment. Completions are\nappend-only. \u0060courseVersionId\u0060 is the course version the enrolment is pinned to.\n\u0060triggeredByCompletionId\u0060 names the activity or lesson completion that completed\nthe course when the completion was derived automatically, and is \u0060null\u0060 when it\nwas recorded directly (a sign-off). \u0060recordedBy\u0060 is \u0060null\u0060 when the user\nrecorded it themselves.",
        "example": {
          "id": "019cbe57-5380-7375-86b4-a74f3f4da0ac",
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
          "score": 0.8,
          "completedAt": "2026-03-05T14:12:00\u002B00:00",
          "createdAt": "2026-03-05T14:12:00\u002B00:00",
          "recordedBy": null,
          "triggeredByCompletionId": "019cbe57-5380-704a-9f16-595215e71aa0",
          "_links": {
            "self": "/v1/course-completions/019cbe57-5380-7375-86b4-a74f3f4da0ac",
            "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "completedAt": {
            "format": "date-time",
            "type": "string"
          },
          "courseVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "enrolmentId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "recordedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "score": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "triggeredByCompletionId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "userId",
          "courseVersionId",
          "enrolmentId",
          "score",
          "completedAt",
          "createdAt",
          "recordedBy",
          "triggeredByCompletionId",
          "_links"
        ],
        "type": "object"
      },
      "CourseDto": {
        "description": "A course: the top of the content tree, made of modules that hold placed lessons.\n\u0060completionModel\u0060 decides when a learner has completed the course, using\n\u0060minimumAssessmentScore\u0060 when it is \u0060minimum_assessment_score\u0060. \u0060status\u0060\nis read-only here and changes only through the \u0060:publish\u0060, \u0060:unpublish\u0060 and\n\u0060:archive\u0060 actions. \u0060familyId\u0060 is shared by every version of the course, while\n\u0060id\u0060 identifies this version. \u0060presentation\u0060 holds display settings the API\nstores and returns but never interprets; \u0060managedTags\u0060 is always empty and\n\u0060customFields\u0060 always \u0060{}\u0060 for now.",
        "example": {
          "id": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "title": "Working at height",
          "description": "Safe use of ladders, scaffolds and platforms for site staff.",
          "completionModel": "all_activities_completed",
          "minimumAssessmentScore": null,
          "order": 1,
          "selfEnrolmentEnabled": true,
          "enrolmentFrom": null,
          "enrolmentUntil": null,
          "accessFrom": null,
          "accessUntil": null,
          "status": "published",
          "customFields": {
            "costCentre": "HS-104"
          },
          "presentation": {},
          "managedTags": [
            "01990975-33e0-75c7-b2e1-b595a665fce8"
          ],
          "familyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
          "versionNumber": 1,
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "list": "/v1/courses",
            "update": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "delete": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "latestPublished": "/v1/courses/families/019bb1c1-6440-7aea-baf4-7992098170ae/latest-published",
            "unpublish": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3:unpublish",
            "archive": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3:archive",
            "enrol": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3:enrol"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "accessFrom": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "accessUntil": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "completionModel": {
            "$ref": "#/components/schemas/CompletionModel"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentFrom": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentUntil": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "managedTags": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "type": "array"
          },
          "minimumAssessmentScore": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "selfEnrolmentEnabled": {
            "type": "boolean"
          },
          "status": {
            "$ref": "#/components/schemas/ContentStatus"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "completionModel",
          "minimumAssessmentScore",
          "order",
          "selfEnrolmentEnabled",
          "enrolmentFrom",
          "enrolmentUntil",
          "accessFrom",
          "accessUntil",
          "status",
          "customFields",
          "presentation",
          "managedTags",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "CourseMergePatch": {
        "description": "The body of \u0060PATCH /v1/courses/{id}\u0060, a JSON Merge Patch (RFC 7396): a field left\nout is unchanged, a field sent is set, and \u0060null\u0060 clears a field that may be empty.\n\u0060title\u0060, \u0060order\u0060 and \u0060selfEnrolmentEnabled\u0060 cannot be set to \u0060null\u0060,\nand \u0060null\u0060 for \u0060presentation\u0060 resets it to \u0060{}\u0060. \u0060status\u0060 is not a\nfield here; it changes only through the \u0060:publish\u0060, \u0060:unpublish\u0060 and\n\u0060:archive\u0060 actions.",
        "example": {
          "description": "Safe use of ladders, scaffolds and mobile platforms for site staff.",
          "enrolmentUntil": "2026-12-31T23:59:00\u002B00:00"
        },
        "properties": {
          "accessFrom": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "accessUntil": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "completionModel": {
            "$ref": "#/components/schemas/CompletionModel"
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentFrom": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentUntil": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "minimumAssessmentScore": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "order": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "selfEnrolmentEnabled": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "title": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "CourseOutlineDto": {
        "description": "The response of \u0060GET /v1/courses/{courseId}/outline\u0060: a course\u0027s navigation tree in\none call, with its modules, each module\u0027s placed lessons and each lesson\u0027s placed\nactivities, every list in placement order. Each node carries the plain fields of the\nitem it represents but leaves out \u0060presentation\u0060, \u0060definition\u0060,\n\u0060customFields\u0060 and \u0060managedTags\u0060, so the response stays small; read the item\nitself, or \u0060GET /v1/courses/{courseId}/structure\u0060, when you need them. With\n\u0060?userId=\u0060 (a user id or \u0060me\u0060), lesson and activity nodes also carry that\nuser\u0027s completion state in \u0060progress\u0060. Only the root carries \u0060_links\u0060.",
        "example": {
          "id": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "title": "Working at height",
          "description": "Safe use of ladders, scaffolds and platforms for site staff.",
          "completionModel": "all_activities_completed",
          "minimumAssessmentScore": null,
          "order": 1,
          "selfEnrolmentEnabled": true,
          "enrolmentFrom": null,
          "enrolmentUntil": null,
          "accessFrom": null,
          "accessUntil": null,
          "status": "published",
          "familyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
          "versionNumber": 1,
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "modules": [
            {
              "id": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
              "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "title": "Before you climb",
              "description": "Checks to make before any work at height begins.",
              "lessonSequencing": "linear",
              "completionModel": null,
              "order": 1,
              "createdAt": "2026-01-12T10:30:00\u002B00:00",
              "updatedAt": "2026-01-12T10:30:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "lessons": [
                {
                  "lessonPlacementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
                  "order": 1,
                  "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
                  "title": "Inspecting a ladder",
                  "description": "What to look for before each use, and when to take a ladder out of service.",
                  "activitySequencing": "any",
                  "status": "published",
                  "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
                  "versionNumber": 1,
                  "createdAt": "2026-01-12T10:30:00\u002B00:00",
                  "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                  "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                  "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                  "progress": {
                    "completed": true,
                    "completionId": "019cbe57-5380-7f76-a8d1-5189a92f79aa",
                    "completedAt": "2026-03-05T14:12:00\u002B00:00"
                  },
                  "activities": [
                    {
                      "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
                      "order": 1,
                      "required": true,
                      "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
                      "title": "Ladder safety check",
                      "description": "Five questions on inspecting a ladder before use.",
                      "componentUri": "https://serenapp.io/activities/quiz",
                      "producesScore": true,
                      "carriesQuestions": true,
                      "status": "published",
                      "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
                      "versionNumber": 1,
                      "createdAt": "2026-01-12T10:30:00\u002B00:00",
                      "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                      "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                      "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                      "progress": {
                        "completed": true,
                        "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
                        "score": 0.8,
                        "completedAt": "2026-03-05T14:12:00\u002B00:00"
                      }
                    }
                  ]
                }
              ]
            }
          ],
          "_links": {
            "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/outline",
            "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "accessFrom": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "accessUntil": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "completionModel": {
            "$ref": "#/components/schemas/CompletionModel"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentFrom": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentUntil": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "minimumAssessmentScore": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "modules": {
            "items": {
              "$ref": "#/components/schemas/OutlineModuleDto"
            },
            "type": "array"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "selfEnrolmentEnabled": {
            "type": "boolean"
          },
          "status": {
            "$ref": "#/components/schemas/ContentStatus"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "completionModel",
          "minimumAssessmentScore",
          "order",
          "selfEnrolmentEnabled",
          "enrolmentFrom",
          "enrolmentUntil",
          "accessFrom",
          "accessUntil",
          "status",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "modules",
          "_links"
        ],
        "type": "object"
      },
      "CourseReportDetailRowDto": {
        "description": "One active enrolment on the course in \u0060GET /v1/reports/courses/{id}\u0060: the\nlearner, the course version the enrolment is pinned to, the server-computed\n\u0060percentComplete\u0060, the enrolment\u0027s \u0060status\u0060 (\u0060completed\u0060,\n\u0060overdue\u0060, \u0060in_progress\u0060 or \u0060not_started\u0060), its \u0060deadline\u0060 and\n\u0060isMandatory\u0060, and \u0060completedAt\u0060, when it was first completed.",
        "example": {
          "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "displayName": "Priya Shah",
          "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "percentComplete": 100,
          "status": "completed",
          "deadline": "2026-03-31T17:00:00\u002B00:00",
          "isMandatory": true,
          "completedAt": "2026-03-05T14:12:00\u002B00:00",
          "_links": {
            "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
            "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
            "user": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "completedAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "courseVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "deadline": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "displayName": {
            "type": "string"
          },
          "enrolmentId": {
            "format": "uuid",
            "type": "string"
          },
          "isMandatory": {
            "type": "boolean"
          },
          "percentComplete": {
            "format": "double",
            "type": "number"
          },
          "status": {
            "type": "string"
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "enrolmentId",
          "userId",
          "displayName",
          "courseVersionId",
          "percentComplete",
          "status",
          "deadline",
          "isMandatory",
          "completedAt",
          "_links"
        ],
        "type": "object"
      },
      "CourseReportRowDto": {
        "description": "One course in \u0060GET /v1/reports/courses\u0060. Only courses with at least one\npublished version are listed; each is named by \u0060courseFamilyId\u0060 and described by\nits latest published version. The counts cover active enrolments on any version of\nthe course and mean the same as in the users report: \u0060completed\u0060 within the report\nwindow, and \u0060enrolled\u0060, \u0060overdue\u0060 and \u0060mandatoryOutstanding\u0060 as they\nstand now.",
        "example": {
          "courseFamilyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
          "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "title": "Working at height",
          "completionModel": "all_activities_completed",
          "enrolled": 42,
          "completed": 31,
          "overdue": 4,
          "mandatoryOutstanding": 11,
          "_links": {
            "self": "/v1/reports/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "completed": {
            "format": "int32",
            "type": "integer"
          },
          "completionModel": {
            "type": "string"
          },
          "courseFamilyId": {
            "format": "uuid",
            "type": "string"
          },
          "courseVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "enrolled": {
            "format": "int32",
            "type": "integer"
          },
          "mandatoryOutstanding": {
            "format": "int32",
            "type": "integer"
          },
          "overdue": {
            "format": "int32",
            "type": "integer"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "courseFamilyId",
          "courseVersionId",
          "title",
          "completionModel",
          "enrolled",
          "completed",
          "overdue",
          "mandatoryOutstanding",
          "_links"
        ],
        "type": "object"
      },
      "CourseStructureDto": {
        "description": "The response of \u0060GET /v1/courses/{courseId}/structure\u0060: a course\u0027s whole content\ntree in one call, with every field of each item. \u0060course\u0060 is the course as its own\nread returns it, and \u0060modules\u0060 holds each module with its placed lessons and their\nplaced activities, every list in placement order. With \u0060?userId=\u0060 (a user id or\n\u0060me\u0060), lesson and activity nodes also carry that user\u0027s completion state in\n\u0060progress\u0060. Course completion is not included; read it from\n\u0060GET /v1/enrolments/{id}/progress\u0060.",
        "example": {
          "course": {
            "id": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "title": "Working at height",
            "description": "Safe use of ladders, scaffolds and platforms for site staff.",
            "completionModel": "all_activities_completed",
            "minimumAssessmentScore": null,
            "order": 1,
            "selfEnrolmentEnabled": true,
            "enrolmentFrom": null,
            "enrolmentUntil": null,
            "accessFrom": null,
            "accessUntil": null,
            "status": "published",
            "customFields": {
              "costCentre": "HS-104"
            },
            "presentation": {},
            "managedTags": [
              "01990975-33e0-75c7-b2e1-b595a665fce8"
            ],
            "familyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
            "versionNumber": 1,
            "createdAt": "2026-01-12T10:30:00\u002B00:00",
            "updatedAt": "2026-02-03T15:20:00\u002B00:00",
            "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "_links": {
              "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "list": "/v1/courses",
              "update": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "delete": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "latestPublished": "/v1/courses/families/019bb1c1-6440-7aea-baf4-7992098170ae/latest-published",
              "unpublish": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3:unpublish",
              "archive": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3:archive",
              "enrol": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3:enrol"
            }
          },
          "modules": [
            {
              "module": {
                "id": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
                "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
                "title": "Before you climb",
                "description": "Checks to make before any work at height begins.",
                "lessonSequencing": "linear",
                "completionModel": null,
                "order": 1,
                "customFields": {},
                "presentation": {},
                "createdAt": "2026-01-12T10:30:00\u002B00:00",
                "updatedAt": "2026-01-12T10:30:00\u002B00:00",
                "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "_links": {
                  "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
                  "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
                  "update": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
                  "delete": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3"
                }
              },
              "lessons": [
                {
                  "lessonPlacementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
                  "order": 1,
                  "presentation": {},
                  "progress": {
                    "completed": true,
                    "completionId": "019cbe57-5380-7f76-a8d1-5189a92f79aa",
                    "completedAt": "2026-03-05T14:12:00\u002B00:00"
                  },
                  "lesson": {
                    "id": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
                    "title": "Inspecting a ladder",
                    "description": "What to look for before each use, and when to take a ladder out of service.",
                    "activitySequencing": "any",
                    "status": "published",
                    "customFields": {},
                    "presentation": {
                      "schemaVersion": 2,
                      "title": "Inspecting a ladder",
                      "readingTime": 4,
                      "blocks": [
                        {
                          "type": "https://serenapp.io/blocks/lead",
                          "text": "Falls remain the most common cause of serious workplace injury."
                        },
                        {
                          "type": "https://serenapp.io/blocks/h2",
                          "text": "Before you climb"
                        },
                        {
                          "type": "https://serenapp.io/blocks/list",
                          "items": [
                            "Check the feet",
                            "Check the rungs",
                            "Check the locks"
                          ]
                        }
                      ]
                    },
                    "managedTags": [
                      "01990975-33e0-75c7-b2e1-b595a665fce8"
                    ],
                    "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
                    "versionNumber": 1,
                    "createdAt": "2026-01-12T10:30:00\u002B00:00",
                    "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                    "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                    "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                    "activities": [
                      {
                        "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
                        "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
                        "order": 1,
                        "required": true,
                        "presentation": {},
                        "activity": {
                          "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
                          "title": "Ladder safety check",
                          "description": "Five questions on inspecting a ladder before use.",
                          "componentUri": "https://serenapp.io/activities/quiz",
                          "producesScore": true,
                          "carriesQuestions": true,
                          "status": "published",
                          "definition": {},
                          "customFields": {},
                          "presentation": {},
                          "managedTags": [],
                          "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
                          "versionNumber": 1,
                          "createdAt": "2026-01-12T10:30:00\u002B00:00",
                          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                          "_links": {
                            "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                            "list": "/v1/activities",
                            "update": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                            "delete": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                            "latestPublished": "/v1/activities/families/019bb1c1-6440-7a1b-89fb-2ecc8c0ad419/latest-published",
                            "unpublish": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:unpublish",
                            "archive": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:archive"
                          }
                        },
                        "progress": {
                          "completed": true,
                          "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
                          "score": 0.8,
                          "completedAt": "2026-03-05T14:12:00\u002B00:00"
                        }
                      }
                    ],
                    "_links": {
                      "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/structure",
                      "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                      "activities": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/activities"
                    }
                  }
                }
              ]
            }
          ],
          "_links": {
            "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/structure",
            "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "course": {
            "$ref": "#/components/schemas/CourseDto"
          },
          "modules": {
            "items": {
              "$ref": "#/components/schemas/StructureModuleDto"
            },
            "type": "array"
          }
        },
        "required": [
          "course",
          "modules",
          "_links"
        ],
        "type": "object"
      },
      "CreateActivityCommand": {
        "description": "The body of \u0060POST /v1/activities\u0060. \u0060title\u0060 and \u0060componentUri\u0060 are\nrequired, and \u0060componentUri\u0060 cannot be changed once the activity exists. \u0060id\u0060\nis optional: leave it out and the server assigns one, or supply a UUID of your own; a\nmalformed UUID is rejected with 422 and one already in use with 409. \u0060definition\u0060\nmust be a JSON object and defaults to \u0060{}\u0060; \u0060presentation\u0060 may be a JSON\nobject of up to 16 KB, and \u0060customFields\u0060 accepts only an empty object for now. The\nnew activity starts at version 1 in \u0060draft\u0060.",
        "example": {
          "title": "Ladder safety check",
          "componentUri": "https://serenapp.io/activities/quiz",
          "producesScore": true,
          "carriesQuestions": true,
          "description": "Five questions on inspecting a ladder before use.",
          "definition": null,
          "customFields": null,
          "presentation": null,
          "id": null
        },
        "properties": {
          "carriesQuestions": {
            "default": false,
            "type": "boolean"
          },
          "componentUri": {
            "type": "string"
          },
          "customFields": {},
          "definition": {},
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "presentation": {},
          "producesScore": {
            "default": false,
            "type": "boolean"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "title",
          "componentUri"
        ],
        "type": "object"
      },
      "CreateActivityPlacementRequest": {
        "description": "The body of \u0060POST /v1/lessons/{lessonId}/activity-placements\u0060; the lesson is named\nby the route. \u0060activityId\u0060 must name an existing activity, or the request is\nrejected with 422. The placement is appended after the lesson\u0027s existing placements, and\n\u0060required\u0060 defaults to \u0060true\u0060.",
        "example": {
          "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
          "required": true,
          "customFields": null,
          "presentation": null,
          "id": null
        },
        "properties": {
          "activityId": {
            "type": "string"
          },
          "customFields": {},
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "presentation": {},
          "required": {
            "default": true,
            "type": "boolean"
          }
        },
        "required": [
          "activityId"
        ],
        "type": "object"
      },
      "CreateAgentCommand": {
        "example": {
          "name": "Course drafting assistant"
        },
        "properties": {
          "name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CreateAgentDelegationRequest": {
        "example": {
          "scope": [
            "content:read",
            "content:write"
          ]
        },
        "properties": {
          "scope": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "scope"
        ],
        "type": "object"
      },
      "CreateCourseCommand": {
        "description": "The body of \u0060POST /v1/courses\u0060. \u0060title\u0060 and \u0060completionModel\u0060 are\nrequired, and \u0060minimumAssessmentScore\u0060 is required when \u0060completionModel\u0060 is\n\u0060minimum_assessment_score\u0060. \u0060id\u0060 is optional: leave it out and the server\nassigns one, or supply a UUID of your own; a malformed UUID is rejected with 422 and one\nalready in use with 409. Leave out \u0060order\u0060 to place the course after the existing\nones. \u0060presentation\u0060 may be a JSON object of up to 16 KB, and \u0060customFields\u0060\naccepts only an empty object for now. The new course starts at version 1 in\n\u0060draft\u0060.",
        "example": {
          "title": "Working at height",
          "completionModel": "all_activities_completed",
          "description": "Safe use of ladders, scaffolds and platforms for site staff.",
          "minimumAssessmentScore": null,
          "order": null,
          "customFields": {
            "costCentre": "HS-104"
          },
          "presentation": null,
          "selfEnrolmentEnabled": true,
          "enrolmentFrom": null,
          "enrolmentUntil": null,
          "accessFrom": null,
          "accessUntil": null,
          "id": null
        },
        "properties": {
          "accessFrom": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "accessUntil": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "completionModel": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CompletionModel"
              }
            ]
          },
          "customFields": {},
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentFrom": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentUntil": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "minimumAssessmentScore": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "order": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "presentation": {},
          "selfEnrolmentEnabled": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "title",
          "completionModel"
        ],
        "type": "object"
      },
      "CreateEnrolmentRequest": {
        "description": "The body of \u0060POST /v1/enrolments\u0060. \u0060courseId\u0060 names the course, and\n\u0060userId\u0060 the learner (or \u0060me\u0060), defaulting to the caller. A caller with\n\u0060enrolments:write:tenant\u0060 may enrol any user and set \u0060deadline\u0060 and\n\u0060isMandatory\u0060; any other caller may only enrol themselves, on a published course\nthat allows self-enrolment, and sending \u0060deadline\u0060 or \u0060isMandatory\u0060 is refused\nwith 403. If the user already holds an active enrolment on the course, that enrolment\nis returned with 200 instead of 201. \u0060id\u0060 optionally supplies the new enrolment\u0027s\nid.",
        "example": {
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "deadline": "2026-03-31T17:00:00\u002B00:00",
          "isMandatory": true,
          "id": null
        },
        "properties": {
          "courseId": {
            "type": [
              "null",
              "string"
            ]
          },
          "deadline": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "isMandatory": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "userId": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "type": "object"
      },
      "CreateLessonCommand": {
        "description": "The body of \u0060POST /v1/lessons\u0060. \u0060title\u0060 is required, and\n\u0060activitySequencing\u0060 defaults to \u0060any\u0060. \u0060id\u0060 is optional: leave it out\nand the server assigns one, or supply a UUID of your own; a malformed UUID is rejected\nwith 422 and one already in use with 409. \u0060presentation\u0060 may be a JSON object of up\nto 16 KB, and \u0060customFields\u0060 accepts only an empty object for now. The new lesson\nstarts at version 1 in \u0060draft\u0060.",
        "example": {
          "title": "Inspecting a ladder",
          "description": "What to look for before each use, and when to take a ladder out of service.",
          "activitySequencing": "any",
          "customFields": null,
          "presentation": {
            "schemaVersion": 2,
            "title": "Inspecting a ladder",
            "readingTime": 4,
            "blocks": [
              {
                "type": "https://serenapp.io/blocks/lead",
                "text": "Falls remain the most common cause of serious workplace injury."
              },
              {
                "type": "https://serenapp.io/blocks/h2",
                "text": "Before you climb"
              },
              {
                "type": "https://serenapp.io/blocks/list",
                "items": [
                  "Check the feet",
                  "Check the rungs",
                  "Check the locks"
                ]
              }
            ]
          },
          "id": null
        },
        "properties": {
          "activitySequencing": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/SequencingMode"
              }
            ]
          },
          "customFields": {},
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "presentation": {},
          "title": {
            "type": "string"
          }
        },
        "required": [
          "title"
        ],
        "type": "object"
      },
      "CreateLessonPlacementRequest": {
        "description": "The body of \u0060POST /v1/courses/{courseId}/modules/{moduleId}/lesson-placements\u0060; the\ncourse and module are named by the route. \u0060lessonId\u0060 must name an existing lesson,\nor the request is rejected with 422. The placement is appended after the module\u0027s\nexisting placements.",
        "example": {
          "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
          "customFields": null,
          "presentation": null,
          "id": null
        },
        "properties": {
          "customFields": {},
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "lessonId": {
            "type": "string"
          },
          "presentation": {}
        },
        "required": [
          "lessonId"
        ],
        "type": "object"
      },
      "CreateModuleRequest": {
        "description": "The body of \u0060POST /v1/courses/{courseId}/modules\u0060; the course is named by the\nroute. \u0060title\u0060 is required. \u0060id\u0060 is optional: leave it out and the server\nassigns one, or supply a UUID of your own; a malformed UUID is rejected with 422 and one\nalready in use with 409. The module is appended after the course\u0027s existing modules.\n\u0060presentation\u0060 may be a JSON object of up to 16 KB, and \u0060customFields\u0060 accepts\nonly an empty object for now.",
        "example": {
          "title": "Before you climb",
          "description": "Checks to make before any work at height begins.",
          "lessonSequencing": "linear",
          "completionModel": null,
          "customFields": null,
          "presentation": null,
          "id": null
        },
        "properties": {
          "completionModel": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CompletionModel"
              }
            ]
          },
          "customFields": {},
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "lessonSequencing": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/SequencingMode"
              }
            ]
          },
          "presentation": {},
          "title": {
            "type": "string"
          }
        },
        "required": [
          "title"
        ],
        "type": "object"
      },
      "CreateQuestionCommand": {
        "description": "The body of \u0060POST /v1/questions\u0060. \u0060type\u0060 and \u0060prompt\u0060 are required, and\n\u0060type\u0060 cannot be changed later. Multiple-choice questions list their answer choices\nin \u0060options\u0060, in display order; \u0060true_false\u0060 and \u0060short_answer\u0060 questions\nleave \u0060options\u0060 empty and keep their answer in \u0060definition\u0060. \u0060likert\u0060 and\n\u0060rating\u0060 questions list at least two scale points in \u0060options\u0060, each with a\ndistinct numeric \u0060value\u0060 and no answer key; \u0060free_text\u0060 questions leave\n\u0060options\u0060 empty and may not carry \u0060answer\u0060 or \u0060acceptedAnswers\u0060 in\n\u0060definition\u0060. \u0060id\u0060 is\noptional: leave it out and the server assigns one, or supply a UUID of your own; a\nmalformed UUID is rejected with 422 and one already in use with 409. The answer key and\n\u0060explanation\u0060 are returned only by \u0060GET /v1/questions/{id}/answer-key\u0060.",
        "example": {
          "type": "multiple_choice_single",
          "prompt": "What must you check before every climb?",
          "definition": null,
          "options": [
            {
              "id": null,
              "label": "The feet, rungs and locks",
              "value": null
            },
            {
              "id": null,
              "label": "Only the paint",
              "value": null
            }
          ],
          "explanation": "A ladder fails at its feet, rungs or locks, so those are checked every time.",
          "customFields": null,
          "presentation": null,
          "id": null
        },
        "properties": {
          "customFields": {},
          "definition": {},
          "explanation": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "options": {
            "items": {
              "$ref": "#/components/schemas/QuestionOptionInput"
            },
            "type": [
              "null",
              "array"
            ]
          },
          "presentation": {},
          "prompt": {
            "type": "string"
          },
          "type": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/QuestionType"
              }
            ]
          }
        },
        "required": [
          "type",
          "prompt"
        ],
        "type": "object"
      },
      "CreateQuestionPlacementRequest": {
        "description": "The body of \u0060POST /v1/activities/{activityId}/question-placements\u0060; the activity is\nnamed by the route and must have \u0060carriesQuestions\u0060 set, or the request is rejected\nwith 422. \u0060questionId\u0060 must name an existing question, or the request is rejected\nwith 422. The placement is appended after the activity\u0027s existing placements.\n\u0060required\u0060 defaults to \u0060false\u0060; when \u0060true\u0060, every completion submitted\nfor the activity must answer this placement, or it is rejected with 422.",
        "example": {
          "questionId": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
          "required": true,
          "customFields": null,
          "presentation": null,
          "id": null
        },
        "properties": {
          "customFields": {},
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "presentation": {},
          "questionId": {
            "type": "string"
          },
          "required": {
            "default": false,
            "type": "boolean"
          }
        },
        "required": [
          "questionId"
        ],
        "type": "object"
      },
      "CreateTagCommand": {
        "description": "The body of \u0060POST /v1/tags\u0060. \u0060tagGroupId\u0060 and \u0060name\u0060 are required: the\ngroup must exist, or the request is rejected with 422, and a name another tag in the\ngroup already uses is rejected with 409. A tag cannot move to another group later.\n\u0060id\u0060 is optional: leave it out and the server assigns one, or supply a UUID of your\nown; a malformed UUID is rejected with 422 and one already in use with 409.\n\u0060customFields\u0060 accepts only an empty object for now.",
        "example": {
          "tagGroupId": "01990975-33e0-718c-87a3-27c01cd74192",
          "name": "Health and safety",
          "description": "Keeping people safe at work.",
          "customFields": null,
          "id": null
        },
        "properties": {
          "customFields": {},
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "name": {
            "type": "string"
          },
          "tagGroupId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "tagGroupId",
          "name"
        ],
        "type": "object"
      },
      "CreateTagGroupCommand": {
        "description": "The body of \u0060POST /v1/tag-groups\u0060. \u0060name\u0060 is required, and a name another tag\ngroup already uses is rejected with 409. \u0060appliesTo\u0060 lists the entity types the\ngroup\u0027s tags are meant for, as distinct names. \u0060id\u0060 is optional: leave it out and\nthe server assigns one, or supply a UUID of your own; a malformed UUID is rejected with\n422 and one already in use with 409. \u0060customFields\u0060 accepts only an empty object\nfor now.",
        "example": {
          "name": "Topic",
          "description": "What a course or lesson is about, for filtering the catalogue.",
          "exclusive": false,
          "appliesTo": [
            "course",
            "lesson"
          ],
          "customFields": null,
          "id": null
        },
        "properties": {
          "appliesTo": {
            "items": {
              "type": "string"
            },
            "type": [
              "null",
              "array"
            ]
          },
          "customFields": {},
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "exclusive": {
            "default": false,
            "type": "boolean"
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CreateWebhookSubscriptionCommand": {
        "description": "The body of \u0060POST /v1/webhook-subscriptions\u0060. \u0060name\u0060 (at most 200\ncharacters) must be unique in the tenant, or the request fails with 409.\n\u0060targetUrl\u0060 is the absolute \u0060https\u0060 URL deliveries are sent to (at most 2,000\ncharacters). \u0060eventTypes\u0060 lists 1 to 100 event types to deliver, each a lowercase\ndotted name such as \u0060user.created\u0060 of at most 200 characters; a longer list is\nrefused with that one error and no entry is checked. \u0060id\u0060 optionally supplies the\nnew subscription\u0027s id. The server generates the signing secret and returns it once, in\nthe 201 response.",
        "example": {
          "name": "Completions to HR",
          "targetUrl": "https://hr.example.com/hooks/seren",
          "eventTypes": [
            "course_completion.created",
            "enrolment.created"
          ],
          "id": null
        },
        "properties": {
          "eventTypes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "name": {
            "type": "string"
          },
          "targetUrl": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "targetUrl",
          "eventTypes"
        ],
        "type": "object"
      },
      "CursorPageInfo": {
        "description": "Where a page sits in its list. \u0060limit\u0060 is the page size that was applied. While\n\u0060hasMore\u0060 is true, pass \u0060nextCursor\u0060 as \u0060after\u0060 to get the next page; on\nthe last page \u0060hasMore\u0060 is false and \u0060nextCursor\u0060 is null. \u0060totalCount\u0060\nis the number of items in the whole list when the request asked for it with\n\u0060includeCount=true\u0060, and null otherwise.",
        "example": {
          "limit": 25,
          "hasMore": true,
          "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5Y2FkYzYtOWE4MC03ZGFmLWE3MDYtZGQyZjRkMTk3ZTZmIn0",
          "totalCount": null
        },
        "properties": {
          "hasMore": {
            "type": "boolean"
          },
          "limit": {
            "format": "int32",
            "type": "integer"
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "totalCount": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          }
        },
        "required": [
          "limit",
          "hasMore",
          "nextCursor"
        ],
        "type": "object"
      },
      "CursorPageOfActivityCompletionDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019cbe57-5380-704a-9f16-595215e71aa0",
              "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "activityVersionId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "placementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
              "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
              "score": 0.8,
              "completedAt": "2026-03-05T14:12:00\u002B00:00",
              "createdAt": "2026-03-05T14:12:00\u002B00:00",
              "recordedBy": null,
              "triggeredByRecordId": null,
              "responses": [
                {
                  "id": "019cbe57-5380-7068-9012-eb773f7b168b",
                  "questionPlacementId": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
                  "questionVersionId": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
                  "isCorrect": true,
                  "score": 1,
                  "response": [
                    "019bb1c1-6440-7eb0-ba7d-398bbc7e264c"
                  ],
                  "createdAt": "2026-03-05T14:12:00\u002B00:00"
                }
              ],
              "_links": {
                "self": "/v1/activity-completions/019cbe57-5380-704a-9f16-595215e71aa0",
                "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "placement": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5Y2JlNTctNTM4MC03MDRhLTlmMTYtNTk1MjE1ZTcxYWEwIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2/completions",
            "submit": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2/completions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/ActivityCompletionDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfActivityDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "title": "Ladder safety check",
              "description": "Five questions on inspecting a ladder before use.",
              "componentUri": "https://serenapp.io/activities/quiz",
              "producesScore": true,
              "carriesQuestions": true,
              "status": "published",
              "definition": {},
              "customFields": {},
              "presentation": {},
              "managedTags": [],
              "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
              "versionNumber": 1,
              "createdAt": "2026-01-12T10:30:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "list": "/v1/activities",
                "update": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "delete": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "latestPublished": "/v1/activities/families/019bb1c1-6440-7a1b-89fb-2ecc8c0ad419/latest-published",
                "unpublish": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:unpublish",
                "archive": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:archive"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YmIxYzEtNjQ0MC03MDViLTg3ZWEtM2I4NjY5N2U2Y2MyIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/activities",
            "create": "/v1/activities"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/ActivityDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfActivityPlacementDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019c2416-cd00-7f88-9800-5cdd08a0d146",
              "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "order": 1,
              "required": true,
              "customFields": {},
              "presentation": {},
              "familyId": "019c2416-cd00-7453-9c0b-92ffc149261b",
              "versionNumber": 1,
              "createdAt": "2026-02-03T15:20:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
                "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "update": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
                "delete": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
                "completions": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions",
                "submitCompletion": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzI0MTYtY2QwMC03Zjg4LTk4MDAtNWNkZDA4YTBkMTQ2In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2/placements"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/ActivityPlacementDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfAgentDelegationDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019c2416-cd00-7631-a7a9-795318683c22",
              "agentId": "019c2416-cd00-7767-a377-bd13a3d1cedb",
              "mode": "delegated",
              "scope": [
                "content:read",
                "content:write"
              ],
              "draftOnly": true,
              "grantedByActorId": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "authorisedByActorId": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "grantedAt": "2026-02-03T15:20:00\u002B00:00",
              "expiresAt": "2026-03-05T15:20:00\u002B00:00",
              "reconfirmationDueAt": "2026-02-10T15:20:00\u002B00:00",
              "status": "active",
              "createdAt": "2026-02-03T15:20:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/agents/019c2416-cd00-7767-a377-bd13a3d1cedb/delegations/019c2416-cd00-7631-a7a9-795318683c22",
                "revoke": "/v1/agents/019c2416-cd00-7767-a377-bd13a3d1cedb/delegations/019c2416-cd00-7631-a7a9-795318683c22:revoke"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzI0MTYtY2QwMC03NjMxLWE3YTktNzk1MzE4NjgzYzIyIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/agents/019c2416-cd00-7767-a377-bd13a3d1cedb/delegations"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/AgentDelegationDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfAgentDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019c2416-cd00-7767-a377-bd13a3d1cedb",
              "name": "Course drafting assistant",
              "ownerActorId": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "ownership": "external",
              "mode": "delegated",
              "status": "active",
              "createdAt": "2026-02-03T15:20:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/agents/019c2416-cd00-7767-a377-bd13a3d1cedb",
                "delegations": "/v1/agents/019c2416-cd00-7767-a377-bd13a3d1cedb/delegations"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzI0MTYtY2QwMC03NzY3LWEzNzctYmQxM2EzZDFjZWRiIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/agents"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/AgentDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfCourseCompletionDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019cbe57-5380-7375-86b4-a74f3f4da0ac",
              "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
              "score": 0.8,
              "completedAt": "2026-03-05T14:12:00\u002B00:00",
              "createdAt": "2026-03-05T14:12:00\u002B00:00",
              "recordedBy": null,
              "triggeredByCompletionId": "019cbe57-5380-704a-9f16-595215e71aa0",
              "_links": {
                "self": "/v1/course-completions/019cbe57-5380-7375-86b4-a74f3f4da0ac",
                "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
                "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5Y2JlNTctNTM4MC03Mzc1LTg2YjQtYTc0ZjNmNGRhMGFjIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/completions",
            "record": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/completions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/CourseCompletionDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfCourseDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "title": "Working at height",
              "description": "Safe use of ladders, scaffolds and platforms for site staff.",
              "completionModel": "all_activities_completed",
              "minimumAssessmentScore": null,
              "order": 1,
              "selfEnrolmentEnabled": true,
              "enrolmentFrom": null,
              "enrolmentUntil": null,
              "accessFrom": null,
              "accessUntil": null,
              "status": "published",
              "customFields": {
                "costCentre": "HS-104"
              },
              "presentation": {},
              "managedTags": [
                "01990975-33e0-75c7-b2e1-b595a665fce8"
              ],
              "familyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
              "versionNumber": 1,
              "createdAt": "2026-01-12T10:30:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
                "list": "/v1/courses",
                "update": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
                "delete": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
                "latestPublished": "/v1/courses/families/019bb1c1-6440-7aea-baf4-7992098170ae/latest-published",
                "unpublish": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3:unpublish",
                "archive": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3:archive",
                "enrol": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3:enrol"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YmIxYzEtNjQ0MC03ZGNlLWE0NjctM2NlNDk3NjVjN2YzIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/courses",
            "create": "/v1/courses"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/CourseDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfCourseReportDetailRowDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
              "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "displayName": "Priya Shah",
              "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "percentComplete": 100,
              "status": "completed",
              "deadline": "2026-03-31T17:00:00\u002B00:00",
              "isMandatory": true,
              "completedAt": "2026-03-05T14:12:00\u002B00:00",
              "_links": {
                "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
                "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
                "user": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzY1YWQtOTI4MC03NGNiLTlmZGItZjYxMWYzYWQ2ZGM4In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/reports/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "report": "/v1/reports/courses"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/CourseReportDetailRowDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfCourseReportRowDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "courseFamilyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
              "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "title": "Working at height",
              "completionModel": "all_activities_completed",
              "enrolled": 42,
              "completed": 31,
              "overdue": 4,
              "mandatoryOutstanding": 11,
              "_links": {
                "self": "/v1/reports/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
                "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YmIxYzEtNjQ0MC03YWVhLWJhZjQtNzk5MjA5ODE3MGFlIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/reports/courses"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/CourseReportRowDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfEnrolmentDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
              "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "courseFamilyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
              "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "deadline": "2026-03-31T17:00:00\u002B00:00",
              "isMandatory": true,
              "method": "admin",
              "deletedAt": null,
              "createdAt": "2026-02-16T09:00:00\u002B00:00",
              "updatedAt": "2026-02-16T09:00:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
                "completions": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/completions",
                "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
                "update": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
                "withdraw": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzY1YWQtOTI4MC03NGNiLTlmZGItZjYxMWYzYWQ2ZGM4In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/enrolments",
            "enrol": "/v1/enrolments",
            "bulk": "/v1/enrolments:bulk"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/EnrolmentDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfLearningRecordDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019cbe57-5380-731c-bd90-540fe6a96dd7",
              "actorUserId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "verb": "http://adlnet.gov/expapi/verbs/completed",
              "object": {
                "type": "activity",
                "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "iri": null
              },
              "result": {
                "score": {
                  "scaled": 0.8
                },
                "success": true,
                "completion": true
              },
              "context": {
                "registration": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
              },
              "recordedBy": null,
              "createdAt": "2026-03-05T14:12:00\u002B00:00",
              "_links": {
                "self": "/v1/learning-records/019cbe57-5380-731c-bd90-540fe6a96dd7"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5Y2JlNTctNTM4MC03MzFjLWJkOTAtNTQwZmU2YTk2ZGQ3In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/learning-records",
            "capture": "/v1/learning-records"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/LearningRecordDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfLessonCompletionDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019cbe57-5380-7f76-a8d1-5189a92f79aa",
              "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "lessonVersionId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "placementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
              "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
              "score": null,
              "completedAt": "2026-03-05T14:12:00\u002B00:00",
              "createdAt": "2026-03-05T14:12:00\u002B00:00",
              "recordedBy": null,
              "_links": {
                "self": "/v1/lesson-completions/019cbe57-5380-7f76-a8d1-5189a92f79aa",
                "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "placement": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5Y2JlNTctNTM4MC03Zjc2LWE4ZDEtNTE4OWE5MmY3OWFhIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/completions",
            "submit": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/completions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/LessonCompletionDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfLessonDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "title": "Inspecting a ladder",
              "description": "What to look for before each use, and when to take a ladder out of service.",
              "activitySequencing": "any",
              "status": "published",
              "customFields": {},
              "presentation": {
                "schemaVersion": 2,
                "title": "Inspecting a ladder",
                "readingTime": 4,
                "blocks": [
                  {
                    "type": "https://serenapp.io/blocks/lead",
                    "text": "Falls remain the most common cause of serious workplace injury."
                  },
                  {
                    "type": "https://serenapp.io/blocks/h2",
                    "text": "Before you climb"
                  },
                  {
                    "type": "https://serenapp.io/blocks/list",
                    "items": [
                      "Check the feet",
                      "Check the rungs",
                      "Check the locks"
                    ]
                  }
                ]
              },
              "managedTags": [
                "01990975-33e0-75c7-b2e1-b595a665fce8"
              ],
              "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
              "versionNumber": 1,
              "createdAt": "2026-01-12T10:30:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "list": "/v1/lessons",
                "update": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "delete": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "latestPublished": "/v1/lessons/families/019bb1c1-6440-740d-b36a-5a49db5899fa/latest-published",
                "unpublish": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c:unpublish",
                "archive": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c:archive"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YmIxYzEtNjQ0MC03OGE4LTk5NDgtZjc0ZWE1M2ZlNDdjIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/lessons",
            "create": "/v1/lessons"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/LessonDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfLessonPlacementDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019c2416-cd00-7949-814a-d4c64cdb5328",
              "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "moduleId": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
              "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "order": 1,
              "customFields": {},
              "presentation": {},
              "familyId": "019c2416-cd00-7e95-97e1-faba65107204",
              "versionNumber": 1,
              "createdAt": "2026-02-03T15:20:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
                "module": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
                "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "update": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
                "delete": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
                "completions": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions",
                "submitCompletion": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzI0MTYtY2QwMC03OTQ5LTgxNGEtZDRjNjRjZGI1MzI4In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/placements"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/LessonPlacementDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfQuestionDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
              "type": "multiple_choice_single",
              "prompt": "What must you check before every climb?",
              "definition": {},
              "customFields": {},
              "presentation": {},
              "options": [
                {
                  "id": "019bb1c1-6440-7eb0-ba7d-398bbc7e264c",
                  "order": 1,
                  "label": "The feet, rungs and locks",
                  "value": null
                },
                {
                  "id": "019bb1c1-6440-7eec-8330-12f6635ff33d",
                  "order": 2,
                  "label": "Only the paint",
                  "value": null
                }
              ],
              "managedTags": [],
              "familyId": "019bb1c1-6440-7112-8ac9-8e55dbe33168",
              "versionNumber": 1,
              "createdAt": "2026-01-12T10:30:00\u002B00:00",
              "updatedAt": "2026-01-12T10:30:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
                "list": "/v1/questions",
                "update": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
                "delete": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
                "answerKey": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf/answer-key"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YmIxYzEtNjQ0MC03OTA3LWI1MWYtYjBiYjk4NzJlZmRmIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/questions",
            "create": "/v1/questions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/QuestionDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfQuestionPlacementDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
              "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "questionId": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
              "order": 1,
              "required": true,
              "customFields": {},
              "presentation": {},
              "familyId": "019c2416-cd00-7007-b94f-852667e9fcae",
              "versionNumber": 1,
              "createdAt": "2026-02-03T15:20:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f",
                "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "question": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
                "update": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f",
                "delete": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzI0MTYtY2QwMC03YWQ2LTg0ZTMtZDE0YmY5NTg2ZTBmIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf/placements"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/QuestionPlacementDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfTagDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "01990975-33e0-75c7-b2e1-b595a665fce8",
              "tagGroupId": "01990975-33e0-718c-87a3-27c01cd74192",
              "name": "Health and safety",
              "description": "Keeping people safe at work.",
              "customFields": {},
              "createdAt": "2025-09-02T08:05:00\u002B00:00",
              "updatedAt": "2025-09-02T08:05:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/tags/01990975-33e0-75c7-b2e1-b595a665fce8",
                "list": "/v1/tags",
                "update": "/v1/tags/01990975-33e0-75c7-b2e1-b595a665fce8",
                "delete": "/v1/tags/01990975-33e0-75c7-b2e1-b595a665fce8"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5OTA5NzUtMzNlMC03NWM3LWIyZTEtYjU5NWE2NjVmY2U4In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/tags",
            "create": "/v1/tags"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/TagDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfTagGroupDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "01990975-33e0-718c-87a3-27c01cd74192",
              "name": "Topic",
              "description": "What a course or lesson is about, for filtering the catalogue.",
              "exclusive": false,
              "appliesTo": [
                "course",
                "lesson"
              ],
              "customFields": {},
              "createdAt": "2025-09-02T08:05:00\u002B00:00",
              "updatedAt": "2025-09-02T08:05:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/tag-groups/01990975-33e0-718c-87a3-27c01cd74192",
                "list": "/v1/tag-groups",
                "update": "/v1/tag-groups/01990975-33e0-718c-87a3-27c01cd74192",
                "delete": "/v1/tag-groups/01990975-33e0-718c-87a3-27c01cd74192"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5OTA5NzUtMzNlMC03MThjLTg3YTMtMjdjMDFjZDc0MTkyIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/tag-groups",
            "create": "/v1/tag-groups"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/TagGroupDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfUserDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "idpSubjectId": "0199b9ad-d000-7aca-a056-7ef80dedf5cb",
              "displayName": "Priya Shah",
              "status": "active",
              "createdAt": "2025-10-06T13:20:00\u002B00:00",
              "updatedAt": "2025-10-06T13:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897",
                "contact": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897/contact",
                "list": "/v1/users",
                "update": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897",
                "deactivate": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897:deactivate",
                "erase": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897:erase",
                "export": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897:export",
                "progress": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897/progress"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5OWI5YWQtZDAwMC03ZmVkLWJjY2QtZmQ5Yzk2YTE4ODk3In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/users"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/UserDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfUserProgressItemDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
              "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "courseTitle": "Working at height",
              "completionModel": "all_activities_completed",
              "percentComplete": 100,
              "completed": 1,
              "total": 1,
              "bestScore": 0.8,
              "minimumAssessmentScore": null,
              "status": "completed",
              "deadline": "2026-03-31T17:00:00\u002B00:00",
              "isMandatory": true,
              "completedAt": "2026-03-05T14:12:00\u002B00:00",
              "_links": {
                "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
                "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
                "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzY1YWQtOTI4MC03NGNiLTlmZGItZjYxMWYzYWQ2ZGM4In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897/progress"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/UserProgressItemDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfUserReportDetailRowDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
              "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "courseTitle": "Working at height",
              "percentComplete": 100,
              "status": "completed",
              "deadline": "2026-03-31T17:00:00\u002B00:00",
              "isMandatory": true,
              "completedAt": "2026-03-05T14:12:00\u002B00:00",
              "_links": {
                "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
                "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
                "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzY1YWQtOTI4MC03NGNiLTlmZGItZjYxMWYzYWQ2ZGM4In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/reports/users/0199b9ad-d000-7fed-bccd-fd9c96a18897",
            "report": "/v1/reports/users"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/UserReportDetailRowDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfUserReportRowDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "displayName": "Priya Shah",
              "enrolled": 3,
              "completed": 2,
              "overdue": 0,
              "mandatoryOutstanding": 1,
              "_links": {
                "self": "/v1/reports/users/0199b9ad-d000-7fed-bccd-fd9c96a18897",
                "user": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5OWI5YWQtZDAwMC03ZmVkLWJjY2QtZmQ5Yzk2YTE4ODk3In0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/reports/users"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/UserReportRowDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfWebhookSubscriptionDto": {
        "description": "One page of a list. \u0060data\u0060 holds the items in order, \u0060pagination\u0060 says whether\nmore follow and how to ask for them, and \u0060_links\u0060 lists what the caller may do with\nthe collection.",
        "example": {
          "data": [
            {
              "id": "019c2416-cd00-7b0f-b522-09fce53deff2",
              "name": "Completions to HR",
              "targetUrl": "https://hr.example.com/hooks/seren",
              "eventTypes": [
                "course_completion.created",
                "enrolment.created"
              ],
              "active": true,
              "createdAt": "2026-02-03T15:20:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/webhook-subscriptions/019c2416-cd00-7b0f-b522-09fce53deff2",
                "list": "/v1/webhook-subscriptions",
                "update": "/v1/webhook-subscriptions/019c2416-cd00-7b0f-b522-09fce53deff2",
                "delete": "/v1/webhook-subscriptions/019c2416-cd00-7b0f-b522-09fce53deff2"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "eyJzb3J0IjoiIiwia2V5cyI6W10sImlkIjoiMDE5YzI0MTYtY2QwMC03YjBmLWI1MjItMDlmY2U1M2RlZmYyIn0",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/webhook-subscriptions",
            "create": "/v1/webhook-subscriptions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/WebhookSubscriptionDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "DataExportAccepted": {
        "description": "The 202 response of \u0060POST /v1/users/{id}:export\u0060. \u0060jobId\u0060 identifies the\nexport job; poll \u0060GET /v1/data-exports/{jobId}\u0060 for its outcome.",
        "example": {
          "jobId": "019cf5d1-e6e0-7bea-afd6-90d3587e1f12"
        },
        "properties": {
          "jobId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "jobId"
        ],
        "type": "object"
      },
      "DataExportDto": {
        "description": "A personal-data export job, as polled at \u0060GET /v1/data-exports/{jobId}\u0060.\n\u0060subjectUserId\u0060 is the user whose data is exported. \u0060downloadUrl\u0060 is set only\nonce \u0060status\u0060 is \u0060succeeded\u0060 and until \u0060expiresAt\u0060 passes; it is a\nshort-lived, read-only link issued fresh on each read, so fetch it again rather than\nstoring it. \u0060error\u0060 gives a general reason when the job has \u0060failed\u0060.",
        "example": {
          "id": "019cf5d1-e6e0-7bea-afd6-90d3587e1f12",
          "subjectUserId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "status": "succeeded",
          "downloadUrl": "https://exports.example.com/data-export.zip?signature=example-signature",
          "expiresAt": "2026-03-23T08:45:00\u002B00:00",
          "error": null,
          "createdAt": "2026-03-16T08:45:00\u002B00:00",
          "completedAt": "2026-03-16T08:48:00\u002B00:00",
          "_links": {
            "self": "/v1/data-exports/019cf5d1-e6e0-7bea-afd6-90d3587e1f12"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "completedAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "downloadUrl": {
            "format": "uri",
            "type": [
              "null",
              "string"
            ]
          },
          "error": {
            "type": [
              "null",
              "string"
            ]
          },
          "expiresAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/ExportStatus"
          },
          "subjectUserId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "subjectUserId",
          "status",
          "downloadUrl",
          "expiresAt",
          "error",
          "createdAt",
          "completedAt",
          "_links"
        ],
        "type": "object"
      },
      "EnrolmentDto": {
        "description": "An enrolment of a user on a course. \u0060courseFamilyId\u0060 identifies the course\nacross its versions, and a user has at most one active enrolment per course family;\n\u0060courseVersionId\u0060 is the course version the enrolment is pinned to. \u0060method\u0060\nrecords how the enrolment was made. \u0060deletedAt\u0060 is set on a withdrawn enrolment,\nwhich lists return only with \u0060?includeDeleted=true\u0060.",
        "example": {
          "id": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "courseFamilyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
          "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "deadline": "2026-03-31T17:00:00\u002B00:00",
          "isMandatory": true,
          "method": "admin",
          "deletedAt": null,
          "createdAt": "2026-02-16T09:00:00\u002B00:00",
          "updatedAt": "2026-02-16T09:00:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
            "completions": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/completions",
            "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
            "update": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
            "withdraw": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "courseFamilyId": {
            "format": "uuid",
            "type": "string"
          },
          "courseVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "deadline": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "deletedAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "isMandatory": {
            "type": "boolean"
          },
          "method": {
            "$ref": "#/components/schemas/EnrolmentMethod"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "userId",
          "courseFamilyId",
          "courseVersionId",
          "deadline",
          "isMandatory",
          "method",
          "deletedAt",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "EnrolmentMergePatch": {
        "description": "The body of \u0060PATCH /v1/enrolments/{id}\u0060, a JSON Merge Patch (RFC 7396): a field\nleft out is unchanged and a field sent is set. Only \u0060deadline\u0060 (which \u0060null\u0060\nclears) and \u0060isMandatory\u0060 (which cannot be \u0060null\u0060) can be changed, and the\nrequest needs \u0060enrolments:write:tenant\u0060, even for the caller\u0027s own enrolment.\nWithdrawing and restoring use \u0060DELETE /v1/enrolments/{id}\u0060 and\n\u0060:restore\u0060.",
        "example": {
          "deadline": "2026-04-14T17:00:00\u002B00:00"
        },
        "properties": {
          "deadline": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "isMandatory": {
            "type": [
              "null",
              "boolean"
            ]
          }
        },
        "type": "object"
      },
      "EnrolmentMethod": {
        "description": "How an enrolment was made: \u0060self\u0060 when learners enrolled themselves,\n\u0060admin\u0060 when a caller with tenant-wide enrolment rights enrolled them, and\n\u0060bulk\u0060 when it came from \u0060POST /v1/enrolments:bulk\u0060. Set by the server and\nnever changed.",
        "enum": [
          "self",
          "admin",
          "bulk"
        ],
        "example": "self"
      },
      "ExportStatus": {
        "description": "Where a data-export job stands: \u0060pending\u0060 until it has run, then\n\u0060succeeded\u0060 or \u0060failed\u0060. Set by the server only.",
        "enum": [
          "pending",
          "succeeded",
          "failed"
        ],
        "example": "pending"
      },
      "HttpValidationProblemDetails": {
        "example": {
          "type": "https://errors.serenapp.io/validation.failed",
          "title": "One or more validation errors occurred.",
          "status": 422,
          "errors": {
            "limit": [
              "\u0027Limit\u0027 must be between 1 and 100. You entered 500."
            ]
          },
          "code": "validation.failed",
          "correlationId": "4bf92f3577b34da6a3ce929d0e0e4736"
        },
        "properties": {
          "code": {
            "description": "Stable, machine-readable error code (area.reason, e.g. question.not_found). Branch on this \u2014 not on status or type. Framework status responses carry an http.* code.",
            "type": "string"
          },
          "correlationId": {
            "description": "Request correlation id, also echoed on the X-Correlation-Id response header and in logs.",
            "type": "string"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "errors": {
            "additionalProperties": {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "type": "object"
          },
          "fields": {
            "description": "Submitted members the caller may not set; present on a field-denial 403.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "instance": {
            "type": [
              "null",
              "string"
            ]
          },
          "referencing": {
            "description": "Entities referencing this resource; present on an in-use deletion 409.",
            "items": {
              "properties": {
                "entityType": {
                  "type": "string"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "status": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "unmet": {
            "description": "Unmet prerequisites blocking a completion; present on a prerequisite 409.",
            "items": {
              "properties": {
                "entityType": {
                  "type": "string"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "code",
          "correlationId"
        ],
        "type": "object"
      },
      "JsonElement": {},
      "LearningObjectDto": {
        "description": "The object of a learning record: \u0060type\u0060 names its kind, and exactly one of\n\u0060id\u0060 (an object held by this API) or \u0060iri\u0060 (an object held elsewhere) is\nset.",
        "example": {
          "type": "activity",
          "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
          "iri": null
        },
        "properties": {
          "id": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "iri": {
            "type": [
              "null",
              "string"
            ]
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "type",
          "id",
          "iri"
        ],
        "type": "object"
      },
      "LearningObjectInput": {
        "description": "What a captured learning record is about. \u0060type\u0060 is required and names the kind\nof object (for example \u0060activity\u0060). Send exactly one of \u0060id\u0060, the id of an\nobject held by this API, or \u0060iri\u0060, an absolute IRI naming an object held\nelsewhere; sending both or neither is rejected with 422.",
        "example": {
          "type": "activity",
          "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
          "iri": null
        },
        "properties": {
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "iri": {
            "type": [
              "null",
              "string"
            ]
          },
          "type": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "type": "object"
      },
      "LearningRecordDto": {
        "description": "A learning record: an append-only, xAPI-style statement that the user named by\n\u0060actorUserId\u0060 did \u0060verb\u0060 to \u0060object\u0060, with any \u0060result\u0060 and\n\u0060context\u0060 returned as the JSON that was captured. \u0060recordedBy\u0060 is \u0060null\u0060\nwhen the learner recorded it themselves.",
        "example": {
          "id": "019cbe57-5380-731c-bd90-540fe6a96dd7",
          "actorUserId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "verb": "http://adlnet.gov/expapi/verbs/completed",
          "object": {
            "type": "activity",
            "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "iri": null
          },
          "result": {
            "score": {
              "scaled": 0.8
            },
            "success": true,
            "completion": true
          },
          "context": {
            "registration": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
          },
          "recordedBy": null,
          "createdAt": "2026-03-05T14:12:00\u002B00:00",
          "_links": {
            "self": "/v1/learning-records/019cbe57-5380-731c-bd90-540fe6a96dd7"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "actorUserId": {
            "format": "uuid",
            "type": "string"
          },
          "context": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/JsonElement"
              }
            ]
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "object": {
            "$ref": "#/components/schemas/LearningObjectDto"
          },
          "recordedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "result": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/JsonElement"
              }
            ]
          },
          "verb": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "actorUserId",
          "verb",
          "object",
          "result",
          "context",
          "recordedBy",
          "createdAt",
          "_links"
        ],
        "type": "object"
      },
      "LessonCompletionDto": {
        "description": "A record that a user completed a lesson. Completions are append-only: completing\nagain adds a new record. \u0060lessonVersionId\u0060 is the lesson version completed.\n\u0060placementId\u0060 is the lesson placement it was completed through, or \u0060null\u0060\nfor a standalone completion; \u0060enrolmentId\u0060 is set when a placed completion matched\nexactly one active enrolment, else \u0060null\u0060. \u0060recordedBy\u0060 is \u0060null\u0060 when\nthe user recorded it themselves.",
        "example": {
          "id": "019cbe57-5380-7f76-a8d1-5189a92f79aa",
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "lessonVersionId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
          "placementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
          "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
          "score": null,
          "completedAt": "2026-03-05T14:12:00\u002B00:00",
          "createdAt": "2026-03-05T14:12:00\u002B00:00",
          "recordedBy": null,
          "_links": {
            "self": "/v1/lesson-completions/019cbe57-5380-7f76-a8d1-5189a92f79aa",
            "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "placement": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "completedAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "enrolmentId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lessonVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "placementId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "recordedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "score": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "userId",
          "lessonVersionId",
          "placementId",
          "enrolmentId",
          "score",
          "completedAt",
          "createdAt",
          "recordedBy",
          "_links"
        ],
        "type": "object"
      },
      "LessonDto": {
        "description": "A lesson: a reusable unit of learning made of placed activities, which courses place\ninto their modules. \u0060activitySequencing\u0060 decides whether its activities may be\ncompleted in any order or only in sequence. \u0060status\u0060 is read-only here and changes\nonly through the \u0060:publish\u0060, \u0060:unpublish\u0060 and \u0060:archive\u0060 actions.\n\u0060familyId\u0060 is shared by every version of the lesson, while \u0060id\u0060 identifies\nthis version. \u0060presentation\u0060 holds display settings the API stores and returns but\nnever interprets; \u0060managedTags\u0060 is always empty and \u0060customFields\u0060 always\n\u0060{}\u0060 for now.",
        "example": {
          "id": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
          "title": "Inspecting a ladder",
          "description": "What to look for before each use, and when to take a ladder out of service.",
          "activitySequencing": "any",
          "status": "published",
          "customFields": {},
          "presentation": {
            "schemaVersion": 2,
            "title": "Inspecting a ladder",
            "readingTime": 4,
            "blocks": [
              {
                "type": "https://serenapp.io/blocks/lead",
                "text": "Falls remain the most common cause of serious workplace injury."
              },
              {
                "type": "https://serenapp.io/blocks/h2",
                "text": "Before you climb"
              },
              {
                "type": "https://serenapp.io/blocks/list",
                "items": [
                  "Check the feet",
                  "Check the rungs",
                  "Check the locks"
                ]
              }
            ]
          },
          "managedTags": [
            "01990975-33e0-75c7-b2e1-b595a665fce8"
          ],
          "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
          "versionNumber": 1,
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "list": "/v1/lessons",
            "update": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "delete": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "latestPublished": "/v1/lessons/families/019bb1c1-6440-740d-b36a-5a49db5899fa/latest-published",
            "unpublish": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c:unpublish",
            "archive": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c:archive"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "activitySequencing": {
            "$ref": "#/components/schemas/SequencingMode"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "managedTags": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "type": "array"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "status": {
            "$ref": "#/components/schemas/ContentStatus"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "activitySequencing",
          "status",
          "customFields",
          "presentation",
          "managedTags",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "LessonMergePatch": {
        "description": "The body of \u0060PATCH /v1/lessons/{id}\u0060, a JSON Merge Patch (RFC 7396): a field left\nout is unchanged, a field sent is set, and \u0060null\u0060 clears a field that may be empty.\n\u0060null\u0060 for \u0060presentation\u0060 resets it to \u0060{}\u0060. \u0060status\u0060 is not a field\nhere; it changes only through the \u0060:publish\u0060, \u0060:unpublish\u0060 and \u0060:archive\u0060\nactions.",
        "example": {
          "activitySequencing": "linear"
        },
        "properties": {
          "activitySequencing": {
            "$ref": "#/components/schemas/SequencingMode"
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "title": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "LessonNodeProgressDto": {
        "description": "A user\u0027s completion state for one placed lesson in a course structure read.\n\u0060completed\u0060 says whether the lesson has been completed in this placement; when it\nhas, \u0060completionId\u0060 and \u0060completedAt\u0060 identify that completion, and otherwise\nthey are \u0060null\u0060.",
        "example": {
          "completed": true,
          "completionId": "019cbe57-5380-7f76-a8d1-5189a92f79aa",
          "completedAt": "2026-03-05T14:12:00\u002B00:00"
        },
        "properties": {
          "completed": {
            "type": "boolean"
          },
          "completedAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "completionId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "completed",
          "completionId",
          "completedAt"
        ],
        "type": "object"
      },
      "LessonPlacementDto": {
        "description": "A lesson placed in a module. \u0060lessonId\u0060 names the specific lesson version placed,\nand \u0060courseId\u0060 and \u0060moduleId\u0060 say where it is placed; \u0060courseId\u0060 is\nalways filled in. \u0060order\u0060 is its position among the module\u0027s placements, set by the\nserver: new placements are appended, and \u0060:reorder\u0060 moves them. \u0060presentation\u0060\nholds display settings for this placement only; \u0060customFields\u0060 is always \u0060{}\u0060\nfor now.",
        "example": {
          "id": "019c2416-cd00-7949-814a-d4c64cdb5328",
          "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "moduleId": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
          "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
          "order": 1,
          "customFields": {},
          "presentation": {},
          "familyId": "019c2416-cd00-7e95-97e1-faba65107204",
          "versionNumber": 1,
          "createdAt": "2026-02-03T15:20:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
            "module": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
            "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "update": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
            "delete": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
            "completions": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions",
            "submitCompletion": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "courseId": {
            "format": "uuid",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lessonId": {
            "format": "uuid",
            "type": "string"
          },
          "moduleId": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "courseId",
          "moduleId",
          "lessonId",
          "order",
          "customFields",
          "presentation",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "LessonPlacementMergePatch": {
        "description": "The body of \u0060PATCH /v1/lesson-placements/{id}\u0060, a JSON Merge Patch (RFC 7396): a\nfield left out is unchanged and a field sent is set. \u0060null\u0060 for \u0060presentation\u0060\nresets it to \u0060{}\u0060, and \u0060customFields\u0060 accepts only an empty object for now.\nThe position is not a field here; use \u0060:reorder\u0060 on the module\u0027s placements.",
        "example": {
          "presentation": {}
        },
        "properties": {
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          }
        },
        "type": "object"
      },
      "LessonStructureDto": {
        "description": "The response of \u0060GET /v1/lessons/{lessonId}/structure\u0060: a lesson\u0027s own fields with\nits placed activities in placement order, each activity in full. The course structure\nread nests this same object under each placed lesson. With \u0060?userId=\u0060 (a user id or\n\u0060me\u0060), each activity node carries that user\u0027s completion state in \u0060progress\u0060;\nthe lesson itself carries none, because lesson completion is recorded per placement in a\ncourse.",
        "example": {
          "id": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
          "title": "Inspecting a ladder",
          "description": "What to look for before each use, and when to take a ladder out of service.",
          "activitySequencing": "any",
          "status": "published",
          "customFields": {},
          "presentation": {
            "schemaVersion": 2,
            "title": "Inspecting a ladder",
            "readingTime": 4,
            "blocks": [
              {
                "type": "https://serenapp.io/blocks/lead",
                "text": "Falls remain the most common cause of serious workplace injury."
              },
              {
                "type": "https://serenapp.io/blocks/h2",
                "text": "Before you climb"
              },
              {
                "type": "https://serenapp.io/blocks/list",
                "items": [
                  "Check the feet",
                  "Check the rungs",
                  "Check the locks"
                ]
              }
            ]
          },
          "managedTags": [
            "01990975-33e0-75c7-b2e1-b595a665fce8"
          ],
          "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
          "versionNumber": 1,
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "activities": [
            {
              "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
              "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "order": 1,
              "required": true,
              "presentation": {},
              "activity": {
                "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "title": "Ladder safety check",
                "description": "Five questions on inspecting a ladder before use.",
                "componentUri": "https://serenapp.io/activities/quiz",
                "producesScore": true,
                "carriesQuestions": true,
                "status": "published",
                "definition": {},
                "customFields": {},
                "presentation": {},
                "managedTags": [],
                "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
                "versionNumber": 1,
                "createdAt": "2026-01-12T10:30:00\u002B00:00",
                "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "_links": {
                  "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                  "list": "/v1/activities",
                  "update": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                  "delete": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                  "latestPublished": "/v1/activities/families/019bb1c1-6440-7a1b-89fb-2ecc8c0ad419/latest-published",
                  "unpublish": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:unpublish",
                  "archive": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:archive"
                }
              },
              "progress": {
                "completed": true,
                "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
                "score": 0.8,
                "completedAt": "2026-03-05T14:12:00\u002B00:00"
              }
            }
          ],
          "_links": {
            "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/structure",
            "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "activities": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/activities"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "activities": {
            "items": {
              "$ref": "#/components/schemas/StructureActivityDto"
            },
            "type": "array"
          },
          "activitySequencing": {
            "$ref": "#/components/schemas/SequencingMode"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "managedTags": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "type": "array"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "status": {
            "$ref": "#/components/schemas/ContentStatus"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "activitySequencing",
          "status",
          "customFields",
          "presentation",
          "managedTags",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "activities",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfActivityPlacementDto": {
        "description": "A collection returned whole rather than in pages, because its parent bounds its size:\n\u0060data\u0060 holds every item in order, and \u0060_links\u0060 lists what the caller may do\nwith the collection.",
        "example": {
          "data": [
            {
              "id": "019c2416-cd00-7f88-9800-5cdd08a0d146",
              "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "order": 1,
              "required": true,
              "customFields": {},
              "presentation": {},
              "familyId": "019c2416-cd00-7453-9c0b-92ffc149261b",
              "versionNumber": 1,
              "createdAt": "2026-02-03T15:20:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
                "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "update": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
                "delete": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
                "completions": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions",
                "submitCompletion": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions"
              }
            }
          ],
          "_links": {
            "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/activity-placements",
            "create": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/activity-placements",
            "reorder": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/activity-placements:reorder"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/ActivityPlacementDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfBulkEnrolItemResult": {
        "description": "A collection returned whole rather than in pages, because its parent bounds its size:\n\u0060data\u0060 holds every item in order, and \u0060_links\u0060 lists what the caller may do\nwith the collection.",
        "example": {
          "data": [
            {
              "enrolment": {
                "id": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
                "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
                "courseFamilyId": "019bb1c1-6440-7aea-baf4-7992098170ae",
                "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
                "deadline": "2026-03-31T17:00:00\u002B00:00",
                "isMandatory": true,
                "method": "admin",
                "deletedAt": null,
                "createdAt": "2026-02-16T09:00:00\u002B00:00",
                "updatedAt": "2026-02-16T09:00:00\u002B00:00",
                "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "_links": {
                  "self": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
                  "completions": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/completions",
                  "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
                  "update": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
                  "withdraw": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
                }
              },
              "created": true
            }
          ],
          "_links": {
            "self": "/v1/enrolments",
            "enrol": "/v1/enrolments",
            "bulk": "/v1/enrolments:bulk"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/BulkEnrolItemResult"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfLessonPlacementDto": {
        "description": "A collection returned whole rather than in pages, because its parent bounds its size:\n\u0060data\u0060 holds every item in order, and \u0060_links\u0060 lists what the caller may do\nwith the collection.",
        "example": {
          "data": [
            {
              "id": "019c2416-cd00-7949-814a-d4c64cdb5328",
              "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "moduleId": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
              "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "order": 1,
              "customFields": {},
              "presentation": {},
              "familyId": "019c2416-cd00-7e95-97e1-faba65107204",
              "versionNumber": 1,
              "createdAt": "2026-02-03T15:20:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
                "module": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
                "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "update": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
                "delete": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
                "completions": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions",
                "submitCompletion": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions"
              }
            }
          ],
          "_links": {
            "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3/lesson-placements",
            "create": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3/lesson-placements",
            "reorder": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3/lesson-placements:reorder"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/LessonPlacementDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfModuleDto": {
        "description": "A collection returned whole rather than in pages, because its parent bounds its size:\n\u0060data\u0060 holds every item in order, and \u0060_links\u0060 lists what the caller may do\nwith the collection.",
        "example": {
          "data": [
            {
              "id": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
              "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "title": "Before you climb",
              "description": "Checks to make before any work at height begins.",
              "lessonSequencing": "linear",
              "completionModel": null,
              "order": 1,
              "customFields": {},
              "presentation": {},
              "createdAt": "2026-01-12T10:30:00\u002B00:00",
              "updatedAt": "2026-01-12T10:30:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
                "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
                "update": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
                "delete": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3"
              }
            }
          ],
          "_links": {
            "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules",
            "create": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules",
            "reorder": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules:reorder"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/ModuleDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfPlacedActivityDto": {
        "description": "A collection returned whole rather than in pages, because its parent bounds its size:\n\u0060data\u0060 holds every item in order, and \u0060_links\u0060 lists what the caller may do\nwith the collection.",
        "example": {
          "data": [
            {
              "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
              "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "order": 1,
              "required": true,
              "presentation": {},
              "activity": {
                "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "title": "Ladder safety check",
                "description": "Five questions on inspecting a ladder before use.",
                "componentUri": "https://serenapp.io/activities/quiz",
                "producesScore": true,
                "carriesQuestions": true,
                "status": "published",
                "definition": {},
                "customFields": {},
                "presentation": {},
                "managedTags": [],
                "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
                "versionNumber": 1,
                "createdAt": "2026-01-12T10:30:00\u002B00:00",
                "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "_links": {
                  "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                  "list": "/v1/activities",
                  "update": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                  "delete": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                  "latestPublished": "/v1/activities/families/019bb1c1-6440-7a1b-89fb-2ecc8c0ad419/latest-published",
                  "unpublish": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:unpublish",
                  "archive": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:archive"
                }
              },
              "_links": {
                "self": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
                "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "completions": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions",
                "submitCompletion": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions"
              }
            }
          ],
          "_links": {
            "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/activities"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/PlacedActivityDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfPlacedLessonDto": {
        "description": "A collection returned whole rather than in pages, because its parent bounds its size:\n\u0060data\u0060 holds every item in order, and \u0060_links\u0060 lists what the caller may do\nwith the collection.",
        "example": {
          "data": [
            {
              "lessonPlacementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
              "moduleId": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
              "order": 1,
              "presentation": {},
              "lesson": {
                "id": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "title": "Inspecting a ladder",
                "description": "What to look for before each use, and when to take a ladder out of service.",
                "activitySequencing": "any",
                "status": "published",
                "customFields": {},
                "presentation": {
                  "schemaVersion": 2,
                  "title": "Inspecting a ladder",
                  "readingTime": 4,
                  "blocks": [
                    {
                      "type": "https://serenapp.io/blocks/lead",
                      "text": "Falls remain the most common cause of serious workplace injury."
                    },
                    {
                      "type": "https://serenapp.io/blocks/h2",
                      "text": "Before you climb"
                    },
                    {
                      "type": "https://serenapp.io/blocks/list",
                      "items": [
                        "Check the feet",
                        "Check the rungs",
                        "Check the locks"
                      ]
                    }
                  ]
                },
                "managedTags": [
                  "01990975-33e0-75c7-b2e1-b595a665fce8"
                ],
                "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
                "versionNumber": 1,
                "createdAt": "2026-01-12T10:30:00\u002B00:00",
                "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "_links": {
                  "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                  "list": "/v1/lessons",
                  "update": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                  "delete": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                  "latestPublished": "/v1/lessons/families/019bb1c1-6440-740d-b36a-5a49db5899fa/latest-published",
                  "unpublish": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c:unpublish",
                  "archive": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c:archive"
                }
              },
              "_links": {
                "self": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
                "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "module": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
                "modulePlacements": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3/lesson-placements",
                "completions": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions",
                "submitCompletion": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions"
              }
            }
          ],
          "_links": {
            "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3/lessons"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/PlacedLessonDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfPlacedQuestionDto": {
        "description": "A collection returned whole rather than in pages, because its parent bounds its size:\n\u0060data\u0060 holds every item in order, and \u0060_links\u0060 lists what the caller may do\nwith the collection.",
        "example": {
          "data": [
            {
              "questionPlacementId": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
              "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "order": 1,
              "required": true,
              "presentation": {},
              "question": {
                "id": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
                "type": "multiple_choice_single",
                "prompt": "What must you check before every climb?",
                "definition": {},
                "customFields": {},
                "presentation": {},
                "options": [
                  {
                    "id": "019bb1c1-6440-7eb0-ba7d-398bbc7e264c",
                    "order": 1,
                    "label": "The feet, rungs and locks",
                    "value": null
                  },
                  {
                    "id": "019bb1c1-6440-7eec-8330-12f6635ff33d",
                    "order": 2,
                    "label": "Only the paint",
                    "value": null
                  }
                ],
                "managedTags": [],
                "familyId": "019bb1c1-6440-7112-8ac9-8e55dbe33168",
                "versionNumber": 1,
                "createdAt": "2026-01-12T10:30:00\u002B00:00",
                "updatedAt": "2026-01-12T10:30:00\u002B00:00",
                "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "_links": {
                  "self": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
                  "list": "/v1/questions",
                  "update": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
                  "delete": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
                  "answerKey": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf/answer-key"
                }
              },
              "_links": {
                "self": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f",
                "question": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
                "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2"
              }
            }
          ],
          "_links": {
            "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2/questions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/PlacedQuestionDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfQuestionPlacementDto": {
        "description": "A collection returned whole rather than in pages, because its parent bounds its size:\n\u0060data\u0060 holds every item in order, and \u0060_links\u0060 lists what the caller may do\nwith the collection.",
        "example": {
          "data": [
            {
              "id": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
              "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "questionId": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
              "order": 1,
              "required": true,
              "customFields": {},
              "presentation": {},
              "familyId": "019c2416-cd00-7007-b94f-852667e9fcae",
              "versionNumber": 1,
              "createdAt": "2026-02-03T15:20:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "_links": {
                "self": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f",
                "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                "question": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
                "update": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f",
                "delete": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f"
              }
            }
          ],
          "_links": {
            "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2/question-placements",
            "create": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2/question-placements",
            "reorder": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2/question-placements:reorder"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/QuestionPlacementDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "MembershipSubjectBody": {
        "description": "The body of the membership \u0060:suspend\u0060 and \u0060:reactivate\u0060 actions: the identity\nprovider\u0027s subject for the user. A missing subject is rejected with 422.",
        "example": {
          "idpSubjectId": "0199b9ad-d000-7aca-a056-7ef80dedf5cb"
        },
        "properties": {
          "idpSubjectId": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "idpSubjectId"
        ],
        "type": "object"
      },
      "ModuleDto": {
        "description": "A module: a section of the course named by \u0060courseId\u0060, holding placed lessons.\n\u0060order\u0060 is its position among the course\u0027s modules, set by the server and changed\nwith \u0060:reorder\u0060. \u0060lessonSequencing\u0060 decides whether its lessons may be\ncompleted in any order or only in sequence. Modules are not versioned, so there is no\n\u0060status\u0060, \u0060familyId\u0060 or \u0060versionNumber\u0060; \u0060presentation\u0060 holds\ndisplay settings the API stores and returns but never interprets, and\n\u0060customFields\u0060 is always \u0060{}\u0060 for now.",
        "example": {
          "id": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
          "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "title": "Before you climb",
          "description": "Checks to make before any work at height begins.",
          "lessonSequencing": "linear",
          "completionModel": null,
          "order": 1,
          "customFields": {},
          "presentation": {},
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-01-12T10:30:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
            "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "update": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
            "delete": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "completionModel": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CompletionModel"
              }
            ]
          },
          "courseId": {
            "format": "uuid",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lessonSequencing": {
            "$ref": "#/components/schemas/SequencingMode"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "id",
          "courseId",
          "title",
          "description",
          "lessonSequencing",
          "completionModel",
          "order",
          "customFields",
          "presentation",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "ModuleMergePatch": {
        "description": "The body of \u0060PATCH /v1/courses/{courseId}/modules/{moduleId}\u0060, a JSON Merge Patch\n(RFC 7396): a field left out is unchanged, a field sent is set, and \u0060null\u0060 clears a\nfield that may be empty. \u0060null\u0060 for \u0060presentation\u0060 resets it to \u0060{}\u0060. The\nposition is not a field here; use \u0060:reorder\u0060 on the course\u0027s modules. A module\ncannot move to another course.",
        "example": {
          "title": "Before you climb: the checks"
        },
        "properties": {
          "completionModel": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CompletionModel"
              }
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "lessonSequencing": {
            "$ref": "#/components/schemas/SequencingMode"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "title": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "OutlineActivityDto": {
        "description": "An activity node of the course outline. \u0060activityPlacementId\u0060, \u0060order\u0060 and\n\u0060required\u0060 describe the placement; the remaining fields are the placed activity\u0027s,\nwith its id given as \u0060activityId\u0060. \u0060familyId\u0060 stays the same across versions\nof the activity, so use it to refer to the activity over time. The activity\u0027s\n\u0060definition\u0060 is not included.",
        "example": {
          "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
          "order": 1,
          "required": true,
          "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
          "title": "Ladder safety check",
          "description": "Five questions on inspecting a ladder before use.",
          "componentUri": "https://serenapp.io/activities/quiz",
          "producesScore": true,
          "carriesQuestions": true,
          "status": "published",
          "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
          "versionNumber": 1,
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "progress": {
            "completed": true,
            "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
            "score": 0.8,
            "completedAt": "2026-03-05T14:12:00\u002B00:00"
          }
        },
        "properties": {
          "activityId": {
            "format": "uuid",
            "type": "string"
          },
          "activityPlacementId": {
            "format": "uuid",
            "type": "string"
          },
          "carriesQuestions": {
            "type": "boolean"
          },
          "componentUri": {
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "producesScore": {
            "type": "boolean"
          },
          "progress": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ActivityProgressDto"
              }
            ]
          },
          "required": {
            "type": "boolean"
          },
          "status": {
            "$ref": "#/components/schemas/ContentStatus"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "activityPlacementId",
          "order",
          "required",
          "activityId",
          "title",
          "description",
          "componentUri",
          "producesScore",
          "carriesQuestions",
          "status",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "progress"
        ],
        "type": "object"
      },
      "OutlineLessonDto": {
        "description": "A lesson node of the course outline. \u0060lessonPlacementId\u0060 and \u0060order\u0060 describe\nthe placement; the remaining fields are the placed lesson\u0027s, with its id given as\n\u0060lessonId\u0060. Lesson completion is recorded against the placement, while the lesson\u0027s\nown routes use \u0060lessonId\u0060. When \u0060activitySequencing\u0060 is \u0060linear\u0060,\ncompleting an activity before the ones placed ahead of it is refused with 409\n\u0060completion.prerequisite_not_met\u0060. Draft lessons and activities appear only to\ncallers holding \u0060content:write\u0060.",
        "example": {
          "lessonPlacementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
          "order": 1,
          "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
          "title": "Inspecting a ladder",
          "description": "What to look for before each use, and when to take a ladder out of service.",
          "activitySequencing": "any",
          "status": "published",
          "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
          "versionNumber": 1,
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "progress": {
            "completed": true,
            "completionId": "019cbe57-5380-7f76-a8d1-5189a92f79aa",
            "completedAt": "2026-03-05T14:12:00\u002B00:00"
          },
          "activities": [
            {
              "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
              "order": 1,
              "required": true,
              "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "title": "Ladder safety check",
              "description": "Five questions on inspecting a ladder before use.",
              "componentUri": "https://serenapp.io/activities/quiz",
              "producesScore": true,
              "carriesQuestions": true,
              "status": "published",
              "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
              "versionNumber": 1,
              "createdAt": "2026-01-12T10:30:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "progress": {
                "completed": true,
                "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
                "score": 0.8,
                "completedAt": "2026-03-05T14:12:00\u002B00:00"
              }
            }
          ]
        },
        "properties": {
          "activities": {
            "items": {
              "$ref": "#/components/schemas/OutlineActivityDto"
            },
            "type": "array"
          },
          "activitySequencing": {
            "$ref": "#/components/schemas/SequencingMode"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "lessonId": {
            "format": "uuid",
            "type": "string"
          },
          "lessonPlacementId": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "progress": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/LessonNodeProgressDto"
              }
            ]
          },
          "status": {
            "$ref": "#/components/schemas/ContentStatus"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "lessonPlacementId",
          "order",
          "lessonId",
          "title",
          "description",
          "activitySequencing",
          "status",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "progress",
          "activities"
        ],
        "type": "object"
      },
      "OutlineModuleDto": {
        "description": "A module node of the course outline, with its placed lessons in placement order. Modules\nare not versioned, so the node has no \u0060status\u0060, \u0060familyId\u0060 or\n\u0060versionNumber\u0060. When \u0060lessonSequencing\u0060 is \u0060linear\u0060, completing a lesson\nbefore the ones placed ahead of it is refused with 409\n\u0060completion.prerequisite_not_met\u0060, so a navigation view can use it to show which\nlessons are reachable.",
        "example": {
          "id": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
          "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "title": "Before you climb",
          "description": "Checks to make before any work at height begins.",
          "lessonSequencing": "linear",
          "completionModel": null,
          "order": 1,
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-01-12T10:30:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "lessons": [
            {
              "lessonPlacementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
              "order": 1,
              "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "title": "Inspecting a ladder",
              "description": "What to look for before each use, and when to take a ladder out of service.",
              "activitySequencing": "any",
              "status": "published",
              "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
              "versionNumber": 1,
              "createdAt": "2026-01-12T10:30:00\u002B00:00",
              "updatedAt": "2026-02-03T15:20:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "progress": {
                "completed": true,
                "completionId": "019cbe57-5380-7f76-a8d1-5189a92f79aa",
                "completedAt": "2026-03-05T14:12:00\u002B00:00"
              },
              "activities": [
                {
                  "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
                  "order": 1,
                  "required": true,
                  "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
                  "title": "Ladder safety check",
                  "description": "Five questions on inspecting a ladder before use.",
                  "componentUri": "https://serenapp.io/activities/quiz",
                  "producesScore": true,
                  "carriesQuestions": true,
                  "status": "published",
                  "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
                  "versionNumber": 1,
                  "createdAt": "2026-01-12T10:30:00\u002B00:00",
                  "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                  "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                  "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                  "progress": {
                    "completed": true,
                    "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
                    "score": 0.8,
                    "completedAt": "2026-03-05T14:12:00\u002B00:00"
                  }
                }
              ]
            }
          ]
        },
        "properties": {
          "completionModel": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CompletionModel"
              }
            ]
          },
          "courseId": {
            "format": "uuid",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lessonSequencing": {
            "$ref": "#/components/schemas/SequencingMode"
          },
          "lessons": {
            "items": {
              "$ref": "#/components/schemas/OutlineLessonDto"
            },
            "type": "array"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "id",
          "courseId",
          "title",
          "description",
          "lessonSequencing",
          "completionModel",
          "order",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "lessons"
        ],
        "type": "object"
      },
      "PlacedActivityDto": {
        "description": "One item of \u0060GET /v1/lessons/{lessonId}/activities\u0060: an activity placed in the\nlesson, in placement order. \u0060activity\u0060 is the full activity as its own read returns\nit. \u0060activityPlacementId\u0060, \u0060lessonId\u0060, \u0060order\u0060, \u0060required\u0060 and\n\u0060presentation\u0060 belong to the placement; \u0060required\u0060 is \u0060false\u0060 for\noptional practice that does not count towards completion, and the placement\u0027s\n\u0060presentation\u0060 is separate from the activity\u0027s own.",
        "example": {
          "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
          "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
          "order": 1,
          "required": true,
          "presentation": {},
          "activity": {
            "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "title": "Ladder safety check",
            "description": "Five questions on inspecting a ladder before use.",
            "componentUri": "https://serenapp.io/activities/quiz",
            "producesScore": true,
            "carriesQuestions": true,
            "status": "published",
            "definition": {},
            "customFields": {},
            "presentation": {},
            "managedTags": [],
            "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
            "versionNumber": 1,
            "createdAt": "2026-01-12T10:30:00\u002B00:00",
            "updatedAt": "2026-02-03T15:20:00\u002B00:00",
            "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "_links": {
              "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "list": "/v1/activities",
              "update": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "delete": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "latestPublished": "/v1/activities/families/019bb1c1-6440-7a1b-89fb-2ecc8c0ad419/latest-published",
              "unpublish": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:unpublish",
              "archive": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:archive"
            }
          },
          "_links": {
            "self": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146",
            "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "completions": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions",
            "submitCompletion": "/v1/activity-placements/019c2416-cd00-7f88-9800-5cdd08a0d146/completions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "activity": {
            "$ref": "#/components/schemas/ActivityDto"
          },
          "activityPlacementId": {
            "format": "uuid",
            "type": "string"
          },
          "lessonId": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "required": {
            "type": "boolean"
          }
        },
        "required": [
          "activityPlacementId",
          "lessonId",
          "order",
          "required",
          "presentation",
          "activity",
          "_links"
        ],
        "type": "object"
      },
      "PlacedLessonDto": {
        "description": "One item of \u0060GET /v1/courses/{courseId}/lessons\u0060 and\n\u0060GET /v1/courses/{courseId}/modules/{moduleId}/lessons\u0060: a lesson placed in a\nmodule, in placement order. \u0060lesson\u0060 is the full lesson as its own read returns it.\n\u0060lessonPlacementId\u0060, \u0060moduleId\u0060, \u0060order\u0060 and \u0060presentation\u0060 belong\nto the placement, and the placement\u0027s \u0060presentation\u0060 applies in this module only,\nseparate from the lesson\u0027s own.",
        "example": {
          "lessonPlacementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
          "moduleId": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
          "order": 1,
          "presentation": {},
          "lesson": {
            "id": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "title": "Inspecting a ladder",
            "description": "What to look for before each use, and when to take a ladder out of service.",
            "activitySequencing": "any",
            "status": "published",
            "customFields": {},
            "presentation": {
              "schemaVersion": 2,
              "title": "Inspecting a ladder",
              "readingTime": 4,
              "blocks": [
                {
                  "type": "https://serenapp.io/blocks/lead",
                  "text": "Falls remain the most common cause of serious workplace injury."
                },
                {
                  "type": "https://serenapp.io/blocks/h2",
                  "text": "Before you climb"
                },
                {
                  "type": "https://serenapp.io/blocks/list",
                  "items": [
                    "Check the feet",
                    "Check the rungs",
                    "Check the locks"
                  ]
                }
              ]
            },
            "managedTags": [
              "01990975-33e0-75c7-b2e1-b595a665fce8"
            ],
            "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
            "versionNumber": 1,
            "createdAt": "2026-01-12T10:30:00\u002B00:00",
            "updatedAt": "2026-02-03T15:20:00\u002B00:00",
            "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "_links": {
              "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "list": "/v1/lessons",
              "update": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "delete": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "latestPublished": "/v1/lessons/families/019bb1c1-6440-740d-b36a-5a49db5899fa/latest-published",
              "unpublish": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c:unpublish",
              "archive": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c:archive"
            }
          },
          "_links": {
            "self": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328",
            "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "module": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
            "modulePlacements": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3/lesson-placements",
            "completions": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions",
            "submitCompletion": "/v1/lesson-placements/019c2416-cd00-7949-814a-d4c64cdb5328/completions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "lesson": {
            "$ref": "#/components/schemas/LessonDto"
          },
          "lessonPlacementId": {
            "format": "uuid",
            "type": "string"
          },
          "moduleId": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          }
        },
        "required": [
          "lessonPlacementId",
          "moduleId",
          "order",
          "presentation",
          "lesson",
          "_links"
        ],
        "type": "object"
      },
      "PlacedQuestionDto": {
        "description": "One item of \u0060GET /v1/activities/{activityId}/questions\u0060: a question placed in the\nactivity, in placement order. \u0060question\u0060 is the full question as its own read\nreturns it. \u0060questionPlacementId\u0060, \u0060activityId\u0060, \u0060order\u0060,\n\u0060required\u0060 and \u0060presentation\u0060 belong to the placement: \u0060required\u0060 is true\nwhen every completion submitted for the activity must answer this placement, and the\nplacement\u0027s \u0060presentation\u0060 applies in this activity only, separate from the\nquestion\u0027s own.",
        "example": {
          "questionPlacementId": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
          "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
          "order": 1,
          "required": true,
          "presentation": {},
          "question": {
            "id": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
            "type": "multiple_choice_single",
            "prompt": "What must you check before every climb?",
            "definition": {},
            "customFields": {},
            "presentation": {},
            "options": [
              {
                "id": "019bb1c1-6440-7eb0-ba7d-398bbc7e264c",
                "order": 1,
                "label": "The feet, rungs and locks",
                "value": null
              },
              {
                "id": "019bb1c1-6440-7eec-8330-12f6635ff33d",
                "order": 2,
                "label": "Only the paint",
                "value": null
              }
            ],
            "managedTags": [],
            "familyId": "019bb1c1-6440-7112-8ac9-8e55dbe33168",
            "versionNumber": 1,
            "createdAt": "2026-01-12T10:30:00\u002B00:00",
            "updatedAt": "2026-01-12T10:30:00\u002B00:00",
            "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "_links": {
              "self": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
              "list": "/v1/questions",
              "update": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
              "delete": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
              "answerKey": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf/answer-key"
            }
          },
          "_links": {
            "self": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f",
            "question": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
            "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "activityId": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "question": {
            "$ref": "#/components/schemas/QuestionDto"
          },
          "questionPlacementId": {
            "format": "uuid",
            "type": "string"
          },
          "required": {
            "type": "boolean"
          }
        },
        "required": [
          "questionPlacementId",
          "activityId",
          "order",
          "required",
          "presentation",
          "question",
          "_links"
        ],
        "type": "object"
      },
      "PlanFeatureDto": {
        "example": {
          "key": "media_hosting",
          "enabled": true
        },
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "key": {
            "type": "string"
          }
        },
        "required": [
          "key",
          "enabled"
        ],
        "type": "object"
      },
      "PlanFeaturesDto": {
        "example": {
          "features": [
            {
              "key": "custom_activities",
              "enabled": true
            },
            {
              "key": "external_embeds",
              "enabled": false
            },
            {
              "key": "media_hosting",
              "enabled": true
            }
          ]
        },
        "properties": {
          "features": {
            "items": {
              "$ref": "#/components/schemas/PlanFeatureDto"
            },
            "type": "array"
          }
        },
        "required": [
          "features"
        ],
        "type": "object"
      },
      "ProblemDetails": {
        "properties": {
          "code": {
            "description": "Stable, machine-readable error code (area.reason, e.g. question.not_found). Branch on this \u2014 not on status or type. Framework status responses carry an http.* code.",
            "type": "string"
          },
          "correlationId": {
            "description": "Request correlation id, also echoed on the X-Correlation-Id response header and in logs.",
            "type": "string"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "fields": {
            "description": "Submitted members the caller may not set; present on a field-denial 403.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "instance": {
            "type": [
              "null",
              "string"
            ]
          },
          "referencing": {
            "description": "Entities referencing this resource; present on an in-use deletion 409.",
            "items": {
              "properties": {
                "entityType": {
                  "type": "string"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "status": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "unmet": {
            "description": "Unmet prerequisites blocking a completion; present on a prerequisite 409.",
            "items": {
              "properties": {
                "entityType": {
                  "type": "string"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "code",
          "correlationId"
        ],
        "type": "object"
      },
      "ProgressDto": {
        "description": "One enrolment\u0027s progress, computed by the server according to the course\u0027s\n\u0060completionModel\u0060. \u0060percentComplete\u0060 is the headline figure. \u0060completed\u0060\nand \u0060total\u0060 are the counts behind it under \u0060all_activities_completed\u0060 and\n\u0060all_lessons_completed\u0060, and \u0060null\u0060 under the other models. \u0060bestScore\u0060 is\nthe user\u0027s best score on the course and \u0060minimumAssessmentScore\u0060 the course\u0027s pass\nmark, if any. \u0060completion\u0060 is the enrolment\u0027s first course completion, or\n\u0060null\u0060 while it has none.",
        "example": {
          "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "completionModel": "all_activities_completed",
          "percentComplete": 100,
          "completed": 1,
          "total": 1,
          "bestScore": 0.8,
          "minimumAssessmentScore": null,
          "completion": {
            "id": "019cbe57-5380-7375-86b4-a74f3f4da0ac",
            "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
            "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
            "score": 0.8,
            "completedAt": "2026-03-05T14:12:00\u002B00:00",
            "createdAt": "2026-03-05T14:12:00\u002B00:00",
            "recordedBy": null,
            "triggeredByCompletionId": "019cbe57-5380-704a-9f16-595215e71aa0",
            "_links": {
              "self": "/v1/course-completions/019cbe57-5380-7375-86b4-a74f3f4da0ac",
              "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8"
            }
          },
          "_links": {
            "self": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
            "completions": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/completions",
            "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
            "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "bestScore": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "completed": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "completion": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CourseCompletionDto"
              }
            ]
          },
          "completionModel": {
            "type": "string"
          },
          "courseVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "enrolmentId": {
            "format": "uuid",
            "type": "string"
          },
          "minimumAssessmentScore": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "percentComplete": {
            "format": "double",
            "type": "number"
          },
          "total": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "enrolmentId",
          "userId",
          "courseVersionId",
          "completionModel",
          "percentComplete",
          "completed",
          "total",
          "bestScore",
          "minimumAssessmentScore",
          "completion",
          "_links"
        ],
        "type": "object"
      },
      "QuestionAnswerKeyDto": {
        "description": "The response of \u0060GET /v1/questions/{id}/answer-key\u0060, the only read that returns a\nquestion\u0027s answer key; it requires both \u0060content:read\u0060 and \u0060answer-keys.read\u0060.\nOne flat shape serves every question type: \u0060answer\u0060 holds a \u0060true_false\u0060\nquestion\u0027s answer, \u0060acceptedAnswers\u0060 a \u0060short_answer\u0060 question\u0027s, and\n\u0060options\u0060 the per-option key of a multiple-choice question; \u0060answer\u0060 and\n\u0060acceptedAnswers\u0060 are \u0060null\u0060 on any other type. A question without a key, such\nas an unscored survey item, still returns 200, with its key fields \u0060null\u0060.",
        "example": {
          "id": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
          "options": [
            {
              "optionId": "019bb1c1-6440-7eb0-ba7d-398bbc7e264c",
              "isCorrect": true,
              "correctRank": null
            },
            {
              "optionId": "019bb1c1-6440-7eec-8330-12f6635ff33d",
              "isCorrect": false,
              "correctRank": null
            }
          ],
          "answer": null,
          "acceptedAnswers": null,
          "explanation": "A ladder fails at its feet, rungs or locks, so those are checked every time."
        },
        "properties": {
          "acceptedAnswers": {
            "items": {
              "type": "string"
            },
            "type": [
              "null",
              "array"
            ]
          },
          "answer": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "explanation": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "options": {
            "items": {
              "$ref": "#/components/schemas/QuestionOptionAnswerKeyDto"
            },
            "type": "array"
          }
        },
        "required": [
          "id",
          "options",
          "answer",
          "acceptedAnswers",
          "explanation"
        ],
        "type": "object"
      },
      "QuestionDto": {
        "description": "A question: a prompt, and for a multiple-choice question its answer choices, of the kind\nnamed by \u0060type\u0060. This read never contains the answer key: there is no\n\u0060explanation\u0060, \u0060options\u0060 say nothing about correctness, and the \u0060answer\u0060\nand \u0060acceptedAnswers\u0060 properties are removed from \u0060definition\u0060; the key is\nserved only by \u0060GET /v1/questions/{id}/answer-key\u0060. \u0060options\u0060 lists a\nmultiple-choice question\u0027s answer choices, or a \u0060likert\u0060 or \u0060rating\u0060\nquestion\u0027s scale points with their \u0060value\u0060, in order, and is empty for the other\ntypes.\n\u0060presentation\u0060 holds display settings the API stores and returns but never\ninterprets; \u0060managedTags\u0060 is always empty and \u0060customFields\u0060 always \u0060{}\u0060\nfor now.",
        "example": {
          "id": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
          "type": "multiple_choice_single",
          "prompt": "What must you check before every climb?",
          "definition": {},
          "customFields": {},
          "presentation": {},
          "options": [
            {
              "id": "019bb1c1-6440-7eb0-ba7d-398bbc7e264c",
              "order": 1,
              "label": "The feet, rungs and locks",
              "value": null
            },
            {
              "id": "019bb1c1-6440-7eec-8330-12f6635ff33d",
              "order": 2,
              "label": "Only the paint",
              "value": null
            }
          ],
          "managedTags": [],
          "familyId": "019bb1c1-6440-7112-8ac9-8e55dbe33168",
          "versionNumber": 1,
          "createdAt": "2026-01-12T10:30:00\u002B00:00",
          "updatedAt": "2026-01-12T10:30:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
            "list": "/v1/questions",
            "update": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
            "delete": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
            "answerKey": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf/answer-key"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "definition": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "managedTags": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "type": "array"
          },
          "options": {
            "items": {
              "$ref": "#/components/schemas/QuestionOptionDto"
            },
            "type": "array"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "prompt": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/QuestionType"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "type",
          "prompt",
          "definition",
          "customFields",
          "presentation",
          "options",
          "managedTags",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "QuestionMergePatch": {
        "description": "The body of \u0060PATCH /v1/questions/{id}\u0060, a JSON Merge Patch (RFC 7396): a field left\nout is unchanged, a field sent is set, and \u0060null\u0060 clears a field that may be empty.\n\u0060type\u0060 cannot be changed, and sending it is rejected with 422. \u0060options\u0060, when\nsent, is the complete list: options with a known \u0060id\u0060 are updated, options without\none are added, existing options left out are removed, and \u0060null\u0060 is rejected.\n\u0060definition\u0060 replaces the stored definition whole, except that its \u0060answer\u0060\nand \u0060acceptedAnswers\u0060 properties are kept when left out, set when sent, and removed\nwhen sent as \u0060null\u0060.",
        "example": {
          "prompt": "What must you check before every climb, however short?"
        },
        "properties": {
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "definition": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "explanation": {
            "type": [
              "null",
              "string"
            ]
          },
          "options": {
            "items": {
              "$ref": "#/components/schemas/QuestionOptionInput"
            },
            "type": "array"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "prompt": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/JsonElement"
          }
        },
        "type": "object"
      },
      "QuestionOptionAnswerKeyDto": {
        "description": "One option\u0027s entry in a question\u0027s answer key. \u0060optionId\u0060 matches the option\u0027s\n\u0060id\u0060 in the question read; \u0060isCorrect\u0060 and \u0060correctRank\u0060 are the key, and\nboth are \u0060null\u0060 for an unscored option.",
        "example": {
          "optionId": "019bb1c1-6440-7eb0-ba7d-398bbc7e264c",
          "isCorrect": true,
          "correctRank": null
        },
        "properties": {
          "correctRank": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "isCorrect": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "optionId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "optionId",
          "isCorrect",
          "correctRank"
        ],
        "type": "object"
      },
      "QuestionOptionDto": {
        "description": "One answer choice of a question: a stable \u0060id\u0060, its display position \u0060order\u0060\n(set by the server), its \u0060label\u0060, and an optional numeric \u0060value\u0060, for example\na point on a rating scale. It never says whether the option is correct; that is served\nonly by \u0060GET /v1/questions/{id}/answer-key\u0060.",
        "example": {
          "id": "019bb1c1-6440-7eb0-ba7d-398bbc7e264c",
          "order": 1,
          "label": "The feet, rungs and locks",
          "value": null
        },
        "properties": {
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "value": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          }
        },
        "required": [
          "id",
          "order",
          "label",
          "value"
        ],
        "type": "object"
      },
      "QuestionOptionInput": {
        "description": "One answer choice, or scale point, in the \u0060options\u0060 array of a question create or\nupdate; its position in the array sets its order. Leave out \u0060id\u0060 to add a new\noption; on update, an \u0060id\u0060 selects the existing option to change, and an id that is\nnot one of the question\u0027s options is rejected with 422. On a multiple-choice question,\n\u0060isCorrect\u0060 lets the server mark submitted answers itself; \u0060correctRank\u0060 is\nstored but never marked by the server. On a \u0060likert\u0060 or \u0060rating\u0060 question every\noption needs a \u0060value\u0060, no two the same, and \u0060isCorrect\u0060 and\n\u0060correctRank\u0060 are refused. \u0060value\u0060 is not kept when left out on update. On\nupdate, leaving out \u0060isCorrect\u0060 or \u0060correctRank\u0060 keeps the stored value,\nsending a value sets it, and sending \u0060null\u0060 clears it.",
        "example": {
          "id": "019bb1c1-6440-7eb0-ba7d-398bbc7e264c",
          "label": "The feet, rungs and locks",
          "value": null
        },
        "properties": {
          "correctRank": {
            "description": "This option\u0027s place in the correct order, where the question has one; stored, but\nnever marked by the server. On update, leave it out to keep the stored value, or\nsend \u0060null\u0060 to clear it.",
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "isCorrect": {
            "description": "Whether this option is a correct answer. On update, leave it out to keep the stored\nvalue, or send \u0060null\u0060 to clear it.",
            "type": [
              "null",
              "boolean"
            ]
          },
          "label": {
            "type": "string"
          },
          "value": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          }
        },
        "required": [
          "id",
          "label"
        ],
        "type": "object"
      },
      "QuestionPlacementDto": {
        "description": "A question placed in an activity. \u0060questionId\u0060 names the specific question version\nplaced, and \u0060order\u0060 is its position among the activity\u0027s placements, set by the\nserver: new placements are appended, and \u0060:reorder\u0060 moves them. \u0060required\u0060 is\ntrue when every completion submitted for the activity must answer this placement (a\nmissing, null, blank or empty answer is rejected with 422), and defaults to false.\n\u0060presentation\u0060 holds display settings for this placement only; \u0060customFields\u0060\nis always \u0060{}\u0060 for now.",
        "example": {
          "id": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
          "activityId": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
          "questionId": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
          "order": 1,
          "required": true,
          "customFields": {},
          "presentation": {},
          "familyId": "019c2416-cd00-7007-b94f-852667e9fcae",
          "versionNumber": 1,
          "createdAt": "2026-02-03T15:20:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f",
            "activity": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "question": "/v1/questions/019bb1c1-6440-7907-b51f-b0bb9872efdf",
            "update": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f",
            "delete": "/v1/question-placements/019c2416-cd00-7ad6-84e3-d14bf9586e0f"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "activityId": {
            "format": "uuid",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "familyId": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "questionId": {
            "format": "uuid",
            "type": "string"
          },
          "required": {
            "type": "boolean"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "versionNumber": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "activityId",
          "questionId",
          "order",
          "required",
          "customFields",
          "presentation",
          "familyId",
          "versionNumber",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "QuestionPlacementMergePatch": {
        "description": "The body of \u0060PATCH /v1/question-placements/{id}\u0060, a JSON Merge Patch (RFC 7396): a\nfield left out is unchanged and a field sent is set. \u0060required\u0060 takes \u0060true\u0060 or\n\u0060false\u0060, never \u0060null\u0060. \u0060null\u0060 for \u0060presentation\u0060 resets it to\n\u0060{}\u0060, and \u0060customFields\u0060 accepts only an empty object for now. The position is\nnot a field here; use \u0060:reorder\u0060 on the activity\u0027s placements.",
        "example": {
          "required": false
        },
        "properties": {
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "required": {
            "type": [
              "null",
              "boolean"
            ]
          }
        },
        "type": "object"
      },
      "QuestionResponseDto": {
        "description": "One answered question within an activity completion, returned only inside the\ncompletion\u0027s \u0060responses\u0060. \u0060response\u0060 is the submitted answer as raw JSON\n(option ids, a boolean, a number or free text). For a question the server can mark,\n\u0060isCorrect\u0060 and \u0060score\u0060 are computed by the server and are the authoritative\nresult; otherwise they are the values the caller supplied.",
        "example": {
          "id": "019cbe57-5380-7068-9012-eb773f7b168b",
          "questionPlacementId": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
          "questionVersionId": "019bb1c1-6440-7907-b51f-b0bb9872efdf",
          "isCorrect": true,
          "score": 1,
          "response": [
            "019bb1c1-6440-7eb0-ba7d-398bbc7e264c"
          ],
          "createdAt": "2026-03-05T14:12:00\u002B00:00"
        },
        "properties": {
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "isCorrect": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "questionPlacementId": {
            "format": "uuid",
            "type": "string"
          },
          "questionVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "response": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/JsonElement"
              }
            ]
          },
          "score": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          }
        },
        "required": [
          "id",
          "questionPlacementId",
          "questionVersionId",
          "isCorrect",
          "score",
          "response",
          "createdAt"
        ],
        "type": "object"
      },
      "QuestionType": {
        "description": "The kind of a question, fixed when it is created: \u0060multiple_choice_single\u0060 and\n\u0060multiple_choice_multi\u0060 offer answer choices in \u0060options\u0060, of which the\nlearner picks one or several; \u0060true_false\u0060 takes a true or false answer; and\n\u0060short_answer\u0060 takes a short text answer. Those two keep their answer in\n\u0060definition\u0060. \u0060likert\u0060 and \u0060rating\u0060 offer the points of a scale in\n\u0060options\u0060, each with a numeric \u0060value\u0060, of which the learner picks one; and\n\u0060free_text\u0060 takes an open written answer. These three are never keyed: the API\nrecords the answer and never marks it.",
        "enum": [
          "multiple_choice_single",
          "multiple_choice_multi",
          "true_false",
          "short_answer",
          "likert",
          "rating",
          "free_text"
        ],
        "example": "multiple_choice_single"
      },
      "ReadAgentAuthorityBody": {
        "description": "The body of \u0060POST /v1/agents/authority:read\u0060: the identity provider\u0027s subject for\nthe user who granted the delegation, the agent, and the delegation. Each is required; a\nmissing one is rejected with 422.",
        "example": {
          "idpSubjectId": "0199044e-d7e0-735a-bad6-e24671254235",
          "agentId": "019c2416-cd00-7767-a377-bd13a3d1cedb",
          "delegationId": "019c2416-cd00-7631-a7a9-795318683c22"
        },
        "properties": {
          "agentId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "delegationId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "idpSubjectId": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "idpSubjectId",
          "agentId",
          "delegationId"
        ],
        "type": "object"
      },
      "ReadSubjectPermissionsBody": {
        "description": "The body of \u0060POST /v1/users/permissions:read\u0060: the identity provider\u0027s subject for\nthe user. A missing subject is rejected with 422.",
        "example": {
          "idpSubjectId": "0199b9ad-d000-7aca-a056-7ef80dedf5cb"
        },
        "properties": {
          "idpSubjectId": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "idpSubjectId"
        ],
        "type": "object"
      },
      "ReorderActivityPlacementsRequest": {
        "description": "The body of \u0060POST /v1/lessons/{lessonId}/activity-placements:reorder\u0060; the lesson\nis named by the route. \u0060placementIds\u0060 lists every placement in the lesson exactly\nonce, in the new order; a list with a missing or extra id is rejected with 409.",
        "example": {
          "placementIds": [
            "019c2416-cd00-7f88-9800-5cdd08a0d146"
          ]
        },
        "properties": {
          "placementIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "placementIds"
        ],
        "type": "object"
      },
      "ReorderLessonPlacementsRequest": {
        "description": "The body of\n\u0060POST /v1/courses/{courseId}/modules/{moduleId}/lesson-placements:reorder\u0060; the\ncourse and module are named by the route. \u0060placementIds\u0060 lists every placement in\nthe module exactly once, in the new order; a list with a missing or extra id is rejected\nwith 409.",
        "example": {
          "placementIds": [
            "019c2416-cd00-7949-814a-d4c64cdb5328"
          ]
        },
        "properties": {
          "placementIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "placementIds"
        ],
        "type": "object"
      },
      "ReorderModulesRequest": {
        "description": "The body of \u0060POST /v1/courses/{courseId}/modules:reorder\u0060; the course is named by\nthe route. \u0060moduleIds\u0060 lists every module in the course exactly once, in the new\norder; a list with a missing or extra id is rejected with 409.",
        "example": {
          "moduleIds": [
            "019bb1c1-6440-7401-bc27-1f9ec1cf22c3"
          ]
        },
        "properties": {
          "moduleIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "moduleIds"
        ],
        "type": "object"
      },
      "ReorderQuestionPlacementsRequest": {
        "description": "The body of \u0060POST /v1/activities/{activityId}/question-placements:reorder\u0060; the\nactivity is named by the route. \u0060placementIds\u0060 lists every placement in the\nactivity exactly once, in the new order; a list with a missing or extra id is rejected\nwith 409.",
        "example": {
          "placementIds": [
            "019c2416-cd00-7ad6-84e3-d14bf9586e0f"
          ]
        },
        "properties": {
          "placementIds": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "placementIds"
        ],
        "type": "object"
      },
      "ReplacePermissionsBody": {
        "description": "The body of \u0060PUT /v1/users/{id}/permissions\u0060: the complete set of permissions the\nuser should hold, replacing whatever they hold now. \u0060permissions\u0060 is required; send\nan empty list to remove them all. It may list no more entries than there are assignable\npermissions.",
        "example": {
          "permissions": [
            "content:read",
            "enrolments:read",
            "progress:read",
            "progress:write"
          ]
        },
        "properties": {
          "permissions": {
            "items": {
              "type": "string"
            },
            "type": [
              "null",
              "array"
            ]
          }
        },
        "required": [
          "permissions"
        ],
        "type": "object"
      },
      "ResolvePermissionsBody": {
        "description": "The body of \u0060POST /v1/users/permissions:resolve\u0060: the identity provider\u0027s subject\nfor the signed-in user, the external provider that federated the sign-in if any, and the\nprovider\u0027s claims, from which a first sign-in\u0027s profile and starting permissions are\ndrawn. A missing subject is rejected with 422.",
        "example": {
          "idpSubjectId": "0199b9ad-d000-7aca-a056-7ef80dedf5cb",
          "externalProvider": null,
          "idpClaims": {
            "name": "Priya Shah",
            "email": "priya.shah@example.com"
          }
        },
        "properties": {
          "externalProvider": {
            "type": [
              "null",
              "string"
            ]
          },
          "idpClaims": {
            "additionalProperties": {
              "type": "string"
            },
            "type": [
              "null",
              "object"
            ]
          },
          "idpSubjectId": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "idpSubjectId",
          "externalProvider",
          "idpClaims"
        ],
        "type": "object"
      },
      "ResolvedPermissionsDto": {
        "description": "The result of \u0060POST /v1/users/permissions:resolve\u0060 and\n\u0060POST /v1/users/permissions:read\u0060, which a system caller uses when issuing a\ntoken for a signed-in user. \u0060userId\u0060 is the id of the user the sign-in belongs to,\nand \u0060permissions\u0060 the permissions that user holds now. It carries no\n\u0060_links\u0060.",
        "example": {
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "permissions": [
            "content:read",
            "enrolments:read",
            "progress:read",
            "progress:write"
          ]
        },
        "properties": {
          "permissions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "userId",
          "permissions"
        ],
        "type": "object"
      },
      "SequencingMode": {
        "description": "Whether the items placed in a lesson or module must be completed in order: \u0060any\u0060\nlets them be completed in any order, and \u0060linear\u0060 refuses to complete an item until\nevery item placed before it is complete, answering 409\n\u0060completion.prerequisite_not_met\u0060.",
        "enum": [
          "any",
          "linear"
        ],
        "example": "any"
      },
      "StructureActivityDto": {
        "description": "An activity node of a structure read: the placement (\u0060activityPlacementId\u0060,\n\u0060lessonId\u0060, \u0060order\u0060, \u0060required\u0060 and its own \u0060presentation\u0060) with the\nfull placed activity in \u0060activity\u0060. \u0060progress\u0060 is \u0060null\u0060 unless the read\nnamed a user; when it did, \u0060progress\u0060 is always present, with \u0060completed\u0060 set\nto \u0060false\u0060 if the user has not completed the activity.",
        "example": {
          "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
          "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
          "order": 1,
          "required": true,
          "presentation": {},
          "activity": {
            "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
            "title": "Ladder safety check",
            "description": "Five questions on inspecting a ladder before use.",
            "componentUri": "https://serenapp.io/activities/quiz",
            "producesScore": true,
            "carriesQuestions": true,
            "status": "published",
            "definition": {},
            "customFields": {},
            "presentation": {},
            "managedTags": [],
            "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
            "versionNumber": 1,
            "createdAt": "2026-01-12T10:30:00\u002B00:00",
            "updatedAt": "2026-02-03T15:20:00\u002B00:00",
            "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "_links": {
              "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "list": "/v1/activities",
              "update": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "delete": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
              "latestPublished": "/v1/activities/families/019bb1c1-6440-7a1b-89fb-2ecc8c0ad419/latest-published",
              "unpublish": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:unpublish",
              "archive": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:archive"
            }
          },
          "progress": {
            "completed": true,
            "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
            "score": 0.8,
            "completedAt": "2026-03-05T14:12:00\u002B00:00"
          }
        },
        "properties": {
          "activity": {
            "$ref": "#/components/schemas/ActivityDto"
          },
          "activityPlacementId": {
            "format": "uuid",
            "type": "string"
          },
          "lessonId": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "progress": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ActivityProgressDto"
              }
            ]
          },
          "required": {
            "type": "boolean"
          }
        },
        "required": [
          "activityPlacementId",
          "lessonId",
          "order",
          "required",
          "presentation",
          "activity",
          "progress"
        ],
        "type": "object"
      },
      "StructureModuleDto": {
        "description": "A module node of a course structure read: \u0060module\u0060 is the module as its own read\nreturns it, and \u0060lessons\u0060 its placed lessons in placement order.",
        "example": {
          "module": {
            "id": "019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
            "courseId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
            "title": "Before you climb",
            "description": "Checks to make before any work at height begins.",
            "lessonSequencing": "linear",
            "completionModel": null,
            "order": 1,
            "customFields": {},
            "presentation": {},
            "createdAt": "2026-01-12T10:30:00\u002B00:00",
            "updatedAt": "2026-01-12T10:30:00\u002B00:00",
            "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "_links": {
              "self": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
              "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3",
              "update": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3",
              "delete": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3/modules/019bb1c1-6440-7401-bc27-1f9ec1cf22c3"
            }
          },
          "lessons": [
            {
              "lessonPlacementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
              "order": 1,
              "presentation": {},
              "progress": {
                "completed": true,
                "completionId": "019cbe57-5380-7f76-a8d1-5189a92f79aa",
                "completedAt": "2026-03-05T14:12:00\u002B00:00"
              },
              "lesson": {
                "id": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "title": "Inspecting a ladder",
                "description": "What to look for before each use, and when to take a ladder out of service.",
                "activitySequencing": "any",
                "status": "published",
                "customFields": {},
                "presentation": {
                  "schemaVersion": 2,
                  "title": "Inspecting a ladder",
                  "readingTime": 4,
                  "blocks": [
                    {
                      "type": "https://serenapp.io/blocks/lead",
                      "text": "Falls remain the most common cause of serious workplace injury."
                    },
                    {
                      "type": "https://serenapp.io/blocks/h2",
                      "text": "Before you climb"
                    },
                    {
                      "type": "https://serenapp.io/blocks/list",
                      "items": [
                        "Check the feet",
                        "Check the rungs",
                        "Check the locks"
                      ]
                    }
                  ]
                },
                "managedTags": [
                  "01990975-33e0-75c7-b2e1-b595a665fce8"
                ],
                "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
                "versionNumber": 1,
                "createdAt": "2026-01-12T10:30:00\u002B00:00",
                "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                "activities": [
                  {
                    "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
                    "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
                    "order": 1,
                    "required": true,
                    "presentation": {},
                    "activity": {
                      "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
                      "title": "Ladder safety check",
                      "description": "Five questions on inspecting a ladder before use.",
                      "componentUri": "https://serenapp.io/activities/quiz",
                      "producesScore": true,
                      "carriesQuestions": true,
                      "status": "published",
                      "definition": {},
                      "customFields": {},
                      "presentation": {},
                      "managedTags": [],
                      "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
                      "versionNumber": 1,
                      "createdAt": "2026-01-12T10:30:00\u002B00:00",
                      "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                      "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                      "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                      "_links": {
                        "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                        "list": "/v1/activities",
                        "update": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                        "delete": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                        "latestPublished": "/v1/activities/families/019bb1c1-6440-7a1b-89fb-2ecc8c0ad419/latest-published",
                        "unpublish": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:unpublish",
                        "archive": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:archive"
                      }
                    },
                    "progress": {
                      "completed": true,
                      "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
                      "score": 0.8,
                      "completedAt": "2026-03-05T14:12:00\u002B00:00"
                    }
                  }
                ],
                "_links": {
                  "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/structure",
                  "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
                  "activities": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/activities"
                }
              }
            }
          ]
        },
        "properties": {
          "lessons": {
            "items": {
              "$ref": "#/components/schemas/StructurePlacedLessonDto"
            },
            "type": "array"
          },
          "module": {
            "$ref": "#/components/schemas/ModuleDto"
          }
        },
        "required": [
          "module",
          "lessons"
        ],
        "type": "object"
      },
      "StructurePlacedLessonDto": {
        "description": "A placed-lesson node of a course structure read. \u0060lessonPlacementId\u0060, \u0060order\u0060\nand \u0060presentation\u0060 belong to the placement, and \u0060lesson\u0060 is the lesson with\nits placed activities, exactly as \u0060GET /v1/lessons/{lessonId}/structure\u0060 returns\nit. \u0060progress\u0060 is \u0060null\u0060 unless the read named a user; when it did,\n\u0060progress\u0060 is always present.",
        "example": {
          "lessonPlacementId": "019c2416-cd00-7949-814a-d4c64cdb5328",
          "order": 1,
          "presentation": {},
          "progress": {
            "completed": true,
            "completionId": "019cbe57-5380-7f76-a8d1-5189a92f79aa",
            "completedAt": "2026-03-05T14:12:00\u002B00:00"
          },
          "lesson": {
            "id": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
            "title": "Inspecting a ladder",
            "description": "What to look for before each use, and when to take a ladder out of service.",
            "activitySequencing": "any",
            "status": "published",
            "customFields": {},
            "presentation": {
              "schemaVersion": 2,
              "title": "Inspecting a ladder",
              "readingTime": 4,
              "blocks": [
                {
                  "type": "https://serenapp.io/blocks/lead",
                  "text": "Falls remain the most common cause of serious workplace injury."
                },
                {
                  "type": "https://serenapp.io/blocks/h2",
                  "text": "Before you climb"
                },
                {
                  "type": "https://serenapp.io/blocks/list",
                  "items": [
                    "Check the feet",
                    "Check the rungs",
                    "Check the locks"
                  ]
                }
              ]
            },
            "managedTags": [
              "01990975-33e0-75c7-b2e1-b595a665fce8"
            ],
            "familyId": "019bb1c1-6440-740d-b36a-5a49db5899fa",
            "versionNumber": 1,
            "createdAt": "2026-01-12T10:30:00\u002B00:00",
            "updatedAt": "2026-02-03T15:20:00\u002B00:00",
            "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
            "activities": [
              {
                "activityPlacementId": "019c2416-cd00-7f88-9800-5cdd08a0d146",
                "lessonId": "019bb1c1-6440-78a8-9948-f74ea53fe47c",
                "order": 1,
                "required": true,
                "presentation": {},
                "activity": {
                  "id": "019bb1c1-6440-705b-87ea-3b86697e6cc2",
                  "title": "Ladder safety check",
                  "description": "Five questions on inspecting a ladder before use.",
                  "componentUri": "https://serenapp.io/activities/quiz",
                  "producesScore": true,
                  "carriesQuestions": true,
                  "status": "published",
                  "definition": {},
                  "customFields": {},
                  "presentation": {},
                  "managedTags": [],
                  "familyId": "019bb1c1-6440-7a1b-89fb-2ecc8c0ad419",
                  "versionNumber": 1,
                  "createdAt": "2026-01-12T10:30:00\u002B00:00",
                  "updatedAt": "2026-02-03T15:20:00\u002B00:00",
                  "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                  "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
                  "_links": {
                    "self": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                    "list": "/v1/activities",
                    "update": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                    "delete": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2",
                    "latestPublished": "/v1/activities/families/019bb1c1-6440-7a1b-89fb-2ecc8c0ad419/latest-published",
                    "unpublish": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:unpublish",
                    "archive": "/v1/activities/019bb1c1-6440-705b-87ea-3b86697e6cc2:archive"
                  }
                },
                "progress": {
                  "completed": true,
                  "completionId": "019cbe57-5380-704a-9f16-595215e71aa0",
                  "score": 0.8,
                  "completedAt": "2026-03-05T14:12:00\u002B00:00"
                }
              }
            ],
            "_links": {
              "self": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/structure",
              "lesson": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c",
              "activities": "/v1/lessons/019bb1c1-6440-78a8-9948-f74ea53fe47c/activities"
            }
          }
        },
        "properties": {
          "lesson": {
            "$ref": "#/components/schemas/LessonStructureDto"
          },
          "lessonPlacementId": {
            "format": "uuid",
            "type": "string"
          },
          "order": {
            "format": "int32",
            "type": "integer"
          },
          "presentation": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "progress": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/LessonNodeProgressDto"
              }
            ]
          }
        },
        "required": [
          "lessonPlacementId",
          "order",
          "presentation",
          "progress",
          "lesson"
        ],
        "type": "object"
      },
      "SubmitActivityCompletionRequest": {
        "description": "The body of \u0060POST /v1/activities/{activityId}/completions\u0060 and\n\u0060POST /v1/activity-placements/{placementId}/completions\u0060; the activity or placement\nis named by the route. \u0060userId\u0060 names the learner (or \u0060me\u0060) and defaults to\nthe caller. \u0060responses\u0060 records the question answers in the same request, at most\n200 of them; a longer list is refused with that one error. If\n\u0060score\u0060 is left out, the server derives it as the fraction of keyed questions\nanswered correctly, but only when it could mark every keyed question in the activity;\notherwise it stays \u0060null\u0060. \u0060id\u0060 optionally supplies the new completion\u0027s id.",
        "example": {
          "userId": null,
          "score": 0.8,
          "responses": [
            {
              "questionPlacementId": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
              "isCorrect": true,
              "score": 1,
              "response": [
                "019bb1c1-6440-7eb0-ba7d-398bbc7e264c"
              ]
            }
          ],
          "id": null
        },
        "properties": {
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "responses": {
            "items": {
              "$ref": "#/components/schemas/SubmitQuestionResponse"
            },
            "type": [
              "null",
              "array"
            ]
          },
          "score": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "userId": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "type": "object"
      },
      "SubmitCourseCompletionRequest": {
        "description": "The body of \u0060POST /v1/enrolments/{enrolmentId}/completions\u0060, which records a\ncourse completion for the enrolment\u0027s learner directly, as a sign-off or an\noverride, without checking the course\u0027s completion rules. It needs\n\u0060progress:write:tenant\u0060, even for the caller\u0027s own enrolment. The enrolment is\nnamed by the route. \u0060score\u0060 is optional, and \u0060id\u0060 optionally supplies the new\ncompletion\u0027s id.",
        "example": {
          "score": 0.8,
          "id": null
        },
        "properties": {
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "score": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          }
        },
        "type": "object"
      },
      "SubmitLessonCompletionRequest": {
        "description": "The body of \u0060POST /v1/lessons/{lessonId}/completions\u0060 and\n\u0060POST /v1/lesson-placements/{placementId}/completions\u0060; the lesson or placement\nis named by the route. \u0060userId\u0060 names the learner (or \u0060me\u0060) and defaults to\nthe caller. \u0060score\u0060 is optional, and \u0060id\u0060 optionally supplies the new\ncompletion\u0027s id.",
        "example": {
          "userId": null,
          "score": null,
          "id": null
        },
        "properties": {
          "id": {
            "type": [
              "null",
              "string"
            ]
          },
          "score": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "userId": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "type": "object"
      },
      "SubmitQuestionResponse": {
        "description": "One answer inside an activity completion\u0027s \u0060responses\u0060. \u0060questionPlacementId\u0060\nnames the question slot answered, and a submission may answer each slot at most once\n(a duplicate is rejected with 422). \u0060response\u0060 is the submitted answer. Where the\nserver can mark the question (a keyed multiple-choice, true/false or short-answer\nquestion), it computes \u0060isCorrect\u0060 and a \u0060score\u0060 of 0 or 1 itself, replacing\nany values sent, and \u0060response\u0060 must then be \u0060null\u0060 or of the question\u0027s shape\n(an array of option ids, a boolean or a string respectively), else 422. Elsewhere\n\u0060isCorrect\u0060 and \u0060score\u0060 are stored as sent, and both may be left out for an\nunscored question.",
        "example": {
          "questionPlacementId": "019c2416-cd00-7ad6-84e3-d14bf9586e0f",
          "isCorrect": true,
          "score": 1,
          "response": [
            "019bb1c1-6440-7eb0-ba7d-398bbc7e264c"
          ]
        },
        "properties": {
          "isCorrect": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "questionPlacementId": {
            "type": "string"
          },
          "response": {},
          "score": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          }
        },
        "required": [
          "questionPlacementId"
        ],
        "type": "object"
      },
      "TagDto": {
        "description": "A tag: a named label in the tag group named by \u0060tagGroupId\u0060, which never changes.\nTags are not versioned and cannot themselves be tagged, and \u0060customFields\u0060 is\nalways \u0060{}\u0060 for now.",
        "example": {
          "id": "01990975-33e0-75c7-b2e1-b595a665fce8",
          "tagGroupId": "01990975-33e0-718c-87a3-27c01cd74192",
          "name": "Health and safety",
          "description": "Keeping people safe at work.",
          "customFields": {},
          "createdAt": "2025-09-02T08:05:00\u002B00:00",
          "updatedAt": "2025-09-02T08:05:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/tags/01990975-33e0-75c7-b2e1-b595a665fce8",
            "list": "/v1/tags",
            "update": "/v1/tags/01990975-33e0-75c7-b2e1-b595a665fce8",
            "delete": "/v1/tags/01990975-33e0-75c7-b2e1-b595a665fce8"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "tagGroupId": {
            "format": "uuid",
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "id",
          "tagGroupId",
          "name",
          "description",
          "customFields",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "TagGroupDto": {
        "description": "A tag group: a named set of tags. \u0060appliesTo\u0060 names the entity types its tags are\nmeant for. Tag groups are not versioned and cannot themselves be tagged, and\n\u0060customFields\u0060 is always \u0060{}\u0060 for now.",
        "example": {
          "id": "01990975-33e0-718c-87a3-27c01cd74192",
          "name": "Topic",
          "description": "What a course or lesson is about, for filtering the catalogue.",
          "exclusive": false,
          "appliesTo": [
            "course",
            "lesson"
          ],
          "customFields": {},
          "createdAt": "2025-09-02T08:05:00\u002B00:00",
          "updatedAt": "2025-09-02T08:05:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/tag-groups/01990975-33e0-718c-87a3-27c01cd74192",
            "list": "/v1/tag-groups",
            "update": "/v1/tag-groups/01990975-33e0-718c-87a3-27c01cd74192",
            "delete": "/v1/tag-groups/01990975-33e0-718c-87a3-27c01cd74192"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "appliesTo": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "exclusive": {
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "exclusive",
          "appliesTo",
          "customFields",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "TagGroupMergePatch": {
        "description": "The body of \u0060PATCH /v1/tag-groups/{id}\u0060, a JSON Merge Patch (RFC 7396): a field\nleft out is unchanged, a field sent is set, and \u0060null\u0060 clears a field that may be\nempty. \u0060exclusive\u0060 and \u0060appliesTo\u0060 cannot be set to \u0060null\u0060; send an empty\n\u0060appliesTo\u0060 array to clear it.",
        "example": {
          "appliesTo": [
            "course",
            "lesson",
            "activity"
          ]
        },
        "properties": {
          "appliesTo": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "exclusive": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "name": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "TagMergePatch": {
        "description": "The body of \u0060PATCH /v1/tags/{id}\u0060, a JSON Merge Patch (RFC 7396): a field left out\nis unchanged, a field sent is set, and \u0060null\u0060 clears a field that may be empty. A\ntag cannot move to another group, so \u0060tagGroupId\u0060 is not a field here; create a new\ntag instead.",
        "example": {
          "name": "Health, safety and welfare"
        },
        "properties": {
          "customFields": {
            "$ref": "#/components/schemas/JsonElement"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "name": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "TenantSettingsDto": {
        "description": "The tenant\u0027s settings, read and changed at \u0060/v1/settings\u0060. A tenant that has\nnever changed a setting reads the defaults. \u0060allowSelfRecordedScores\u0060 (default\n\u0060true\u0060) decides whether learners recording their own completion may send a score;\nwhen it is \u0060false\u0060, a self-recorded completion that carries one is rejected with\n422 \u0060completion.self_recorded_score_not_allowed\u0060, while scores the server computes\nitself are unaffected.",
        "example": {
          "allowSelfRecordedScores": false,
          "_links": {
            "self": "/v1/settings",
            "update": "/v1/settings"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "allowSelfRecordedScores": {
            "type": "boolean"
          }
        },
        "required": [
          "allowSelfRecordedScores",
          "_links"
        ],
        "type": "object"
      },
      "TenantSettingsMergePatch": {
        "description": "The body of \u0060PATCH /v1/settings\u0060, a JSON Merge Patch (RFC 7396): a field left\nout is unchanged and a field sent is set. \u0060allowSelfRecordedScores\u0060 must be\n\u0060true\u0060 or \u0060false\u0060; any other value, \u0060null\u0060 included, is rejected with\n422.",
        "example": {
          "allowSelfRecordedScores": true
        },
        "properties": {
          "allowSelfRecordedScores": {
            "$ref": "#/components/schemas/JsonElement"
          }
        },
        "type": "object"
      },
      "UserContactDto": {
        "description": "A user\u0027s contact details, returned by \u0060GET /v1/users/{id}/contact\u0060, which needs\n\u0060users:read\u0060 and \u0060pii.read\u0060. \u0060contactEmail\u0060 is \u0060null\u0060 when none is\nrecorded. It carries no \u0060_links\u0060.",
        "example": {
          "id": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "contactEmail": "priya.shah@example.com"
        },
        "properties": {
          "contactEmail": {
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "contactEmail"
        ],
        "type": "object"
      },
      "UserDto": {
        "description": "A user in the tenant. \u0060idpSubjectId\u0060 identifies the user at the sign-in\nprovider and \u0060displayName\u0060 is the name shown for them. \u0060status\u0060 says whether\ntheir membership of the tenant is \u0060active\u0060 or \u0060suspended\u0060. The user\u0027s contact\nemail is not included here; it is read at \u0060GET /v1/users/{id}/contact\u0060, which\nneeds \u0060pii.read\u0060.",
        "example": {
          "id": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "idpSubjectId": "0199b9ad-d000-7aca-a056-7ef80dedf5cb",
          "displayName": "Priya Shah",
          "status": "active",
          "createdAt": "2025-10-06T13:20:00\u002B00:00",
          "updatedAt": "2025-10-06T13:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897",
            "contact": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897/contact",
            "list": "/v1/users",
            "update": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897",
            "deactivate": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897:deactivate",
            "erase": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897:erase",
            "export": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897:export",
            "progress": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897/progress"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "displayName": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "idpSubjectId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/UserStatus"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "id",
          "idpSubjectId",
          "displayName",
          "status",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "UserMergePatch": {
        "description": "The body of \u0060PATCH /v1/users/{id}\u0060, a JSON Merge Patch (RFC 7396): a field left\nout is unchanged and a field sent is set. \u0060displayName\u0060 cannot be empty or\n\u0060null\u0060 and is at most 200 characters. \u0060contactEmail\u0060 must be a valid email\naddress of at most 320 characters, and \u0060null\u0060 clears it; reading it back, at\n\u0060GET /v1/users/{id}/contact\u0060, needs \u0060pii.read\u0060 for anyone but the caller.\n\u0060idpSubjectId\u0060 cannot be changed.",
        "example": {
          "displayName": "Priya Shah-Evans"
        },
        "properties": {
          "contactEmail": {
            "type": [
              "null",
              "string"
            ]
          },
          "displayName": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "UserPermissionsDto": {
        "description": "The permissions assigned directly to a user, at \u0060/v1/users/{id}/permissions\u0060:\n\u0060userId\u0060 and \u0060permissions\u0060, a sorted list of permission names. \u0060GET\u0060\nreturns an \u0060ETag\u0060 and answers 304 to a matching \u0060If-None-Match\u0060; \u0060PUT\u0060\nreplaces the set and \u0060PATCH\u0060 grants or revokes some of it. \u0060_links\u0060 offers\nthose changes only to a caller allowed to make them.",
        "example": {
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "permissions": [
            "content:read",
            "enrolments:read",
            "progress:read",
            "progress:write"
          ],
          "_links": {
            "self": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897/permissions",
            "replace": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897/permissions",
            "amend": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897/permissions"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "permissions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "userId",
          "permissions",
          "_links"
        ],
        "type": "object"
      },
      "UserProgressItemDto": {
        "description": "One of a user\u0027s active enrolments in \u0060GET /v1/users/{id}/progress\u0060, with the\ncourse\u0027s title and the same progress figures as\n\u0060GET /v1/enrolments/{enrolmentId}/progress\u0060. \u0060status\u0060 is one of\n\u0060completed\u0060, \u0060overdue\u0060 (past its \u0060deadline\u0060 and not completed),\n\u0060in_progress\u0060 or \u0060not_started\u0060. \u0060completedAt\u0060 is when the enrolment\u0027s\nfirst course completion was recorded, or \u0060null\u0060.",
        "example": {
          "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "courseTitle": "Working at height",
          "completionModel": "all_activities_completed",
          "percentComplete": 100,
          "completed": 1,
          "total": 1,
          "bestScore": 0.8,
          "minimumAssessmentScore": null,
          "status": "completed",
          "deadline": "2026-03-31T17:00:00\u002B00:00",
          "isMandatory": true,
          "completedAt": "2026-03-05T14:12:00\u002B00:00",
          "_links": {
            "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
            "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
            "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "bestScore": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "completed": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "completedAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "completionModel": {
            "type": "string"
          },
          "courseTitle": {
            "type": "string"
          },
          "courseVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "deadline": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentId": {
            "format": "uuid",
            "type": "string"
          },
          "isMandatory": {
            "type": "boolean"
          },
          "minimumAssessmentScore": {
            "format": "double",
            "type": [
              "null",
              "number"
            ]
          },
          "percentComplete": {
            "format": "double",
            "type": "number"
          },
          "status": {
            "type": "string"
          },
          "total": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "enrolmentId",
          "userId",
          "courseVersionId",
          "courseTitle",
          "completionModel",
          "percentComplete",
          "completed",
          "total",
          "bestScore",
          "minimumAssessmentScore",
          "status",
          "deadline",
          "isMandatory",
          "completedAt",
          "_links"
        ],
        "type": "object"
      },
      "UserReportDetailRowDto": {
        "description": "One of the user\u0027s active enrolments in \u0060GET /v1/reports/users/{id}\u0060: the course\nversion the enrolment is pinned to and its title, the server-computed\n\u0060percentComplete\u0060, the enrolment\u0027s \u0060status\u0060 (\u0060completed\u0060,\n\u0060overdue\u0060, \u0060in_progress\u0060 or \u0060not_started\u0060), its \u0060deadline\u0060 and\n\u0060isMandatory\u0060, and \u0060completedAt\u0060, when it was first completed.",
        "example": {
          "enrolmentId": "019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
          "courseVersionId": "019bb1c1-6440-7dce-a467-3ce49765c7f3",
          "courseTitle": "Working at height",
          "percentComplete": 100,
          "status": "completed",
          "deadline": "2026-03-31T17:00:00\u002B00:00",
          "isMandatory": true,
          "completedAt": "2026-03-05T14:12:00\u002B00:00",
          "_links": {
            "enrolment": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8",
            "progress": "/v1/enrolments/019c65ad-9280-74cb-9fdb-f611f3ad6dc8/progress",
            "course": "/v1/courses/019bb1c1-6440-7dce-a467-3ce49765c7f3"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "completedAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "courseTitle": {
            "type": "string"
          },
          "courseVersionId": {
            "format": "uuid",
            "type": "string"
          },
          "deadline": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "enrolmentId": {
            "format": "uuid",
            "type": "string"
          },
          "isMandatory": {
            "type": "boolean"
          },
          "percentComplete": {
            "format": "double",
            "type": "number"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "enrolmentId",
          "courseVersionId",
          "courseTitle",
          "percentComplete",
          "status",
          "deadline",
          "isMandatory",
          "completedAt",
          "_links"
        ],
        "type": "object"
      },
      "UserReportRowDto": {
        "description": "One user in \u0060GET /v1/reports/users\u0060, with counts over their active enrolments.\n\u0060completed\u0060 counts enrolments completed within the report window (from\n\u0060dateFrom\u0060 up to but not including \u0060dateTo\u0060; the last 30 days by default).\n\u0060enrolled\u0060, \u0060overdue\u0060 and \u0060mandatoryOutstanding\u0060 describe the present:\nall enrolments, those past their deadline and not completed, and those that are\nmandatory or carry a deadline and are not yet completed.",
        "example": {
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "displayName": "Priya Shah",
          "enrolled": 3,
          "completed": 2,
          "overdue": 0,
          "mandatoryOutstanding": 1,
          "_links": {
            "self": "/v1/reports/users/0199b9ad-d000-7fed-bccd-fd9c96a18897",
            "user": "/v1/users/0199b9ad-d000-7fed-bccd-fd9c96a18897"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "completed": {
            "format": "int32",
            "type": "integer"
          },
          "displayName": {
            "type": "string"
          },
          "enrolled": {
            "format": "int32",
            "type": "integer"
          },
          "mandatoryOutstanding": {
            "format": "int32",
            "type": "integer"
          },
          "overdue": {
            "format": "int32",
            "type": "integer"
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "userId",
          "displayName",
          "enrolled",
          "completed",
          "overdue",
          "mandatoryOutstanding",
          "_links"
        ],
        "type": "object"
      },
      "UserStatus": {
        "description": "Whether a user\u0027s membership of the tenant is \u0060active\u0060 or \u0060suspended\u0060. A\nsuspended user cannot sign in and cannot be acted for, but can still be read, listed\nand edited; \u0060POST /v1/users/membership:suspend\u0060 and\n\u0060POST /v1/users/membership:reactivate\u0060 move between the two. This reports the\nmembership only: the responses of \u0060:deactivate\u0060 and \u0060:erase\u0060 still show the\nmembership the user had, usually \u0060active\u0060.",
        "enum": [
          "active",
          "suspended"
        ],
        "example": "active"
      },
      "WebhookSubscriptionCreatedDto": {
        "description": "The 201 response of \u0060POST /v1/webhook-subscriptions\u0060: the new subscription plus\n\u0060secret\u0060, the secret used to sign its deliveries. This is the only time the secret\nis returned, so store it now; a lost secret cannot be recovered, and the remedy is to\ndelete the subscription and create a new one. A replay of the request under the same\n\u0060Idempotency-Key\u0060 returns \u0060secret\u0060 as \u0060null\u0060.",
        "example": {
          "id": "019c2416-cd00-7b0f-b522-09fce53deff2",
          "name": "Completions to HR",
          "targetUrl": "https://hr.example.com/hooks/seren",
          "eventTypes": [
            "course_completion.created",
            "enrolment.created"
          ],
          "active": true,
          "secret": "example-signing-secret",
          "createdAt": "2026-02-03T15:20:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/webhook-subscriptions/019c2416-cd00-7b0f-b522-09fce53deff2",
            "list": "/v1/webhook-subscriptions",
            "update": "/v1/webhook-subscriptions/019c2416-cd00-7b0f-b522-09fce53deff2",
            "delete": "/v1/webhook-subscriptions/019c2416-cd00-7b0f-b522-09fce53deff2"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "active": {
            "type": "boolean"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "eventTypes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "secret": {
            "type": [
              "null",
              "string"
            ]
          },
          "targetUrl": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "targetUrl",
          "eventTypes",
          "active",
          "secret",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "WebhookSubscriptionDto": {
        "description": "A webhook subscription: deliveries of the events listed in \u0060eventTypes\u0060 are sent\nto \u0060targetUrl\u0060 while \u0060active\u0060 is \u0060true\u0060. The signing secret is never\nreturned here; it is returned only once, when the subscription is created.",
        "example": {
          "id": "019c2416-cd00-7b0f-b522-09fce53deff2",
          "name": "Completions to HR",
          "targetUrl": "https://hr.example.com/hooks/seren",
          "eventTypes": [
            "course_completion.created",
            "enrolment.created"
          ],
          "active": true,
          "createdAt": "2026-02-03T15:20:00\u002B00:00",
          "updatedAt": "2026-02-03T15:20:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "_links": {
            "self": "/v1/webhook-subscriptions/019c2416-cd00-7b0f-b522-09fce53deff2",
            "list": "/v1/webhook-subscriptions",
            "update": "/v1/webhook-subscriptions/019c2416-cd00-7b0f-b522-09fce53deff2",
            "delete": "/v1/webhook-subscriptions/019c2416-cd00-7b0f-b522-09fce53deff2"
          }
        },
        "properties": {
          "_links": {
            "additionalProperties": {
              "format": "uri-reference",
              "type": "string"
            },
            "description": "Hypermedia links: a map from an operation name to the URI that performs it, listing only the operations the caller may perform on this resource now \u2014 the set varies with the token\u0027s permissions and the resource\u0027s state. Follow these rather than building URIs.",
            "type": "object"
          },
          "active": {
            "type": "boolean"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "eventTypes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "targetUrl": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "updatedBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "targetUrl",
          "eventTypes",
          "active",
          "createdAt",
          "updatedAt",
          "createdBy",
          "updatedBy",
          "_links"
        ],
        "type": "object"
      },
      "WebhookSubscriptionMergePatch": {
        "description": "The body of \u0060PATCH /v1/webhook-subscriptions/{id}\u0060, a JSON Merge Patch (RFC 7396):\na field left out is unchanged and a field sent is set. None of \u0060name\u0060,\n\u0060targetUrl\u0060, \u0060eventTypes\u0060 or \u0060active\u0060 may be \u0060null\u0060, and each follows\nthe same rules as on create; a new \u0060name\u0060 already used in the tenant fails with\n409. Setting \u0060active\u0060 to \u0060false\u0060 stops deliveries to the subscription. The\nsigning secret cannot be changed: delete the subscription and create a new one.",
        "example": {
          "active": false
        },
        "properties": {
          "active": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "eventTypes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "name": {
            "type": "string"
          },
          "targetUrl": {
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    "securitySchemes": {
      "bearer": {
        "bearerFormat": "at\u002Bjwt",
        "description": "A Duende-issued OAuth2 access token (RFC 9068 at\u002Bjwt). Sent as \u0060Authorization: Bearer \u003Ctoken\u003E\u0060; carries the caller\u0027s permission claims and active tenant claim, cross-checked against the X-Tenant-Id header \u2014 or, for a platform-service caller, its capability scopes, with the tenant named by X-Seren-Tenant on the operations that admit one.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "The Seren Platform API is a headless, multi-tenant learning platform: courses and the reusable lessons, activities and questions they are built from; learners\u0027 enrolments, completions and progress; the users of each tenant and what they may do. Seren\u0027s own apps use it on the same terms as any other client.\n\n## Authentication\n\nEvery operation needs an OAuth 2.0 access token unless it says otherwise. Send it as \u0060Authorization: Bearer \u003Ctoken\u003E\u0060. The token carries the caller\u0027s permissions and the tenant it was issued for; an operation the token does not permit answers 403.\n\n## Tenant context\n\nEvery request acts in exactly one tenant, named in a header rather than the hostname:\n\n- \u0060X-Tenant-Id\u0060 for user, integration and agent callers. It is checked against the token\u0027s tenant: a missing header is 400, a mismatch is 403.\n- \u0060X-Seren-Tenant\u0060 for platform-service callers, on the few operations that admit one. Their credentials carry no tenant, so the header is authoritative there.\n\n## Errors\n\nEvery error is an RFC 7807 problem document (\u0060application/problem\u002Bjson\u0060). Branch on its \u0060code\u0060 \u2014 a stable, machine-readable key such as \u0060course.not_found\u0060 \u2014 rather than on the status or the \u0060type\u0060 URI. \u0060correlationId\u0060 identifies the request; quote it when you ask for support. Some errors add members: \u0060errors\u0060 on a field-validation 422, \u0060referencing\u0060 on a 409 for a resource still in use, \u0060unmet\u0060 on a 409 for an unmet prerequisite, and \u0060fields\u0060 on a 403 that refuses a member the caller may not set.\n\n## Paging\n\nLists are paged by cursor. Pass \u0060limit\u0060 (1 to 100, default 25) and, for every page after the first, the previous page\u0027s \u0060pagination.nextCursor\u0060 as \u0060after\u0060. \u0060pagination.hasMore\u0060 is false on the last page. Cursors are opaque: never build or edit one. Where a list offers \u0060includeCount=true\u0060, \u0060pagination.totalCount\u0060 carries the total; it is otherwise null, so no count is run unless you ask for it.\n\n## Links\n\nResources and lists carry \u0060_links\u0060: a map from an operation name to the URI that performs it. The map lists only what the caller may do to that resource now, so it changes with the token\u0027s permissions and the resource\u0027s state. Follow these links rather than building URIs, and treat a missing link as \u0022not available to you\u0022.\n\n## Retrying writes\n\nAny \u0060POST\u0060, \u0060PUT\u0060, \u0060PATCH\u0060 or \u0060DELETE\u0060 accepts an optional \u0060Idempotency-Key\u0060 header. Mint one key for each logical submission and resend it only with the identical request. For 24 hours a resend from the same caller returns the original response without running the write again; the same key with a different request, or from another caller, is refused with 422 \u0060idempotency.key_reuse\u0060. A resend that arrives while the first is still running answers 409 with \u0060Retry-After\u0060.\n\n## Rate limits\n\nEach tenant has a request budget per window (by default 1000 requests a minute), which its users and integrations share. A delegated agent has a budget of its own (by default 300 requests a minute): its requests do not spend the tenant\u0027s budget, and the tenant\u0027s people using theirs up do not limit it. Responses counted against a budget carry \u0060X-RateLimit-Limit\u0060, \u0060X-RateLimit-Remaining\u0060 and \u0060X-RateLimit-Reset\u0060 (Unix epoch seconds), describing the budget that counted the request. Over a budget, a request answers 429 with \u0060Retry-After\u0060. Platform-service callers are not counted.\n\n## Request size\n\nA request body may be at most 1 MiB (1,048,576 bytes) unless its operation says it accepts more. A larger body, with or without a \u0060Content-Length\u0060, answers 413 with the code \u0060http.payload_too_large\u0060 before the request is otherwise read, so nothing it asked for is done.\n\n## Conditional writes\n\nA course, module, lesson, activity, question or placement is returned with a strong \u0060ETag\u0060. Send it back as \u0060If-Match\u0060 on that resource\u0027s \u0060PATCH\u0060 \u2014 or on a module or placement \u0060DELETE\u0060 \u2014 and the write is refused with 412 if the resource has changed since you read it. The four orderable collections (a course\u0027s modules, a module\u0027s lesson placements, a lesson\u0027s activity placements and an activity\u0027s question placements) carry a collection \u0060ETag\u0060 for their \u0060:reorder\u0060 action in the same way. Without \u0060If-Match\u0060 the write is unconditional.\n\n## The \u0060me\u0060 alias\n\nWherever a user id is expected \u2014 the \u0060{id}\u0060 of the \u0060/v1/users/{id}\u0060 routes and the \u0060userId\u0060 and \u0060actorUserId\u0060 parameters \u2014 \u0060me\u0060 stands for the caller\u0027s own user.",
    "title": "Seren Platform API",
    "version": "0.1.0"
  },
  "openapi": "3.1.1",
  "paths": {
    "/v1/activities": {
      "get": {
        "description": "Supports filtering by \u0060customFields.{key}={value}\u0060 (422 until custom field definitions exist), \u0060componentUri={uri}\u0060 (repeat the param for any-of; values are opaque and never comma-split), \u0060status=draft|published|archived\u0060 (repeat the param for any-of; unknown values 400), ?q= full-text search, ?sort=-createdAt,title, and cursor pagination (limit/after). A caller without content:write never receives draft rows; that filter is applied before search, count, and pagination, including when status=draft is explicit.",
        "operationId": "ListActivities",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "componentUri",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfActivityDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List activities",
        "tags": [
          "Activities"
        ]
      },
      "post": {
        "description": "Creates version 1 of a new activity family, in draft; the 201 carries the activity and its ETag. Requires content:write. \u0060componentUri\u0060 names the component that renders the activity and can never change afterwards. Send \u0060id\u0060 to choose the UUID yourself; one already in use is a 409 identifier_in_use. Callers without content:write see the activity only once it leaves draft, through :publish or :archive.",
        "operationId": "CreateActivity",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateActivityCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create an activity",
        "tags": [
          "Activities"
        ]
      }
    },
    "/v1/activities/families/{familyId}/latest-published": {
      "get": {
        "description": "Resolves an activity family to its newest published version: pass the \u0060familyId\u0060, not a version\u0027s id. Requires content:read. A family that is unknown, or that has only draft or archived versions, is a 404 activity.no_published_version, and so is a version id passed by mistake.",
        "operationId": "GetLatestPublishedActivity",
        "parameters": [
          {
            "in": "path",
            "name": "familyId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get the latest published version of an activity",
        "tags": [
          "Activities"
        ]
      }
    },
    "/v1/activities/{activityId}/completions": {
      "get": {
        "description": "Cursor-paginated (limit/after, includeCount) list of a learner\u0027s attempts at this activity. Defaults to the caller\u0027s own completions; a userId for another learner requires progress:read\u0027s tenant rung (progress:read:tenant).",
        "operationId": "ListActivityCompletions",
        "parameters": [
          {
            "in": "path",
            "name": "activityId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "query",
            "name": "userId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfActivityCompletionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List a learner\u0027s completions of an activity",
        "tags": [
          "Activity completions"
        ]
      },
      "post": {
        "description": "Records a standalone completion of this activity with its question responses as one bundle. Defaults to the caller\u0027s own row; a userId for another learner requires progress:write\u0027s tenant rung (progress:write:tenant). The bundle carries at most 200 responses; a longer one is a 422 validation.failed whose one error is that cap, and no response in it is checked. It carries at most one response per questionPlacementId \u2014 duplicates (including two textual spellings of one UUID) are 422 validation.failed with an \u0027errors.responses\u0027 entry. Every question placement the activity marks required must be answered: a bundle that leaves one out, or answers it with null, a blank string or an empty array, is 422 completion.required_response_missing with one \u0027errors\u0027 entry per missing placement, keyed \u0027responses.{questionPlacementId}\u0027, and nothing is recorded. That check runs after the access-window and sequencing checks and after every questionPlacementId has been matched to this activity (422 completion.placement_not_in_activity otherwise). A refusal is replayed for its Idempotency-Key like any other response, so a corrected bundle needs a new key. Where the pinned question version carries an answer key the API can mark (is_correct on multiple_choice_single/multiple_choice_multi, definition.answer on true_false, definition.acceptedAnswers on short_answer) the API computes isCorrect itself and sets the per-response score to 0 or 1; a client-supplied verdict on such a row is silently overridden, never rejected, and a wrong answer is a verdict rather than an error. short_answer matches trimmed and case-insensitively against each accepted answer, exactly and with no further normalization. On marked rows, on required placements and on every free_text, likert and rating question, \u0027response\u0027 must be null or an array of option id strings (multiple choice), null or a boolean (true_false), null or a string (short_answer), null or a string of at most 10,000 characters (free_text), or null or a one-element array holding the id of one of the question\u0027s own options (likert, rating) \u2014 anything else is 422 completion.response_shape_invalid. On a keyed multiple choice question an id in a well-formed array that the version does not have simply marks wrong. free_text, likert and rating are never marked, and like every other unkeyed item and the reserved correct_rank shape they keep the client\u0027s verdict; the response of an optional unkeyed item of the older types stays opaque. When the client omits the completion-level score the API derives a fraction-correct 0-1 value, but only when every keyed question placement on the activity is server-markable and answered exactly once in this bundle; otherwise the score stays null, and a client-supplied score is never overwritten.",
        "operationId": "SubmitActivityCompletion",
        "parameters": [
          {
            "in": "path",
            "name": "activityId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitActivityCompletionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "format": "uuid",
                  "type": "string"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Record a completion of an activity",
        "tags": [
          "Activity completions"
        ]
      }
    },
    "/v1/activities/{activityId}/placements": {
      "get": {
        "description": "Cursor-paginated (limit/after, includeCount) list of the placements that pin this activity, across all lessons.",
        "operationId": "ListActivityUsages",
        "parameters": [
          {
            "in": "path",
            "name": "activityId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfActivityPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List where an activity is placed",
        "tags": [
          "Activity placements"
        ]
      }
    },
    "/v1/activities/{activityId}/question-placements": {
      "get": {
        "description": "Returns every placement in the activity, in order, in one unpaged response, with the collection ETag that :reorder takes as \u0060If-Match\u0060. Requires content:read. Placements are listed whatever the status of the activity. A missing activity is a 404 activity.not_found.",
        "operationId": "ListQuestionPlacements",
        "parameters": [
          {
            "in": "path",
            "name": "activityId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfQuestionPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The collection\u0027s strong entity tag, covering which members it holds and their order but not their content: send it as If-Match on the collection\u0027s :reorder to refuse the reorder if a member has been added, removed or moved since this response. Opaque. A collection revision, not a cache validator: If-None-Match is not honoured.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List an activity\u0027s question placements",
        "tags": [
          "Question placements"
        ]
      },
      "post": {
        "description": "Places a question at the end of the activity; the 201 carries the placement and its ETag. Requires content:write. Any question that has not been deleted can be placed, and the same question more than once. The activity must carry questions (\u0060carriesQuestions\u0060), else a 422 question_placement.activity_no_questions. The activity\u0027s status is not checked, so on live content the placement takes effect at once. A missing activity is a 404 activity.not_found. A \u0060questionId\u0060 that does not exist is a 422 question_placement.question_not_found, and an \u0060id\u0060 already in use is a 409 identifier_in_use. While placed, the question cannot be deleted (409 question.in_use). \u0060required\u0060 defaults to false; when true, every completion submitted for the activity must answer this placement \u2014 one that leaves it out, or answers it with null, a blank string or an empty array, is refused with a 422 completion.required_response_missing.",
        "operationId": "CreateQuestionPlacement",
        "parameters": [
          {
            "in": "path",
            "name": "activityId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateQuestionPlacementRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuestionPlacementDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Place a question in an activity",
        "tags": [
          "Question placements"
        ]
      }
    },
    "/v1/activities/{activityId}/question-placements:reorder": {
      "post": {
        "description": "Sets the order of the activity\u0027s question placements. \u0060placementIds\u0060 must list every placement in the activity exactly once: a missing or extra id is a 409 question_placement.reorder_set_mismatch, and an empty or repeating list is a 422. Requires content:write; conditional on the placement list\u0027s collection ETag when \u0060If-Match\u0060 is sent. Returns the reordered placements and the new collection ETag; only placements whose position changed are rewritten, so only they get new ETags. The activity\u0027s status is not checked, so on live content the new order is visible at once.",
        "operationId": "ReorderQuestionPlacements",
        "parameters": [
          {
            "in": "path",
            "name": "activityId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the collection\u0027s ETag, from its GET or from the last reorder\u0027s response. When a member has been added, removed or moved since (the collection\u0027s current ETag differs), nothing is reordered and the response is 412 {entity}.precondition_failed \u2014 read the collection again and resend with the new ETag. A member\u0027s own edits do not change the collection\u0027s tag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing collection. Absent, the reorder is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReorderQuestionPlacementsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfQuestionPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The collection\u0027s strong entity tag, covering which members it holds and their order but not their content: send it as If-Match on the collection\u0027s :reorder to refuse the reorder if a member has been added, removed or moved since this response. Opaque. A collection revision, not a cache validator: If-None-Match is not honoured.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Reorder an activity\u0027s question placements",
        "tags": [
          "Question placements"
        ]
      }
    },
    "/v1/activities/{activityId}/questions": {
      "get": {
        "description": "Every question placed in the activity, in placement order, each surfacing its placement ids, the full question (options included), and a placement self link. A draft activity is hidden with the ordinary non-leaky 404 unless the caller also holds content:write; Questions have no status gate of their own.",
        "operationId": "ListActivityQuestions",
        "parameters": [
          {
            "in": "path",
            "name": "activityId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfPlacedQuestionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List the questions placed in an activity",
        "tags": [
          "Activities"
        ]
      }
    },
    "/v1/activities/{id}": {
      "delete": {
        "description": "Deleting referenced content is a 409 problem with code activity.in_use whose \u0027referencing\u0027 extension lists the referencing entities ({entityType, id}).",
        "operationId": "DeleteActivity",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete an activity",
        "tags": [
          "Activities"
        ]
      },
      "get": {
        "description": "Returns the activity version. A draft is hidden with the ordinary non-leaky 404 unless the caller also holds content:write; archived reads like published.",
        "operationId": "GetActivityById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get an activity",
        "tags": [
          "Activities"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch, conditional on \u0060If-Match\u0060 when one is sent. Requires content:write. Once the version is published or archived, \u0060title\u0060, \u0060description\u0060, \u0060producesScore\u0060, \u0060carriesQuestions\u0060 and \u0060definition\u0060 are frozen: a patch that sends any of them is a 409 activity.not_draft, so return the activity to draft with :unpublish first. On a draft, \u0060carriesQuestions\u0060 cannot be switched off while questions are placed in the activity (409 activity.has_question_placements): delete its question placements first. \u0060presentation\u0060 stays editable on a live version, but sending it then also requires content:publish (403 content.live_edit_requires_publish). \u0060componentUri\u0060 can never change (422). A patch that changes nothing returns the activity as it stands.",
        "operationId": "UpdateActivity",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActivityMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/ActivityMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update an activity",
        "tags": [
          "Activities"
        ]
      }
    },
    "/v1/activities/{id}:archive": {
      "post": {
        "description": "Draft or published \u2192 archived. Requires content:publish \u2014 the release verb split out of content:write.",
        "operationId": "ArchiveActivity",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Archive an activity",
        "tags": [
          "Activities"
        ]
      }
    },
    "/v1/activities/{id}:publish": {
      "post": {
        "description": "Draft \u2192 published. Requires content:publish \u2014 the release verb split out of content:write, which authors a draft but does not release it.",
        "operationId": "PublishActivity",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Publish an activity",
        "tags": [
          "Activities"
        ]
      }
    },
    "/v1/activities/{id}:unpublish": {
      "post": {
        "description": "Published or archived \u2192 draft (the recovery path out of archived). Requires content:publish \u2014 the release verb split out of content:write.",
        "operationId": "UnpublishActivity",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Return an activity to draft",
        "tags": [
          "Activities"
        ]
      }
    },
    "/v1/activity-completions/{id}": {
      "get": {
        "description": "Returns one activity completion with the question responses submitted in it; no list includes the responses. Requires progress:read, which reaches the caller\u0027s own completions; anyone else\u0027s needs progress:read:tenant. One out of reach is the same non-leaky 404 as a missing one.",
        "operationId": "GetActivityCompletionById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityCompletionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get an activity completion",
        "tags": [
          "Activity completions"
        ]
      }
    },
    "/v1/activity-placements/{id}": {
      "delete": {
        "description": "Permanently removes the placement; the activity itself, and every completion learners recorded under the placement, are kept. Requires content:write; conditional on \u0060If-Match\u0060 when one is sent. Nothing blocks the removal, whatever the lesson\u0027s status, so on live content it disappears for learners at once.",
        "operationId": "DeleteActivityPlacement",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Remove an activity placement",
        "tags": [
          "Activity placements"
        ]
      },
      "get": {
        "description": "Returns one placement: the activity version it pins, its position and its own \u0060presentation\u0060. Requires content:read. A placement is returned whatever the status of the placed activity.",
        "operationId": "GetActivityPlacementById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get an activity placement",
        "tags": [
          "Activity placements"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch, conditional on \u0060If-Match\u0060 when one is sent. Requires content:write. While the lesson is published or archived, sending \u0060presentation\u0060 (even unchanged, or \u0060null\u0060) also requires content:publish (403 content.live_edit_requires_publish). \u0060required\u0060 can change on a lesson of any status but cannot be null. The pinned activity version cannot change: remove the placement and place the other version instead. The position changes only through :reorder.",
        "operationId": "UpdateActivityPlacement",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActivityPlacementMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/ActivityPlacementMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update an activity placement",
        "tags": [
          "Activity placements"
        ]
      }
    },
    "/v1/activity-placements/{placementId}/completions": {
      "get": {
        "description": "Cursor-paginated (limit/after, includeCount) list of a learner\u0027s placed attempts under this placement. Defaults to the caller\u0027s own attempts; a userId for another learner requires progress:read\u0027s tenant rung (progress:read:tenant).",
        "operationId": "ListPlacedActivityCompletions",
        "parameters": [
          {
            "in": "path",
            "name": "placementId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "query",
            "name": "userId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfActivityCompletionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List a learner\u0027s completions of an activity placement",
        "tags": [
          "Activity completions"
        ]
      },
      "post": {
        "description": "Records a placed completion under this activity placement (keyed userId \u002B placementId; the activity derives from the placement\u0027s pin). Subject to the lesson\u0027s activitySequencing gate \u2014 a blocked linear write is a 409 problem with code completion.prerequisite_not_met whose \u0027unmet\u0027 extension lists the incomplete blocking placements ({entityType, id}) in presentation order. A closed course access window is also a 409 conflict, with code completion.access_window_closed. The bundle carries at most 200 responses; a longer one is a 422 validation.failed whose one error is that cap, and no response in it is checked. It carries at most one response per questionPlacementId \u2014 duplicates (including two textual spellings of one UUID) are 422 validation.failed with an \u0027errors.responses\u0027 entry. Every question placement the activity marks required must be answered: a bundle that leaves one out, or answers it with null, a blank string or an empty array, is 422 completion.required_response_missing with one \u0027errors\u0027 entry per missing placement, keyed \u0027responses.{questionPlacementId}\u0027, and nothing is recorded. That check runs after the access-window and sequencing checks and after every questionPlacementId has been matched to this activity (422 completion.placement_not_in_activity otherwise). A refusal is replayed for its Idempotency-Key like any other response, so a corrected bundle needs a new key. Where the pinned question version carries an answer key the API can mark (is_correct on multiple_choice_single/multiple_choice_multi, definition.answer on true_false, definition.acceptedAnswers on short_answer) the API computes isCorrect itself and sets the per-response score to 0 or 1; a client-supplied verdict on such a row is silently overridden, never rejected, and a wrong answer is a verdict rather than an error. short_answer matches trimmed and case-insensitively against each accepted answer, exactly and with no further normalization. On marked rows, on required placements and on every free_text, likert and rating question, \u0027response\u0027 must be null or an array of option id strings (multiple choice), null or a boolean (true_false), null or a string (short_answer), null or a string of at most 10,000 characters (free_text), or null or a one-element array holding the id of one of the question\u0027s own options (likert, rating) \u2014 anything else is 422 completion.response_shape_invalid. On a keyed multiple choice question an id in a well-formed array that the version does not have simply marks wrong. free_text, likert and rating are never marked, and like every other unkeyed item and the reserved correct_rank shape they keep the client\u0027s verdict; the response of an optional unkeyed item of the older types stays opaque. When the client omits the completion-level score the API derives a fraction-correct 0-1 value, but only when every keyed question placement on the activity is server-markable and answered exactly once in this bundle; otherwise the score stays null, and a client-supplied score is never overwritten.",
        "operationId": "SubmitPlacedActivityCompletion",
        "parameters": [
          {
            "in": "path",
            "name": "placementId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitActivityCompletionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "format": "uuid",
                  "type": "string"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Record a completion of an activity placement",
        "tags": [
          "Activity completions"
        ]
      }
    },
    "/v1/agents": {
      "get": {
        "description": "Returns the calling user\u0027s own agents, oldest first, cursor-paged. Requires agents:delegate, and a signed-in user: any other caller is a 403 agent.user_required.",
        "operationId": "ListAgents",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfAgentDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List agents",
        "tags": [
          "Agents"
        ]
      },
      "post": {
        "description": "Registers an agent that the calling user owns, active and in delegated mode; it can act only through a delegation created afterwards. Requires agents:delegate, and a signed-in user: any other caller is a 403 agent.user_required. A suspended user is refused the same way.",
        "operationId": "CreateAgent",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAgentCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Register an agent",
        "tags": [
          "Agents"
        ]
      }
    },
    "/v1/agents/authority:read": {
      "post": {
        "description": "System-only (system:agents:authority), under X-Seren-Tenant: a consented delegation\u0027s current authority \u2014 its scope \u2229 the delegated-authoring allowlist \u2229 the granter\u0027s current stored grants \u2014 and expiry, read by the identity host before it mints an agent token. Uncached; writes nothing. 404 user.not_found for an unknown or offboarded subject, 403 user.suspended for a suspended seat, 403 agent.authority_denied when the delegation does not stand or confers nothing.",
        "operationId": "ReadAgentAuthority",
        "parameters": [
          {
            "description": "The tenant this platform-service call acts in. A system caller\u0027s credentials are capability-scoped and carry no tenant claim, so this header is authoritative: missing or malformed is 400. X-Tenant-Id is not read on this operation, and a caller that is not a platform service is refused (403) whatever it sends.",
            "in": "header",
            "name": "X-Seren-Tenant",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReadAgentAuthorityBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAuthorityDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Read an agent delegation\u0027s current authority",
        "tags": [
          "Agents"
        ]
      }
    },
    "/v1/agents/{id}": {
      "get": {
        "description": "Returns one of the calling user\u0027s agents. Requires agents:delegate, and a signed-in user: any other caller is a 403 agent.user_required. A user manages only their own agents; another user\u0027s is the same 404 agent.not_found as a missing one.",
        "operationId": "GetAgent",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get an agent",
        "tags": [
          "Agents"
        ]
      }
    },
    "/v1/agents/{id}/delegations": {
      "get": {
        "description": "Returns the agent\u0027s delegations, oldest first, cursor-paged. A delegation past its \u0060expiresAt\u0060 reads \u0060expired\u0060, and only one still in force offers the \u0060revoke\u0060 link. Requires agents:delegate, and a signed-in user: any other caller is a 403 agent.user_required. A user manages only their own agents; another user\u0027s is the same 404 agent.not_found as a missing one.",
        "operationId": "ListAgentDelegations",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfAgentDelegationDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List an agent\u0027s delegations",
        "tags": [
          "Agents"
        ]
      },
      "post": {
        "description": "Delegates part of the calling user\u0027s authority to one of their active agents for 30 days, limited to draft content. \u0060scope\u0060 lists one to three permissions from content:read, content:write and answer-keys.read. Any other value is a 422 permission.invalid, even for a user who holds it; one of the three that the user does not hold is a 403 permission.not_held. A suspended or revoked agent is a 409 agent.not_active. An agent has at most one active delegation: revoke it first, else a 409 agent.active_delegation. Requires agents:delegate, and a signed-in user: any other caller is a 403 agent.user_required. A user manages only their own agents; another user\u0027s is the same 404 agent.not_found as a missing one.",
        "operationId": "CreateAgentDelegation",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAgentDelegationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDelegationDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delegate authority to an agent",
        "tags": [
          "Agents"
        ]
      }
    },
    "/v1/agents/{id}/delegations/{delegationId}": {
      "get": {
        "description": "Returns one of the agent\u0027s delegations. A delegation past its \u0060expiresAt\u0060 reads \u0060expired\u0060, and only one still in force offers the \u0060revoke\u0060 link. Requires agents:delegate, and a signed-in user: any other caller is a 403 agent.user_required. A user manages only their own agents; another user\u0027s is the same 404 agent.not_found as a missing one.",
        "operationId": "GetAgentDelegation",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "delegationId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDelegationDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get an agent delegation",
        "tags": [
          "Agents"
        ]
      }
    },
    "/v1/agents/{id}/delegations/{delegationId}:revoke": {
      "post": {
        "description": "Revokes the delegation for good; the agent\u0027s next request is refused. Revoking one already revoked returns it unchanged, and revoking an expired one records it as revoked. Requires agents:delegate, and a signed-in user: any other caller is a 403 agent.user_required. A user manages only their own agents; another user\u0027s is the same 404 agent.not_found as a missing one.",
        "operationId": "RevokeAgentDelegation",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "delegationId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentDelegationDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Revoke an agent delegation",
        "tags": [
          "Agents"
        ]
      }
    },
    "/v1/capabilities": {
      "get": {
        "description": "The authenticated caller\u0027s effective API capabilities. No permission grant is required. Resource links and execution-time checks remain authoritative.",
        "operationId": "GetCapabilities",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CapabilitiesDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get the caller\u0027s capabilities",
        "tags": [
          "Capabilities"
        ]
      }
    },
    "/v1/course-completions/{id}": {
      "get": {
        "description": "Returns one course completion, including after its enrolment was withdrawn. Requires progress:read, which reaches the caller\u0027s own completions; anyone else\u0027s needs progress:read:tenant. One out of reach is the same non-leaky 404 as a missing one.",
        "operationId": "GetCourseCompletionById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseCompletionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a course completion",
        "tags": [
          "Course completions"
        ]
      }
    },
    "/v1/courses": {
      "get": {
        "description": "Supports filtering by \u0060customFields.{key}={value}\u0060 (422 until custom field definitions exist), \u0060status=draft|published|archived\u0060 (repeat the param for any-of; unknown values 400), ?q= full-text search, ?sort=-createdAt,title, and cursor pagination (limit/after). A caller without content:write never receives draft rows; that filter is applied before search, count, and pagination, including when status=draft is explicit.",
        "operationId": "ListCourses",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "id",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfCourseDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List courses",
        "tags": [
          "Courses"
        ]
      },
      "post": {
        "description": "Creates version 1 of a new course family, in draft; the 201 carries the course and its ETag. Requires content:write. \u0060completionModel\u0060 is required, and the minimum_assessment_score model also needs \u0060minimumAssessmentScore\u0060; each window must start at or before its end (422). Leave out \u0060order\u0060 to place the course after the existing ones. Send \u0060id\u0060 to choose the UUID yourself; one already in use is a 409 identifier_in_use. Callers without content:write see the course only once it leaves draft, through :publish or :archive.",
        "operationId": "CreateCourse",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCourseCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a course",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/courses/families/{familyId}/latest-published": {
      "get": {
        "description": "Resolves a course family to its newest published version: pass the \u0060familyId\u0060, not a version\u0027s id. Requires content:read. A family that is unknown, or that has only draft or archived versions, is a 404 course.no_published_version, and so is a version id passed by mistake.",
        "operationId": "GetLatestPublishedCourse",
        "parameters": [
          {
            "in": "path",
            "name": "familyId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get the latest published version of a course",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/courses/{courseId}/lessons": {
      "get": {
        "description": "Every lesson placed anywhere in the course, in (module order, placement order), each surfacing its placement ids and a nested self link. A draft course is a non-leaky 404 and draft lessons are omitted unless the caller also holds content:write.",
        "operationId": "ListCourseLessons",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfPlacedLessonDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List every lesson placed in a course",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/courses/{courseId}/modules": {
      "get": {
        "description": "Returns every module in the course, in order, in one unpaged response, with the collection ETag that :reorder takes as \u0060If-Match\u0060. Requires content:read. While the course is a draft it is hidden with the non-leaky 404 course.not_found unless the caller also holds content:write.",
        "operationId": "ListModules",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfModuleDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The collection\u0027s strong entity tag, covering which members it holds and their order but not their content: send it as If-Match on the collection\u0027s :reorder to refuse the reorder if a member has been added, removed or moved since this response. Opaque. A collection revision, not a cache validator: If-None-Match is not honoured.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List a course\u0027s modules",
        "tags": [
          "Modules"
        ]
      },
      "post": {
        "description": "Adds a module to the end of the course\u0027s modules; the 201 carries the module and its ETag. Requires content:write. The course\u0027s status is not checked, so a module added to a published course is visible at once. A missing course is a 404 course.not_found, and an \u0060id\u0060 already in use is a 409 identifier_in_use. Change the position with :reorder.",
        "operationId": "CreateModule",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateModuleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModuleDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a module",
        "tags": [
          "Modules"
        ]
      }
    },
    "/v1/courses/{courseId}/modules/{moduleId}": {
      "delete": {
        "description": "Deletes the module and permanently removes its lesson placements; the lessons themselves, and the completions learners recorded, are kept. Requires content:write; conditional on \u0060If-Match\u0060 when one is sent. Neither the course\u0027s status nor learners\u0027 progress blocks the delete, so on a published course the module disappears at once. A module addressed under another course is a 404 module.not_found.",
        "operationId": "DeleteModule",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "moduleId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a module",
        "tags": [
          "Modules"
        ]
      },
      "get": {
        "description": "Returns one module of the course. Requires content:read. While the course is a draft it is hidden with the non-leaky 404 course.not_found unless the caller also holds content:write; a module addressed under another course is a 404 module.not_found.",
        "operationId": "GetModuleById",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "moduleId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModuleDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a module",
        "tags": [
          "Modules"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch, conditional on \u0060If-Match\u0060 when one is sent. Requires content:write. A module has no status of its own, so no field is frozen, but while its course is published or archived, sending \u0060presentation\u0060 also requires content:publish (403 content.live_edit_requires_publish). The position changes only through :reorder, and a module never moves to another course: one addressed under the wrong course is a 404 module.not_found.",
        "operationId": "UpdateModule",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "moduleId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModuleMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/ModuleMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModuleDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a module",
        "tags": [
          "Modules"
        ]
      }
    },
    "/v1/courses/{courseId}/modules/{moduleId}/lesson-placements": {
      "get": {
        "description": "Returns every placement in the module, in order, in one unpaged response, with the collection ETag that :reorder takes as \u0060If-Match\u0060. Requires content:read. Placements are listed whatever the status of the course or of the placed lesson, so the list can name a draft lesson\u0027s id; reading that lesson itself stays subject to its own visibility. A missing course is a 404 course.not_found, and a module missing or under another course is a 404 module.not_found.",
        "operationId": "ListLessonPlacements",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "moduleId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfLessonPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The collection\u0027s strong entity tag, covering which members it holds and their order but not their content: send it as If-Match on the collection\u0027s :reorder to refuse the reorder if a member has been added, removed or moved since this response. Opaque. A collection revision, not a cache validator: If-None-Match is not honoured.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List a module\u0027s lesson placements",
        "tags": [
          "Lesson placements"
        ]
      },
      "post": {
        "description": "Places a lesson at the end of the module; the 201 carries the placement and its ETag. Requires content:write. Any version of a lesson that has not been deleted can be placed, a draft included, and the same lesson can be placed more than once. The course\u0027s status is not checked, so on live content the placement takes effect at once. A placed draft version is hidden from callers without content:write, yet in a linear module it still holds learners back from what follows it. A missing course is a 404 course.not_found, and a module missing or under another course is a 404 module.not_found. A \u0060lessonId\u0060 that does not exist is a 422 lesson_placement.lesson_not_found, and an \u0060id\u0060 already in use is a 409 identifier_in_use. While placed, the lesson cannot be deleted (409 lesson.in_use).",
        "operationId": "CreateLessonPlacement",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "moduleId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLessonPlacementRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonPlacementDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Place a lesson in a module",
        "tags": [
          "Lesson placements"
        ]
      }
    },
    "/v1/courses/{courseId}/modules/{moduleId}/lesson-placements:reorder": {
      "post": {
        "description": "Sets the order of the module\u0027s lesson placements. \u0060placementIds\u0060 must list every placement in the module exactly once: a missing or extra id is a 409 lesson_placement.reorder_set_mismatch, and an empty or repeating list is a 422. Requires content:write; conditional on the placement list\u0027s collection ETag when \u0060If-Match\u0060 is sent. Returns the reordered placements and the new collection ETag; only placements whose position changed are rewritten, so only they get new ETags. The order drives the module\u0027s \u0060lessonSequencing\u0060. The course\u0027s status is not checked, so on live content the new order is visible at once.",
        "operationId": "ReorderLessonPlacements",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "moduleId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the collection\u0027s ETag, from its GET or from the last reorder\u0027s response. When a member has been added, removed or moved since (the collection\u0027s current ETag differs), nothing is reordered and the response is 412 {entity}.precondition_failed \u2014 read the collection again and resend with the new ETag. A member\u0027s own edits do not change the collection\u0027s tag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing collection. Absent, the reorder is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReorderLessonPlacementsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfLessonPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The collection\u0027s strong entity tag, covering which members it holds and their order but not their content: send it as If-Match on the collection\u0027s :reorder to refuse the reorder if a member has been added, removed or moved since this response. Opaque. A collection revision, not a cache validator: If-None-Match is not honoured.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Reorder a module\u0027s lesson placements",
        "tags": [
          "Lesson placements"
        ]
      }
    },
    "/v1/courses/{courseId}/modules/{moduleId}/lessons": {
      "get": {
        "description": "One module\u0027s placed lessons, in placement order, each surfacing its placement ids and a nested self link. A draft course is a non-leaky 404 and draft lessons are omitted unless the caller also holds content:write.",
        "operationId": "ListModuleLessons",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "moduleId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfPlacedLessonDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List the lessons placed in a module",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/courses/{courseId}/modules:reorder": {
      "post": {
        "description": "Sets the order of the course\u0027s modules. \u0060moduleIds\u0060 must list every module in the course exactly once: a missing or extra id is a 409 module.reorder_set_mismatch, and an empty or repeating list is a 422. Requires content:write; conditional on the module list\u0027s collection ETag when \u0060If-Match\u0060 is sent. Returns the reordered modules and the new collection ETag; only modules whose position changed are rewritten. The course\u0027s status is not checked, so on a published course the new order is visible at once.",
        "operationId": "ReorderModules",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the collection\u0027s ETag, from its GET or from the last reorder\u0027s response. When a member has been added, removed or moved since (the collection\u0027s current ETag differs), nothing is reordered and the response is 412 {entity}.precondition_failed \u2014 read the collection again and resend with the new ETag. A member\u0027s own edits do not change the collection\u0027s tag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing collection. Absent, the reorder is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReorderModulesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfModuleDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The collection\u0027s strong entity tag, covering which members it holds and their order but not their content: send it as If-Match on the collection\u0027s :reorder to refuse the reorder if a member has been added, removed or moved since this response. Opaque. A collection revision, not a cache validator: If-None-Match is not honoured.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Reorder a course\u0027s modules",
        "tags": [
          "Modules"
        ]
      }
    },
    "/v1/courses/{courseId}/outline": {
      "get": {
        "description": "The course\u0027s navigational skeleton (modules \u2192 placed lessons \u2192 placed activities) in one read, every array in placement order. Each node carries every field of its course, module, lesson or activity except the unbounded ones the structure read inlines (presentation, definition, customFields) and managedTags, so a tree or nav consumer does not over-fetch; lesson and activity nodes also carry their placement, so their own ids are lessonId and activityId. Optional ?userId=me overlays compact completion state (needs progress:read; another user needs its tenant rung, progress:read:tenant); read /structure when you need the whole hydrated course. A draft course is a non-leaky 404 and draft descendants are omitted unless the caller also holds content:write.",
        "operationId": "GetCourseOutline",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "query",
            "name": "userId",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseOutlineDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a course\u0027s outline",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/courses/{courseId}/structure": {
      "get": {
        "description": "The whole course structure (modules \u2192 placed lessons \u2192 placed activities) in one read, every array in placement order. Optional ?userId=me overlays that user\u0027s latest completion state on each lesson and activity node (needs progress:read; another user needs its tenant rung, progress:read:tenant). A draft course is a non-leaky 404 and draft descendants are omitted unless the caller also holds content:write.",
        "operationId": "GetCourseStructure",
        "parameters": [
          {
            "in": "path",
            "name": "courseId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "query",
            "name": "userId",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseStructureDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a course\u0027s full structure",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/courses/{id}": {
      "delete": {
        "description": "Deleting referenced content is a 409 problem with code course.in_use whose \u0027referencing\u0027 extension lists the referencing entities ({entityType, id}).",
        "operationId": "DeleteCourse",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a course",
        "tags": [
          "Courses"
        ]
      },
      "get": {
        "description": "Returns the course version. A draft is hidden with the ordinary non-leaky 404 unless the caller also holds content:write; archived reads like published.",
        "operationId": "GetCourseById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a course",
        "tags": [
          "Courses"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch, conditional on \u0060If-Match\u0060 when one is sent. Requires content:write. Once the version is published or archived, \u0060title\u0060, \u0060description\u0060, \u0060completionModel\u0060 and \u0060minimumAssessmentScore\u0060 are frozen: a patch that sends any of them is a 409 course.not_draft, so return the course to draft with :unpublish first. \u0060presentation\u0060, \u0060order\u0060, \u0060selfEnrolmentEnabled\u0060 and the enrolment and access window dates stay editable on a live version, but sending any of them then also requires content:publish (403 content.live_edit_requires_publish). The course as merged must still hold together (422): each window must start at or before its end, and the minimum_assessment_score model needs \u0060minimumAssessmentScore\u0060 (course.missing_minimum_assessment_score). A patch that changes nothing returns the course as it stands.",
        "operationId": "UpdateCourse",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CourseMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/CourseMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a course",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/courses/{id}:archive": {
      "post": {
        "description": "Draft or published \u2192 archived. Requires content:publish \u2014 the release verb split out of content:write.",
        "operationId": "ArchiveCourse",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Archive a course",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/courses/{id}:enrol": {
      "post": {
        "description": "Self-enrol: enrols the caller in this course (body-less; method=self). Requires enrolments:write, a published version to pin, with no published version reported as a 409 conflict, and the course\u0027s selfEnrolmentEnabled flag (default false \u2192 403). An active duplicate is 409 \u2014 unlike the generic create\u0027s idempotent 200.",
        "operationId": "SelfEnrolInCourse",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrolmentDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Enrol yourself in a course",
        "tags": [
          "Enrolments"
        ]
      }
    },
    "/v1/courses/{id}:publish": {
      "post": {
        "description": "Draft \u2192 published. Requires content:publish \u2014 the release verb split out of content:write, which authors a draft but does not release it.",
        "operationId": "PublishCourse",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Publish a course",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/courses/{id}:unpublish": {
      "post": {
        "description": "Published or archived \u2192 draft (the recovery path out of archived). Requires content:publish \u2014 the release verb split out of content:write.",
        "operationId": "UnpublishCourse",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Return a course to draft",
        "tags": [
          "Courses"
        ]
      }
    },
    "/v1/data-exports/{jobId}": {
      "get": {
        "description": "Polls a GDPR data-export job. While pending, downloadUrl is null; once succeeded it carries a freshly minted read-only signed URL, capped at the artifact\u0027s expiry (7 days from completion). Requires users:export; another subject\u0027s job needs users:export\u0027s tenant rung (users:export:tenant; out of reach reads as 404).",
        "operationId": "GetDataExport",
        "parameters": [
          {
            "in": "path",
            "name": "jobId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataExportDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get the status of a data export",
        "tags": [
          "Data exports"
        ]
      }
    },
    "/v1/enrolments": {
      "get": {
        "description": "Cursor-paginated (limit/after/sort, includeCount) list of enrolments. Defaults to the caller\u0027s own rows; a userId for another learner \u2014 and the tenant-wide unfiltered list \u2014 require enrolments:read\u0027s tenant rung (enrolments:read:tenant). courseId accepts any version id of a course family; includeDeleted=true surfaces withdrawn rows.",
        "operationId": "ListEnrolments",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "query",
            "name": "userId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "courseId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeDeleted",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfEnrolmentDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List enrolments",
        "tags": [
          "Enrolments"
        ]
      },
      "post": {
        "description": "Enrols a user (userId defaults to the caller; \u0027me\u0027 is the self-alias) in a course\u0027s family, pinning the latest published version. A family with no published version is a 409 course-state conflict. A duplicate active enrolment returns the existing row as 200 (idempotent). Without enrolments:write\u0027s tenant rung (enrolments:write:tenant) only self-enrolment is reachable, the course must allow it (selfEnrolmentEnabled), and deadline/isMandatory may not be set.",
        "operationId": "CreateEnrolment",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateEnrolmentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrolmentDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrolmentDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Enrol a user in a course",
        "tags": [
          "Enrolments"
        ]
      }
    },
    "/v1/enrolments/{enrolmentId}/completions": {
      "get": {
        "description": "Cursor-paginated (limit/after, includeCount) list of this enrolment\u0027s course completions, oldest first. Reachable by the enrolment\u0027s own learner, or progress:read\u0027s tenant rung (progress:read:tenant); a withdrawn enrolment\u0027s history stays readable.",
        "operationId": "ListCourseCompletions",
        "parameters": [
          {
            "in": "path",
            "name": "enrolmentId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfCourseCompletionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List an enrolment\u0027s course completions",
        "tags": [
          "Course completions"
        ]
      },
      "post": {
        "description": "Manually records a course completion against this enrolment \u2014 the sign-off for manual_instructor_sign_off courses and the admin override for every other model (the completionModel need not be satisfied). Requires progress:write\u0027s tenant rung (progress:write:tenant) in addition to progress:write \u2014 the operation admits no self rung: learners\u0027 course completions are derived server-side from their placed completions, never self-asserted.",
        "operationId": "SubmitCourseCompletion",
        "parameters": [
          {
            "in": "path",
            "name": "enrolmentId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitCourseCompletionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "format": "uuid",
                  "type": "string"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Record a course completion",
        "tags": [
          "Course completions"
        ]
      }
    },
    "/v1/enrolments/{enrolmentId}/progress": {
      "get": {
        "description": "The enrolment\u0027s server-derived progress: percentComplete follows the course completionModel (completed required activity placements / lesson placements / best-score-vs-bar / sign-off), with the count models\u0027 raw numerator and denominator, the best placed score, and the enrolment\u0027s earliest course completion. Reachable by the enrolment\u0027s own learner or progress:read\u0027s tenant rung (progress:read:tenant).",
        "operationId": "GetEnrolmentProgress",
        "parameters": [
          {
            "in": "path",
            "name": "enrolmentId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProgressDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get an enrolment\u0027s progress",
        "tags": [
          "Progress"
        ]
      }
    },
    "/v1/enrolments/{id}": {
      "delete": {
        "description": "Withdraws the enrolment. It is kept, with the learner\u0027s progress, and can be brought back: :restore reopens this same enrolment, while a fresh POST /v1/enrolments starts the course again.",
        "operationId": "WithdrawEnrolment",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Withdraw an enrolment",
        "tags": [
          "Enrolments"
        ]
      },
      "get": {
        "description": "Returns one active enrolment. Requires enrolments:read, which reaches the caller\u0027s own enrolments; anyone else\u0027s needs enrolments:read:tenant. One out of reach is the same non-leaky 404 as a missing one, and so is a withdrawn enrolment: list enrolments with \u0060includeDeleted=true\u0060 to see those.",
        "operationId": "GetEnrolmentById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrolmentDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get an enrolment",
        "tags": [
          "Enrolments"
        ]
      },
      "patch": {
        "description": "Merge-patches deadline (present-null clears) and isMandatory. Requires enrolments:write\u0027s tenant rung (enrolments:write:tenant) unconditionally \u2014 they are admin-imposed compliance attributes, 403 even on the caller\u0027s own enrolment.",
        "operationId": "UpdateEnrolment",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnrolmentMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/EnrolmentMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrolmentDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update an enrolment",
        "tags": [
          "Enrolments"
        ]
      }
    },
    "/v1/enrolments/{id}:restore": {
      "post": {
        "description": "Re-enrol \u0027extend\u0027: restores a withdrawn enrolment \u2014 same row, same courseVersionId pin, no published re-check (unpublish keeps enrolments). 409 when the row is active or a restarted active enrolment already occupies the (user, course) slot. Re-enrol \u0027restart\u0027 is a plain POST to the collection instead.",
        "operationId": "RestoreEnrolment",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrolmentDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Restore a withdrawn enrolment",
        "tags": [
          "Enrolments"
        ]
      }
    },
    "/v1/enrolments:bulk": {
      "post": {
        "description": "Bulk-enrols an ordered batch of {userId, courseId} pairs (max 200; items may carry deadline/isMandatory). Requires enrolments:write\u0027s tenant rung (enrolments:write:tenant). Atomic: any invalid item fails the whole batch as one 422 keyed per item; this includes an item whose course family has no published version. A batch of more than 200 is a 422 whose one error is that cap, and no item in it is checked. Active duplicates are idempotent successes returning the existing row.",
        "operationId": "BulkEnrol",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkEnrolRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfBulkEnrolItemResult"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Enrol users in bulk",
        "tags": [
          "Enrolments"
        ]
      }
    },
    "/v1/learning-records": {
      "get": {
        "description": "Cursor-paginated (limit/after, includeCount) list of a subject\u0027s learning records. Defaults to the caller\u0027s own records; an actorUserId for another learner requires progress:read\u0027s tenant rung (progress:read:tenant). Filter by verb (exact) and object (matches object id when a UUID, else the object IRI); actorUserId=me resolves to the caller. Records may also be filtered on their context bag with the dynamic syntax \u0060context.{key}={value}\u0060 (exact match against the top-level member\u0027s text value, e.g. \u0060context.lessonPlacementId={uuid}\u0060); several keys AND-compose, each key takes a single value, and a malformed filter (empty key, empty value, repeated key, or more than ten) is 422. Order with sort=createdAt or sort=-createdAt (the server-stamped recording time); omitting sort orders by id ascending. Prefer -createdAt when you want a learner\u0027s latest record: an id may be client-minted, so it is not a reliable clock.",
        "operationId": "ListLearningRecords",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "query",
            "name": "actorUserId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "verb",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "object",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfLearningRecordDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List learning records",
        "tags": [
          "Learning records"
        ]
      },
      "post": {
        "description": "Captures an xAPI-shaped learning record. The subject is actorUserId (me or omitted \u2192 the caller); another learner needs progress:write\u0027s tenant rung (progress:write:tenant). A completed record whose object is an activity also records a standalone completion of that activity, carrying no question responses; when that completion is refused \u2014 for example 404 for a missing activity, or 422 completion.required_response_missing when the activity has a required question placement, which a record cannot answer \u2014 the whole capture is refused and no record is stored. Returns the new id alone plus a Location header \u2014 the record\u0027s free-text result and context are served only by a read under progress:read, so the write does not echo them back. Read it at GET /v1/learning-records/{id}.",
        "operationId": "CaptureLearningRecord",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaptureLearningRecordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "format": "uuid",
                  "type": "string"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Record a learning record",
        "tags": [
          "Learning records"
        ]
      }
    },
    "/v1/learning-records/{id}": {
      "get": {
        "description": "Returns one learning record, including its free-text \u0060result\u0060 and \u0060context\u0060. Requires progress:read, which reaches records whose \u0060actorUserId\u0060 is the caller; anyone else\u0027s needs progress:read:tenant. One out of reach is the same non-leaky 404 as a missing one.",
        "operationId": "GetLearningRecordById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LearningRecordDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a learning record",
        "tags": [
          "Learning records"
        ]
      }
    },
    "/v1/lesson-completions/{id}": {
      "get": {
        "description": "Returns one lesson completion. Requires progress:read, which reaches the caller\u0027s own completions; anyone else\u0027s needs progress:read:tenant. One out of reach is the same non-leaky 404 as a missing one.",
        "operationId": "GetLessonCompletionById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonCompletionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a lesson completion",
        "tags": [
          "Lesson completions"
        ]
      }
    },
    "/v1/lesson-placements/{id}": {
      "delete": {
        "description": "Permanently removes the placement; the lesson itself, and every completion learners recorded under the placement, are kept. Requires content:write; conditional on \u0060If-Match\u0060 when one is sent. Nothing blocks the removal, whatever the course\u0027s status, so on live content it disappears for learners at once.",
        "operationId": "DeleteLessonPlacement",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Remove a lesson placement",
        "tags": [
          "Lesson placements"
        ]
      },
      "get": {
        "description": "Returns one placement: the lesson version it pins, its position and its own \u0060presentation\u0060. Requires content:read. A placement is returned whatever the status of the placed lesson.",
        "operationId": "GetLessonPlacementById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a lesson placement",
        "tags": [
          "Lesson placements"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch, conditional on \u0060If-Match\u0060 when one is sent. Requires content:write. While the course is published or archived, sending \u0060presentation\u0060 (even unchanged, or \u0060null\u0060) also requires content:publish (403 content.live_edit_requires_publish). The pinned lesson version cannot change: remove the placement and place the other version instead. The position changes only through :reorder.",
        "operationId": "UpdateLessonPlacement",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LessonPlacementMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/LessonPlacementMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a lesson placement",
        "tags": [
          "Lesson placements"
        ]
      }
    },
    "/v1/lesson-placements/{placementId}/completions": {
      "get": {
        "description": "Cursor-paginated (limit/after, includeCount) list of a learner\u0027s placed attempts under this placement. Defaults to the caller\u0027s own attempts; a userId for another learner requires progress:read\u0027s tenant rung (progress:read:tenant).",
        "operationId": "ListPlacedLessonCompletions",
        "parameters": [
          {
            "in": "path",
            "name": "placementId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "query",
            "name": "userId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfLessonCompletionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List a learner\u0027s completions of a lesson placement",
        "tags": [
          "Lesson completions"
        ]
      },
      "post": {
        "description": "Records a placed lesson completion under this lesson placement (keyed userId \u002B placementId; the lesson derives from the placement\u0027s pin). Subject to the module\u0027s lessonSequencing gate \u2014 a blocked linear write is a 409 problem with code completion.prerequisite_not_met whose \u0027unmet\u0027 extension lists the incomplete blocking placements ({entityType, id}) in presentation order. A closed course access window is also a 409 conflict, with code completion.access_window_closed.",
        "operationId": "SubmitPlacedLessonCompletion",
        "parameters": [
          {
            "in": "path",
            "name": "placementId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitLessonCompletionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "format": "uuid",
                  "type": "string"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Record a completion of a lesson placement",
        "tags": [
          "Lesson completions"
        ]
      }
    },
    "/v1/lessons": {
      "get": {
        "description": "Supports filtering by \u0060customFields.{key}={value}\u0060 (422 until custom field definitions exist), \u0060status=draft|published|archived\u0060 (repeat the param for any-of; unknown values 400), ?q= full-text search, ?sort=-createdAt,title, and cursor pagination (limit/after). A caller without content:write never receives draft rows; that filter is applied before search, count, and pagination, including when status=draft is explicit.",
        "operationId": "ListLessons",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfLessonDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List lessons",
        "tags": [
          "Lessons"
        ]
      },
      "post": {
        "description": "Creates version 1 of a new lesson family, in draft; the 201 carries the lesson and its ETag. Requires content:write. Send \u0060id\u0060 to choose the UUID yourself; one already in use is a 409 identifier_in_use. Callers without content:write see the lesson only once it leaves draft, through :publish or :archive.",
        "operationId": "CreateLesson",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLessonCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a lesson",
        "tags": [
          "Lessons"
        ]
      }
    },
    "/v1/lessons/families/{familyId}/latest-published": {
      "get": {
        "description": "Resolves a lesson family to its newest published version: pass the \u0060familyId\u0060, not a version\u0027s id. Requires content:read. A family that is unknown, or that has only draft or archived versions, is a 404 lesson.no_published_version, and so is a version id passed by mistake.",
        "operationId": "GetLatestPublishedLesson",
        "parameters": [
          {
            "in": "path",
            "name": "familyId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get the latest published version of a lesson",
        "tags": [
          "Lessons"
        ]
      }
    },
    "/v1/lessons/{id}": {
      "delete": {
        "description": "Deleting referenced content is a 409 problem with code lesson.in_use whose \u0027referencing\u0027 extension lists the referencing entities ({entityType, id}).",
        "operationId": "DeleteLesson",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a lesson",
        "tags": [
          "Lessons"
        ]
      },
      "get": {
        "description": "Returns the lesson version. A draft is hidden with the ordinary non-leaky 404 unless the caller also holds content:write; archived reads like published.",
        "operationId": "GetLessonById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a lesson",
        "tags": [
          "Lessons"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch, conditional on \u0060If-Match\u0060 when one is sent. Requires content:write. Once the version is published or archived, \u0060title\u0060, \u0060description\u0060 and \u0060activitySequencing\u0060 are frozen: a patch that sends any of them is a 409 lesson.not_draft, so return the lesson to draft with :unpublish first. \u0060presentation\u0060 stays editable on a live version, but sending it then also requires content:publish (403 content.live_edit_requires_publish). A patch that changes nothing returns the lesson as it stands.",
        "operationId": "UpdateLesson",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LessonMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/LessonMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a lesson",
        "tags": [
          "Lessons"
        ]
      }
    },
    "/v1/lessons/{id}:archive": {
      "post": {
        "description": "Draft or published \u2192 archived. Requires content:publish \u2014 the release verb split out of content:write.",
        "operationId": "ArchiveLesson",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Archive a lesson",
        "tags": [
          "Lessons"
        ]
      }
    },
    "/v1/lessons/{id}:publish": {
      "post": {
        "description": "Draft \u2192 published. Requires content:publish \u2014 the release verb split out of content:write, which authors a draft but does not release it.",
        "operationId": "PublishLesson",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Publish a lesson",
        "tags": [
          "Lessons"
        ]
      }
    },
    "/v1/lessons/{id}:unpublish": {
      "post": {
        "description": "Published or archived \u2192 draft (the recovery path out of archived). Requires content:publish \u2014 the release verb split out of content:write.",
        "operationId": "UnpublishLesson",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Return a lesson to draft",
        "tags": [
          "Lessons"
        ]
      }
    },
    "/v1/lessons/{lessonId}/activities": {
      "get": {
        "description": "Every activity placed in the lesson, in placement order, each surfacing its placement ids, the required flag, and a placement self link. A draft lesson is a non-leaky 404 and draft activities are omitted unless the caller also holds content:write.",
        "operationId": "ListLessonActivities",
        "parameters": [
          {
            "in": "path",
            "name": "lessonId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfPlacedActivityDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List the activities placed in a lesson",
        "tags": [
          "Lessons"
        ]
      }
    },
    "/v1/lessons/{lessonId}/activity-placements": {
      "get": {
        "description": "Returns every placement in the lesson, in order, in one unpaged response, with the collection ETag that :reorder takes as \u0060If-Match\u0060. Requires content:read. Placements are listed whatever the status of the lesson or of the placed activity, so the list can name a draft activity\u0027s id; reading that activity itself stays subject to its own visibility. A missing lesson is a 404 lesson.not_found.",
        "operationId": "ListActivityPlacements",
        "parameters": [
          {
            "in": "path",
            "name": "lessonId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfActivityPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The collection\u0027s strong entity tag, covering which members it holds and their order but not their content: send it as If-Match on the collection\u0027s :reorder to refuse the reorder if a member has been added, removed or moved since this response. Opaque. A collection revision, not a cache validator: If-None-Match is not honoured.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List a lesson\u0027s activity placements",
        "tags": [
          "Activity placements"
        ]
      },
      "post": {
        "description": "Places an activity at the end of the lesson; the 201 carries the placement and its ETag. Requires content:write. Any version of an activity that has not been deleted can be placed, a draft included, and the same activity can be placed more than once. \u0060required\u0060 defaults to true; false leaves the activity out of the course\u0027s completion count under the all_activities_completed model, though a linear lesson still makes learners take it in order. The lesson\u0027s status is not checked, so on live content the placement takes effect at once. A placed draft version is hidden from callers without content:write, yet in a linear lesson it still holds learners back from what follows it. A missing lesson is a 404 lesson.not_found. An \u0060activityId\u0060 that does not exist is a 422 activity_placement.activity_not_found, and an \u0060id\u0060 already in use is a 409 identifier_in_use. While placed, the activity cannot be deleted (409 activity.in_use).",
        "operationId": "CreateActivityPlacement",
        "parameters": [
          {
            "in": "path",
            "name": "lessonId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateActivityPlacementRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityPlacementDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Place an activity in a lesson",
        "tags": [
          "Activity placements"
        ]
      }
    },
    "/v1/lessons/{lessonId}/activity-placements:reorder": {
      "post": {
        "description": "Sets the order of the lesson\u0027s activity placements. \u0060placementIds\u0060 must list every placement in the lesson exactly once: a missing or extra id is a 409 activity_placement.reorder_set_mismatch, and an empty or repeating list is a 422. Requires content:write; conditional on the placement list\u0027s collection ETag when \u0060If-Match\u0060 is sent. Returns the reordered placements and the new collection ETag; only placements whose position changed are rewritten, so only they get new ETags. The order drives the lesson\u0027s \u0060activitySequencing\u0060. The lesson\u0027s status is not checked, so on live content the new order is visible at once.",
        "operationId": "ReorderActivityPlacements",
        "parameters": [
          {
            "in": "path",
            "name": "lessonId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the collection\u0027s ETag, from its GET or from the last reorder\u0027s response. When a member has been added, removed or moved since (the collection\u0027s current ETag differs), nothing is reordered and the response is 412 {entity}.precondition_failed \u2014 read the collection again and resend with the new ETag. A member\u0027s own edits do not change the collection\u0027s tag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing collection. Absent, the reorder is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReorderActivityPlacementsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedCollectionOfActivityPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The collection\u0027s strong entity tag, covering which members it holds and their order but not their content: send it as If-Match on the collection\u0027s :reorder to refuse the reorder if a member has been added, removed or moved since this response. Opaque. A collection revision, not a cache validator: If-None-Match is not honoured.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Reorder a lesson\u0027s activity placements",
        "tags": [
          "Activity placements"
        ]
      }
    },
    "/v1/lessons/{lessonId}/completions": {
      "get": {
        "description": "Cursor-paginated (limit/after, includeCount) list of a learner\u0027s standalone completions of this lesson. Defaults to the caller\u0027s own completions; a userId for another learner requires progress:read\u0027s tenant rung (progress:read:tenant).",
        "operationId": "ListLessonCompletions",
        "parameters": [
          {
            "in": "path",
            "name": "lessonId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "query",
            "name": "userId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfLessonCompletionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List a learner\u0027s completions of a lesson",
        "tags": [
          "Lesson completions"
        ]
      },
      "post": {
        "description": "Records a standalone lesson completion (keyed userId \u002B lessonId, null placement) \u2014 client-written, never derived; bypasses sequencing and access windows.",
        "operationId": "SubmitLessonCompletion",
        "parameters": [
          {
            "in": "path",
            "name": "lessonId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitLessonCompletionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "format": "uuid",
                  "type": "string"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Record a completion of a lesson",
        "tags": [
          "Lesson completions"
        ]
      }
    },
    "/v1/lessons/{lessonId}/placements": {
      "get": {
        "description": "Cursor-paginated (limit/after, includeCount) list of the placements that pin this lesson, across all modules.",
        "operationId": "ListLessonUsages",
        "parameters": [
          {
            "in": "path",
            "name": "lessonId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfLessonPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List where a lesson is placed",
        "tags": [
          "Lesson placements"
        ]
      }
    },
    "/v1/lessons/{lessonId}/structure": {
      "get": {
        "description": "The lesson with its placed activities in placement order \u2014 the placement-independent core of a course-tree lesson node. Optional ?userId=me overlays that user\u0027s latest completion state on each activity node (needs progress:read; another user needs its tenant rung, progress:read:tenant). A draft lesson is a non-leaky 404 and draft activities are omitted unless the caller also holds content:write.",
        "operationId": "GetLessonStructure",
        "parameters": [
          {
            "in": "path",
            "name": "lessonId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "query",
            "name": "userId",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LessonStructureDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a lesson\u0027s structure",
        "tags": [
          "Lessons"
        ]
      }
    },
    "/v1/plan/features": {
      "get": {
        "description": "The active tenant\u0027s resolved boolean plan features. Requires plan:read. Includes disabled features; exposes no prices, billing records or override reasons.",
        "operationId": "GetPlanFeatures",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanFeaturesDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get the tenant\u0027s plan features",
        "tags": [
          "Capabilities"
        ]
      }
    },
    "/v1/question-placements/{id}": {
      "delete": {
        "description": "Permanently removes the placement; the question itself, and every answer learners submitted under the placement, are kept. Requires content:write; conditional on \u0060If-Match\u0060 when one is sent. Nothing blocks the removal, whatever the activity\u0027s status, so on live content it disappears for learners at once.",
        "operationId": "DeleteQuestionPlacement",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Remove a question placement",
        "tags": [
          "Question placements"
        ]
      },
      "get": {
        "description": "Returns one placement: the question version it pins, its position and its own \u0060presentation\u0060. Requires content:read.",
        "operationId": "GetQuestionPlacementById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuestionPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a question placement",
        "tags": [
          "Question placements"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch, conditional on \u0060If-Match\u0060 when one is sent. Requires content:write. While the activity is published or archived, sending \u0060presentation\u0060 (even unchanged, or \u0060null\u0060) also requires content:publish (403 content.live_edit_requires_publish). The pinned question version cannot change: remove the placement and place the other version instead. The position changes only through :reorder. \u0060required\u0060 (true or false; \u0060null\u0060 is a 422) needs content:write alone, even on live content, and from the next submit decides whether a completion must answer this placement.",
        "operationId": "UpdateQuestionPlacement",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuestionPlacementMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/QuestionPlacementMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuestionPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a question placement",
        "tags": [
          "Question placements"
        ]
      }
    },
    "/v1/questions": {
      "get": {
        "description": "Supports filtering by \u0060customFields.{key}={value}\u0060 (422 until custom field definitions exist), \u0060type={question type}\u0060 (repeat the param for any-of; unknown values 400), ?q= full-text search (matches the prompt only), ?sort=-createdAt,prompt, and cursor pagination (limit/after).",
        "operationId": "ListQuestions",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfQuestionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List questions",
        "tags": [
          "Questions"
        ]
      },
      "post": {
        "description": "Creates a question; the 201 carries it, without its answer key, and its ETag. Requires content:write, which is enough to write an answer key; reading one back takes answer-keys.read. The options and definition must fit the question\u0027s \u0060type\u0060 (422): multiple_choice_single and multiple_choice_multi take at least two options, and marking one or more \u0060isCorrect\u0060 makes the question keyed; true_false keeps its answer in \u0060definition.answer\u0060, and short_answer may list \u0060definition.acceptedAnswers\u0060. likert and rating are scales: at least two options, each with a numeric \u0060value\u0060, no two values equal (1 and 1.0 are the same), never keyed, so \u0060isCorrect\u0060 and \u0060correctRank\u0060 are refused. free_text takes no options, and its definition may be any object except one carrying \u0060answer\u0060 or \u0060acceptedAnswers\u0060 \u2014 it is never keyed. Send \u0060id\u0060 to choose the UUID yourself; one already in use is a 409 identifier_in_use.",
        "operationId": "CreateQuestion",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateQuestionCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuestionDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a question",
        "tags": [
          "Questions"
        ]
      }
    },
    "/v1/questions/{id}": {
      "delete": {
        "description": "Deleting referenced content is a 409 problem with code question.in_use whose \u0027referencing\u0027 extension lists the referencing entities ({entityType, id}).",
        "operationId": "DeleteQuestion",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a question",
        "tags": [
          "Questions"
        ]
      },
      "get": {
        "description": "Returns the question without its answer key, whatever the caller holds; the key and the explanation have their own read, offered as the \u0060answerKey\u0060 link to callers holding answer-keys.read. Requires content:read. Questions have no draft status, so every question is readable.",
        "operationId": "GetQuestionById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuestionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a question",
        "tags": [
          "Questions"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch, conditional on \u0060If-Match\u0060 when one is sent. Requires content:write. Questions have no draft status, so an edit reaches every activity the question is placed in at once. \u0060type\u0060 can never change (422). \u0060options\u0060, when sent, is the complete list: an option with a known \u0060id\u0060 is updated, one without an \u0060id\u0060 is added, and a stored option left out is removed. Answer-key members left out keep their stored values, so a caller who cannot read the key can still edit around it; \u0060null\u0060 clears one. A definition or options that do not fit the type are a 422 question.invalid_definition or question.invalid_options \u2014 including a likert or rating option sent without its \u0060value\u0060, which would clear it, and a free_text definition carrying \u0060answer\u0060 or \u0060acceptedAnswers\u0060.",
        "operationId": "UpdateQuestion",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          },
          {
            "description": "Optional precondition: the ETag of the read this write is based on. When the row has changed since (its current ETag differs), nothing is written and the response is 412 {entity}.precondition_failed \u2014 reload, reapply the change, and resend with the new ETag. Strong comparison: a weak tag (W/\u0022\u2026\u0022), a malformed value or an empty header never matches. \u0027*\u0027 matches any existing row. Absent, the write is unconditional.",
            "in": "header",
            "name": "If-Match",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuestionMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/QuestionMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuestionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "ETag": {
                "description": "The row\u0027s strong entity tag: send it as If-Match on a write to this resource that accepts one, to refuse the write if the row has changed since this response. Opaque \u2014 never derive it from updatedAt. A row revision, not a cache validator: If-None-Match is not honoured. Composed reads carry none; GET the resource for its tag.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "412": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Precondition Failed",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a question",
        "tags": [
          "Questions"
        ]
      }
    },
    "/v1/questions/{id}/answer-key": {
      "get": {
        "description": "Returns the question\u0027s answer key \u2014 the per-option isCorrect/correctRank, the true_false answer, the short_answer acceptedAnswers, and the explanation. Requires content:read \u002B answer-keys.read. An unscored (survey) question is a 200 whose key fields are all null.",
        "operationId": "GetQuestionAnswerKey",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuestionAnswerKeyDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a question\u0027s answer key",
        "tags": [
          "Questions"
        ]
      }
    },
    "/v1/questions/{questionId}/placements": {
      "get": {
        "description": "Cursor-paginated (limit/after, includeCount) list of the placements that pin this question, across all activities.",
        "operationId": "ListQuestionUsages",
        "parameters": [
          {
            "in": "path",
            "name": "questionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfQuestionPlacementDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List where a question is placed",
        "tags": [
          "Question placements"
        ]
      }
    },
    "/v1/reports/courses": {
      "get": {
        "description": "The courses completion report: one row per course FAMILY with a published live version (drafts excluded by the view predicate), identified by its latest published version, with enrolled / completed / overdue / mandatoryOutstanding counts over the family\u0027s live enrolments. Every published family rows; status= shapes the counts only; courseId= (any version id) restricts to that family; completed counts inside the half-open [dateFrom, dateTo) window (default: the trailing 30 days). Cursor pagination (limit/after). Requires reporting:read AND its tenant rung (reporting:read:tenant).",
        "operationId": "ListCourseReport",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "dateFrom",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "dateTo",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "courseId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfCourseReportRowDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Report completion by course",
        "tags": [
          "Reports"
        ]
      }
    },
    "/v1/reports/courses/{id}": {
      "get": {
        "description": "The named course\u0027s per-learner drill-down: one row per live enrolment on the family with the learner\u0027s identity, pinned version, server-derived percentComplete, derived status, compliance attributes, and completion timestamp. {id} is any course version id, resolved to its family; a family with no published version 404s like an unknown id. Same window/status semantics as the list. Requires reporting:read AND its tenant rung (reporting:read:tenant).",
        "operationId": "GetCourseReportDetail",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "dateFrom",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "dateTo",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfCourseReportDetailRowDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Report completion for one course, by learner",
        "tags": [
          "Reports"
        ]
      }
    },
    "/v1/reports/users": {
      "get": {
        "description": "The users completion report: one row per active user with enrolled / completed / overdue / mandatoryOutstanding counts over their live enrolments. completed counts completions inside the half-open [dateFrom, dateTo) window (default: the trailing 30 days); the other counts are current-state. Filters: courseId (any version id \u2014 filters on its family), status (repeat the param for any-of: not_started, in_progress, completed, overdue; filtered lists row only matching users). Cursor pagination (limit/after). Requires reporting:read AND its tenant rung (reporting:read:tenant).",
        "operationId": "ListUserReport",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "dateFrom",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "dateTo",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "courseId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfUserReportRowDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Report completion by user",
        "tags": [
          "Reports"
        ]
      }
    },
    "/v1/reports/users/{id}": {
      "get": {
        "description": "The named user\u0027s per-enrolment drill-down: pinned course, server-derived percentComplete (per the course completionModel), derived status (completed \u003E overdue \u003E in_progress \u003E not_started), compliance attributes, and the completion-of-record timestamp. Same filters and window semantics as the list (rows whose completion falls outside the window drop; incomplete rows always stay). {id} is a user UUID \u2014 \u0027me\u0027 is not an alias on this admin surface (the self read is GET /v1/users/me/progress). Requires reporting:read AND its tenant rung (reporting:read:tenant).",
        "operationId": "GetUserReportDetail",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "dateFrom",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "dateTo",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "courseId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfUserReportDetailRowDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Report completion for one user, by enrolment",
        "tags": [
          "Reports"
        ]
      }
    },
    "/v1/settings": {
      "get": {
        "description": "Returns the tenant\u0027s settings. Requires settings:read. Never 404s \u2014 a tenant that has never written settings gets the composed defaults.",
        "operationId": "GetSettings",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantSettingsDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get the tenant\u0027s settings",
        "tags": [
          "Settings"
        ]
      },
      "patch": {
        "description": "RFC 7396 merge patch of the tenant\u0027s settings. Requires settings:write. A present-null field is a 422 (the flag is non-nullable); an absent field is left untouched.",
        "operationId": "UpdateSettings",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenantSettingsMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/TenantSettingsMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantSettingsDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update the tenant\u0027s settings",
        "tags": [
          "Settings"
        ]
      }
    },
    "/v1/tag-groups": {
      "get": {
        "description": "Supports filtering by \u0060customFields.{key}={value}\u0060 (422 until custom field definitions exist), \u0060name={exact name}\u0060, ?q= full-text search, ?sort=-createdAt,name, and cursor pagination (limit/after).",
        "operationId": "ListTagGroups",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "name",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfTagGroupDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List tag groups",
        "tags": [
          "Tag groups"
        ]
      },
      "post": {
        "description": "Creates a tag group. Requires content:write. A name already used by another tag group is a 409 tag_group.name_in_use, and an \u0060id\u0060 already in use is a 409 identifier_in_use.",
        "operationId": "CreateTagGroup",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTagGroupCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagGroupDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a tag group",
        "tags": [
          "Tag groups"
        ]
      }
    },
    "/v1/tag-groups/{id}": {
      "delete": {
        "description": "Permanently deletes the tag group and every tag in it; there is no restore. Requires content:write.",
        "operationId": "DeleteTagGroup",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a tag group",
        "tags": [
          "Tag groups"
        ]
      },
      "get": {
        "description": "Returns one tag group, including whether it is exclusive and which entity types it applies to. Requires content:read.",
        "operationId": "GetTagGroupById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagGroupDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a tag group",
        "tags": [
          "Tag groups"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch. Requires content:write. \u0060exclusive\u0060 and \u0060appliesTo\u0060 cannot be set to \u0060null\u0060; send \u0060[]\u0060 to clear \u0060appliesTo\u0060. A name already used by another tag group is a 409 tag_group.name_in_use. Tag groups carry no ETag, so this write takes no \u0060If-Match\u0060.",
        "operationId": "UpdateTagGroup",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagGroupMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/TagGroupMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagGroupDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a tag group",
        "tags": [
          "Tag groups"
        ]
      }
    },
    "/v1/tags": {
      "get": {
        "description": "Supports filtering by \u0060customFields.{key}={value}\u0060 (422 until custom field definitions exist), \u0060tagGroupId={uuid}\u0060, \u0060name={exact name}\u0060, ?q= full-text search, ?sort=-createdAt,name, and cursor pagination (limit/after).",
        "operationId": "ListTags",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "tagGroupId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "name",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfTagDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List tags",
        "tags": [
          "Tags"
        ]
      },
      "post": {
        "description": "Creates a tag in a tag group. Requires content:write. A \u0060tagGroupId\u0060 that does not exist is a 422 tag.tag_group_not_found, a name already used in the group is a 409 tag.name_in_use, and an \u0060id\u0060 already in use is a 409 identifier_in_use.",
        "operationId": "CreateTag",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTagCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a tag",
        "tags": [
          "Tags"
        ]
      }
    },
    "/v1/tags/{id}": {
      "delete": {
        "description": "Permanently deletes the tag; there is no restore. Requires content:write.",
        "operationId": "DeleteTag",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a tag",
        "tags": [
          "Tags"
        ]
      },
      "get": {
        "description": "Returns one tag, including the id of its tag group. Requires content:read.",
        "operationId": "GetTagById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a tag",
        "tags": [
          "Tags"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch to the tag\u0027s name and description; a tag stays in the group it was created in. Requires content:write. A name already used in the group is a 409 tag.name_in_use. Tags carry no ETag, so this write takes no \u0060If-Match\u0060.",
        "operationId": "UpdateTag",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/TagMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a tag",
        "tags": [
          "Tags"
        ]
      }
    },
    "/v1/users": {
      "get": {
        "description": "Supports ?q= full-text search over displayName, ?sort=-createdAt,displayName, and cursor pagination (limit/after). Requires users:read; without users:read:tenant the result is the caller\u0027s own row only \u2014 the list silently restricts rather than refusing.",
        "operationId": "ListUsers",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "id",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfUserDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List users",
        "tags": [
          "Users"
        ]
      }
    },
    "/v1/users/membership:reactivate": {
      "post": {
        "description": "System-only (system:memberships:sync): lifts a membership-seat suspension, restoring login and the writes that act for the subject. The mirror of membership:suspend, idempotent the same way. It can never resurrect an offboarded (deactivated) profile \u2014 that switch is terminal and has no reactivate.",
        "operationId": "ReactivateUserMembership",
        "parameters": [
          {
            "description": "The tenant this platform-service call acts in. A system caller\u0027s credentials are capability-scoped and carry no tenant claim, so this header is authoritative: missing or malformed is 400. X-Tenant-Id is not read on this operation, and a caller that is not a platform service is refused (403) whatever it sends.",
            "in": "header",
            "name": "X-Seren-Tenant",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MembershipSubjectBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Lift a membership suspension",
        "tags": [
          "Users"
        ]
      }
    },
    "/v1/users/membership:suspend": {
      "post": {
        "description": "System-only (system:memberships:sync): suspends the Platform profile of the given identity subject, so a suspended membership also blocks the profile. The seat, not offboarding \u2014 reversible, and distinct from POST /v1/users/{id}:deactivate. The profile stays readable, listed and patchable; what refuses is the next login (permissions:resolve) and the learner-acting writes (enrolment, lesson and activity completion, learning records) \u2014 not the manual course-completion sign-off. Idempotent \u2014 an already-suspended subject is a 200 with no event. 404 when no live profile matches the subject: a member who has never logged in has none yet, and an offboarded one stays gone.",
        "operationId": "SuspendUserMembership",
        "parameters": [
          {
            "description": "The tenant this platform-service call acts in. A system caller\u0027s credentials are capability-scoped and carry no tenant claim, so this header is authoritative: missing or malformed is 400. X-Tenant-Id is not read on this operation, and a caller that is not a platform service is refused (403) whatever it sends.",
            "in": "header",
            "name": "X-Seren-Tenant",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MembershipSubjectBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Suspend a membership",
        "tags": [
          "Users"
        ]
      }
    },
    "/v1/users/permissions:read": {
      "post": {
        "description": "System-only (system:permissions:resolve): the permission set of a subject permissions:resolve has already provisioned, read when a refresh token is redeemed. Writes nothing: no provisioning, no seeding, no event. 403 user.suspended for a suspended seat; 404 user.not_found for an unknown or offboarded subject. Returns 200 with { userId, permissions } and no _links.",
        "operationId": "ReadSubjectPermissions",
        "parameters": [
          {
            "description": "The tenant this platform-service call acts in. A system caller\u0027s credentials are capability-scoped and carry no tenant claim, so this header is authoritative: missing or malformed is 400. X-Tenant-Id is not read on this operation, and a caller that is not a platform service is refused (403) whatever it sends.",
            "in": "header",
            "name": "X-Seren-Tenant",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReadSubjectPermissionsBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResolvedPermissionsDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Read a signed-in subject\u0027s permissions",
        "tags": [
          "Permissions"
        ]
      }
    },
    "/v1/users/permissions:resolve": {
      "post": {
        "description": "System-only JIT provisioning (system:permissions:resolve): resolves a federated login to its canonical user id and permission set, seeding the initial set on first provision. Returns 200 with { userId, permissions } and no _links.",
        "operationId": "ResolveUserPermissions",
        "parameters": [
          {
            "description": "The tenant this platform-service call acts in. A system caller\u0027s credentials are capability-scoped and carry no tenant claim, so this header is authoritative: missing or malformed is 400. X-Tenant-Id is not read on this operation, and a caller that is not a platform service is refused (403) whatever it sends.",
            "in": "header",
            "name": "X-Seren-Tenant",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResolvePermissionsBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResolvedPermissionsDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Resolve a sign-in to a user and permissions",
        "tags": [
          "Permissions"
        ]
      }
    },
    "/v1/users/{id}": {
      "get": {
        "description": "Returns a user\u0027s profile, without contact details: those have their own read, which needs pii.read for anyone but the caller. \u0060{id}\u0060 accepts \u0060me\u0060. Requires users:read; another user needs users:read:tenant. A user out of reach, unknown or deactivated is the same non-leaky 404 user.not_found; a suspended user is returned.",
        "operationId": "GetUserById",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a user",
        "tags": [
          "Users"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch to \u0060displayName\u0060 and \u0060contactEmail\u0060; \u0060null\u0060 clears \u0060contactEmail\u0060. \u0060{id}\u0060 accepts \u0060me\u0060. Requires users:write; another user needs users:write:tenant, and one out of reach is a non-leaky 404 user.not_found. The response never includes \u0060contactEmail\u0060: read it back through the user\u0027s contact read, which needs pii.read for anyone but the caller.",
        "operationId": "UpdateUser",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/UserMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a user",
        "tags": [
          "Users"
        ]
      }
    },
    "/v1/users/{id}/contact": {
      "get": {
        "description": "Returns the user\u0027s PII contact details. Requires users:read \u002B pii.read \u2014 except on the caller\u0027s own row (/me, or their own id), which users:read alone reads.",
        "operationId": "GetUserContact",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserContactDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a user\u0027s contact details",
        "tags": [
          "Users"
        ]
      }
    },
    "/v1/users/{id}/permissions": {
      "get": {
        "description": "Returns the user\u0027s assigned permission set. Requires users:read (or a system caller with system:permissions:resolve). Supports If-None-Match \u2192 304.",
        "operationId": "GetUserPermissions",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserPermissionsDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a user\u0027s permissions",
        "tags": [
          "Permissions"
        ]
      },
      "patch": {
        "description": "Grants the permissions in \u0060add\u0060 and revokes those in \u0060remove\u0060, leaving the rest of the user\u0027s set as it is. The body is this add-and-remove list, not a merge patch, and is sent as application/json. It must name at least one permission, and a permission may not appear in both lists (422). Adding a permission the user already has, or removing one they lack, changes nothing and needs nothing held. Requires users:write and users:write:tenant, and never applies to the caller\u0027s own permissions (403 permission.cannot_modify_own); without users:write:tenant another user is a non-leaky 404. A permission that cannot be assigned to a user in a tenant is a 422 permission.invalid. Each list may hold no more entries than there are assignable permissions; a longer one is a 422 validation.failed whose one error is that cap. The caller must hold every permission the change would grant or revoke, else a 403 permission.not_held; this is judged on the change, not on the resulting set. The user\u0027s tokens pick up the change at their next sign-in or token refresh.",
        "operationId": "AmendUserPermissions",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AmendPermissionsBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserPermissionsDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Grant or revoke some of a user\u0027s permissions",
        "tags": [
          "Permissions"
        ]
      },
      "put": {
        "description": "Replaces the user\u0027s whole permission set with \u0060permissions\u0060; \u0060[]\u0060 removes every permission. Requires users:write and users:write:tenant, and never applies to the caller\u0027s own permissions (403 permission.cannot_modify_own); without users:write:tenant another user is a non-leaky 404. A permission that cannot be assigned to a user in a tenant is a 422 permission.invalid. \u0060permissions\u0060 may list no more entries than there are assignable permissions; a longer list is a 422 validation.failed whose one error is that cap. The caller must hold every permission the change would grant or revoke, else a 403 permission.not_held; this is judged on the change, not on the resulting set. The user\u0027s tokens pick up the change at their next sign-in or token refresh.",
        "operationId": "ReplaceUserPermissions",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplacePermissionsBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserPermissionsDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Replace a user\u0027s permissions",
        "tags": [
          "Permissions"
        ]
      }
    },
    "/v1/users/{id}/progress": {
      "get": {
        "description": "The user\u0027s cross-enrolment progress: one row per live enrolment with the pinned course, the server-derived percentComplete (per the course completionModel), the derived status, compliance attributes, and the completion timestamp. {id} accepts \u0027me\u0027 (user tokens). Own rows on the bare verb; any user with progress:read\u0027s tenant rung (progress:read:tenant) \u2014 out-of-reach/unknown/deactivated subjects are one non-leaky 404. Windowless: the reporting-gated, windowed report view of the same rows is GET /v1/reports/users/{id}. Cursor pagination (limit/after).",
        "operationId": "ListUserProgress",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfUserProgressItemDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a user\u0027s progress across enrolments",
        "tags": [
          "Progress"
        ]
      }
    },
    "/v1/users/{id}:deactivate": {
      "post": {
        "description": "Deactivates the platform account (the soft delete \u2014 the row and all progress referencing it are preserved). Requires memberships:manage, the offboarding verb: correcting a profile is users:write, suspending an account is not. Reaching a user needs memberships:manage:tenant; never the caller\u0027s own account. Idempotent \u2014 re-deactivating is 404.",
        "operationId": "DeactivateUser",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Deactivate a user",
        "tags": [
          "Users"
        ]
      }
    },
    "/v1/users/{id}:erase": {
      "post": {
        "description": "GDPR erasure: redacts the user\u0027s PII at source (display name, contact email, question responses, learning-record result/context, and the user\u0027s own domain-event payloads), withdraws enrolments, drops permission assignments, and tombstones the row. Requires users:erase (\u002B users:erase:tenant to reach a user \u2014 the verb admits no self rung, so the bare verb reaches nothing) and the X-Confirm-Destructive: true header. Never on self. Idempotent.",
        "operationId": "EraseUser",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Forbidden",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Erase a user\u0027s personal data",
        "tags": [
          "Users"
        ]
      }
    },
    "/v1/users/{id}:export": {
      "post": {
        "description": "GDPR data export: enqueues an asynchronous job that bundles the user\u0027s data (profile incl. contact details, permissions, enrolments, completions, question responses, learning records) into an L3 artifact. Returns 202 \u002B jobId; poll GET /v1/data-exports/{jobId} for the signed download URL (7-day TTL from completion). Requires users:export (\u002B users:export:tenant to reach another user); self-allowed \u2014 /me:export is the data-subject access right.",
        "operationId": "ExportUser",
        "parameters": [
          {
            "description": "A user id, or \u0060me\u0060 for the caller\u0027s own user.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataExportAccepted"
                }
              }
            },
            "description": "Accepted",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Export a user\u0027s personal data",
        "tags": [
          "Users"
        ]
      }
    },
    "/v1/webhook-subscriptions": {
      "get": {
        "description": "Supports filtering by \u0060active=true|false\u0060 and \u0060eventType={dotted name}\u0060 (repeat the param for any-of), ?sort=-createdAt,name, and cursor pagination (limit/after). Responses never include the signing secret.",
        "operationId": "ListWebhookSubscriptions",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "format": "int32",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "after",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "active",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "in": "query",
            "name": "eventType",
            "schema": {
              "items": {
                "type": "string"
              },
              "type": "array"
            }
          },
          {
            "in": "query",
            "name": "includeCount",
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CursorPageOfWebhookSubscriptionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "List webhook subscriptions",
        "tags": [
          "Webhooks"
        ]
      },
      "post": {
        "description": "Returns the created subscription with its signing secret \u2014 echoed exactly once, here; store it now. A lost secret means delete \u002B recreate. An idempotent replay (same Idempotency-Key) returns the subscription with secret: null \u2014 the stored replay payload is redacted. \u0060eventTypes\u0060 lists 1 to 100 event types, each at most 200 characters; a longer list is a 422 whose one error is that cap, and no entry in it is checked.",
        "operationId": "CreateWebhookSubscription",
        "parameters": [
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookSubscriptionCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionCreatedDto"
                }
              }
            },
            "description": "Created",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Create a webhook subscription",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/v1/webhook-subscriptions/{id}": {
      "delete": {
        "description": "Permanently deletes the subscription and discards every delivery still queued for it. Requires webhooks:manage. To stop deliveries and keep the queue, set \u0060active\u0060 to false instead.",
        "operationId": "DeleteWebhookSubscription",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Delete a webhook subscription",
        "tags": [
          "Webhooks"
        ]
      },
      "get": {
        "description": "Returns one webhook subscription. The signing secret is never included: it is shown once, in the response that created the subscription. Requires webhooks:manage.",
        "operationId": "GetWebhookSubscriptionById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Get a webhook subscription",
        "tags": [
          "Webhooks"
        ]
      },
      "patch": {
        "description": "Applies an RFC 7396 merge patch to \u0060name\u0060, \u0060targetUrl\u0060, \u0060eventTypes\u0060 and \u0060active\u0060, none of which may be \u0060null\u0060. Requires webhooks:manage. \u0060active: false\u0060 pauses delivery: events are no longer queued for the subscription, and deliveries already queued wait until it is active again, then go to the current \u0060targetUrl\u0060. A name already used by another subscription is a 409 webhook_subscription.name_in_use. \u0060eventTypes\u0060 follows the create rules: 1 to 100 entries, each at most 200 characters; a longer list is a 422 whose one error is that cap. The signing secret cannot change; delete the subscription and create a new one to rotate it.",
        "operationId": "UpdateWebhookSubscription",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "The tenant the request acts in. Cross-checked against the token\u0027s tenant claim: a mismatch is 403, a missing header 400.",
            "in": "header",
            "name": "X-Tenant-Id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Optional client-supplied key that makes this mutation safe to retry. Mint one key per logical submission and resend it only with the identical request \u2014 the same route, body and If-Match. For 24 hours a resend from the same caller returns the original stored response without running again; the same key with a different request, or from another caller, is refused with 422 idempotency.key_reuse. Honoured on POST/PUT/PATCH/DELETE; on a DELETE it is what lets a retry after a lost response receive the original 204 rather than a 404.",
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Required for supported delegated-agent mutations: unpadded base64url UTF-8 reason, nonblank and at most 500 characters after decoding. Do not include secrets, answer keys or personal data; the reason is kept in the audit log when a user is erased. Ignored for ordinary callers; supplying this header grants no agent authority.",
            "in": "header",
            "name": "X-Seren-Agent-Reason",
            "schema": {
              "maxLength": 2668,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookSubscriptionMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/WebhookSubscriptionMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionDto"
                }
              }
            },
            "description": "OK",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Bad Request",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Not Found",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "409": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Conflict",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            },
            "description": "Unprocessable Entity",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Update a webhook subscription",
        "tags": [
          "Webhooks"
        ]
      }
    }
  },
  "tags": [
    {
      "description": "Courses and their published versions, plus the composed reads of a course\u0027s modules, lessons, outline and structure.",
      "name": "Courses"
    },
    {
      "description": "The ordered sections a course is divided into.",
      "name": "Modules"
    },
    {
      "description": "Reusable lessons and their published versions, plus the composed reads of a lesson\u0027s activities and structure.",
      "name": "Lessons"
    },
    {
      "description": "Reusable activities and their published versions, plus the composed read of an activity\u0027s questions.",
      "name": "Activities"
    },
    {
      "description": "Reusable questions, and the answer key for callers permitted to read it.",
      "name": "Questions"
    },
    {
      "description": "Where a lesson sits in a course module, in order, with the presentation it has there.",
      "name": "Lesson placements"
    },
    {
      "description": "Where an activity sits in a lesson, in order, with the presentation it has there.",
      "name": "Activity placements"
    },
    {
      "description": "Where a question sits in an activity, in order, with the presentation it has there.",
      "name": "Question placements"
    },
    {
      "description": "Labels that classify content.",
      "name": "Tags"
    },
    {
      "description": "Named sets of tags, such as a subject or a level.",
      "name": "Tag groups"
    },
    {
      "description": "A learner\u0027s place on a course: enrolling, withdrawing and restoring, singly or in bulk.",
      "name": "Enrolments"
    },
    {
      "description": "Evidence that a learner completed a lesson.",
      "name": "Lesson completions"
    },
    {
      "description": "Evidence that a learner completed an activity, with their responses and score.",
      "name": "Activity completions"
    },
    {
      "description": "Evidence that a learner completed the course an enrolment is for.",
      "name": "Course completions"
    },
    {
      "description": "A learner\u0027s progress through an enrolment, or across all of their enrolments.",
      "name": "Progress"
    },
    {
      "description": "Learning activity recorded as xAPI-shaped statements: who did what, to what, with what result.",
      "name": "Learning records"
    },
    {
      "description": "Tenant-wide summaries of enrolment and completion, by course and by learner.",
      "name": "Reports"
    },
    {
      "description": "The people in a tenant: profiles, contact details, membership, deactivation, erasure and data export.",
      "name": "Users"
    },
    {
      "description": "The permissions each user holds in the tenant.",
      "name": "Permissions"
    },
    {
      "description": "Software agents that act for a user, and the authority each has been delegated.",
      "name": "Agents"
    },
    {
      "description": "The status of a requested export of a user\u0027s personal data.",
      "name": "Data exports"
    },
    {
      "description": "Subscriptions that deliver the tenant\u0027s events to an endpoint you run.",
      "name": "Webhooks"
    },
    {
      "description": "The tenant\u0027s own configuration.",
      "name": "Settings"
    },
    {
      "description": "What the calling token may do, and which features the tenant\u0027s plan includes.",
      "name": "Capabilities"
    }
  ]
}
