{
  "components": {
    "schemas": {
      "ActivateComponentBundleRequest": {
        "description": "The body of \u0060:activate-bundle\u0060 and \u0060:activate-editor-bundle\u0060: the id of one of\nthis component\u0027s uploaded bundles, to pin.",
        "example": {
          "bundleId": "019cb885-c360-75c7-b2e1-b595a665fce8"
        },
        "properties": {
          "bundleId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "bundleId"
        ],
        "type": "object"
      },
      "ActivityTypeDto": {
        "description": "One registered activity component, as listed by\n\u0060GET /v1/components:activity-types\u0060 for an authoring client\u0027s picker of\nactivity kinds. Disabled components are listed too, with \u0060status\u0060 set to\n\u0060disabled\u0060, so a client can show them as unavailable rather than hide them.",
        "example": {
          "componentUri": "https://components.example.com/card-sort",
          "title": "Card sort",
          "status": "enabled"
        },
        "properties": {
          "componentUri": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "componentUri",
          "title",
          "status"
        ],
        "type": "object"
      },
      "BlockTypeDto": {
        "description": "One registered block type, as listed by \u0060GET /v1/components:block-types\u0060\nfor a content author\u0027s block editor. \u0060allowedSubjects\u0060 names where the type\nmay be placed. Disabled types are listed too, with \u0060status\u0060 set to\n\u0060disabled\u0060, so a client can show them as unavailable rather than hide them.",
        "example": {
          "componentUri": "https://blocks.example.com/callout",
          "title": "Callout",
          "allowedSubjects": [
            "lesson",
            "module"
          ],
          "status": "enabled"
        },
        "properties": {
          "allowedSubjects": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "componentUri": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "componentUri",
          "title",
          "allowedSubjects",
          "status"
        ],
        "type": "object"
      },
      "CapabilitiesDto": {
        "example": {
          "operations": [
            {
              "operationId": "CreateComponentRegistration",
              "variant": "activity",
              "state": "conditional",
              "reach": [],
              "excludesSelf": false
            },
            {
              "operationId": "CreateComponentRegistration",
              "variant": "block",
              "state": "conditional",
              "reach": [],
              "excludesSelf": false
            },
            {
              "operationId": "GetCapabilities",
              "variant": null,
              "state": "available",
              "reach": [],
              "excludesSelf": false
            },
            {
              "operationId": "ListLtiOutcomes",
              "variant": null,
              "state": "conditional",
              "reach": [
                "tenant",
                "self"
              ],
              "excludesSelf": false
            },
            {
              "operationId": "MintLtiLaunch",
              "variant": null,
              "state": "conditional",
              "reach": [
                "tenant",
                "self"
              ],
              "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": "MintLtiLaunch",
          "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"
      },
      "ComponentBundleDto": {
        "description": "One uploaded version of a component\u0027s code, which cannot be changed once\nuploaded. \u0060componentId\u0060 is the registration it belongs to, \u0060contentHash\u0060\nidentifies its unpacked contents, and \u0060sizeBytes\u0060 is the size of the\nuploaded zip file.",
        "example": {
          "id": "019cb885-c360-75c7-b2e1-b595a665fce8",
          "componentId": "019cadd4-5620-718c-87a3-27c01cd74192",
          "contentHash": "9f2c4e71b0d35a86e1f47c2b9d06a58e3c7b12f40e9d68a5b3f1c07e24d9a6b8",
          "sizeBytes": 184320,
          "createdAt": "2026-03-04T11:05:00\u002B00:00",
          "_links": {
            "self": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192/bundles/019cb885-c360-75c7-b2e1-b595a665fce8",
            "list": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192/bundles",
            "component": "/v1/components/019cadd4-5620-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"
          },
          "componentId": {
            "format": "uuid",
            "type": "string"
          },
          "contentHash": {
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "sizeBytes": {
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "componentId",
          "contentHash",
          "sizeBytes",
          "createdAt",
          "_links"
        ],
        "type": "object"
      },
      "ComponentEditorResolutionDto": {
        "description": "Which editor an authoring client should load to author the \u0060definition\u0060 of\nan activity or block that uses \u0060componentUri\u0060. \u0060resolved\u0060 is true only\nwhen the tenant has registered the URI and activated an editor bundle for it; an\nunregistered URI, or one with no editor, is still a 200 with \u0060resolved\u0060 false\nand the other fields \u0060null\u0060, so the client can fall back to its own editor.\nA disabled registration still resolves, so content authored against it stays\neditable. \u0060editorUrl\u0060 carries an access token that expires at\n\u0060editorUrlExpiresAt\u0060.",
        "example": {
          "componentUri": "https://components.example.com/card-sort",
          "resolved": true,
          "editorUrl": "https://bundles.example.com/card-sort/editor.js?signature=example-signature",
          "contentHash": "4b8e1d06f37a92c5e0b6d4a17f39c28e5a01d7b6c94e3f28a1d5b07c6e2f94a3",
          "editorUrlExpiresAt": "2026-03-16T08:55:00\u002B00:00"
        },
        "properties": {
          "componentUri": {
            "type": "string"
          },
          "contentHash": {
            "type": [
              "null",
              "string"
            ]
          },
          "editorUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "editorUrlExpiresAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "resolved": {
            "type": "boolean"
          }
        },
        "required": [
          "componentUri",
          "resolved",
          "editorUrl",
          "contentHash",
          "editorUrlExpiresAt"
        ],
        "type": "object"
      },
      "ComponentRegistrationDto": {
        "description": "A component registered by the tenant, keyed by its \u0060componentUri\u0060. \u0060kind\u0060\nis \u0060activity\u0060 for an activity component or \u0060block\u0060 for a presentation\nblock type; \u0060allowedSubjects\u0060 names where a block type may be placed and is\n\u0060null\u0060 for an activity. \u0060runtimeBundleId\u0060 is the uploaded bundle that\nlearners are served, and \u0060null\u0060 means nothing is published;\n\u0060editorBundleId\u0060 is the bundle used to author the component\u0027s content. Both\npins change only through the bundle actions such as \u0060:activate-bundle\u0060, and\n\u0060status\u0060 (\u0060enabled\u0060 or \u0060disabled\u0060) only through \u0060:enable\u0060 and\n\u0060:disable\u0060.",
        "example": {
          "id": "019cadd4-5620-718c-87a3-27c01cd74192",
          "componentUri": "https://components.example.com/card-sort",
          "kind": "activity",
          "title": "Card sort",
          "description": "Learners drag each card into the group it belongs to, then check their answers.",
          "status": "enabled",
          "runtimeBundleId": "019cb885-c360-75c7-b2e1-b595a665fce8",
          "editorBundleId": "019cb885-c360-7e0a-bc4a-1e0523cf5816",
          "allowedSubjects": null,
          "createdAt": "2026-03-02T09:15:00\u002B00:00",
          "updatedAt": "2026-03-09T14:40:00\u002B00:00",
          "_links": {
            "self": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192",
            "list": "/v1/components",
            "bundles": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192/bundles",
            "update": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192",
            "disable": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:disable",
            "uploadBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192/bundles",
            "activateEditorBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:activate-editor-bundle",
            "activateBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:activate-bundle",
            "deactivateBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:deactivate-bundle",
            "deactivateEditorBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:deactivate-editor-bundle"
          }
        },
        "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"
          },
          "allowedSubjects": {
            "items": {
              "type": "string"
            },
            "type": [
              "null",
              "array"
            ]
          },
          "componentUri": {
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "editorBundleId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "runtimeBundleId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "componentUri",
          "kind",
          "title",
          "description",
          "status",
          "runtimeBundleId",
          "editorBundleId",
          "allowedSubjects",
          "createdAt",
          "updatedAt",
          "_links"
        ],
        "type": "object"
      },
      "ComponentRegistrationMergePatch": {
        "description": "The body of \u0060PATCH /v1/components/{id}\u0060, a JSON Merge Patch (RFC 7396): a\nfield left out is unchanged, a field sent is set, and \u0060null\u0060 clears\n\u0060description\u0060. \u0060title\u0060 cannot be cleared. \u0060componentUri\u0060 and\n\u0060kind\u0060 cannot be changed and are ignored if sent; \u0060status\u0060 is changed\nonly with \u0060:enable\u0060 and \u0060:disable\u0060, and sending it here is rejected.\n\u0060allowedSubjects\u0060 applies to block types only: a list sent for an activity\nis refused with 409 (a \u0060null\u0060 is accepted and changes nothing), and\n\u0060null\u0060 on a block type is refused with 422.",
        "example": {
          "title": "Card sort",
          "description": "Learners drag each card into the group it belongs to, then check their answers."
        },
        "properties": {
          "allowedSubjects": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "ComponentResolutionDto": {
        "description": "What a learner\u0027s client should run for one \u0060componentUri\u0060. An unregistered\nURI, or one registered as a block type, is still a 200, with \u0060registered\u0060\nfalse, so the client can fall back to its own handling. A disabled registration\nreports its \u0060status\u0060 but no URL. \u0060bundleUrl\u0060 is set only for an enabled\nactivity with a published bundle; it carries an access token that expires at\n\u0060bundleUrlExpiresAt\u0060, and \u0060contentHash\u0060 identifies the bundle\u0027s contents.\nBoth are \u0060null\u0060 whenever \u0060bundleUrl\u0060 is.",
        "example": {
          "componentUri": "https://components.example.com/card-sort",
          "registered": true,
          "status": "enabled",
          "bundleUrl": "https://bundles.example.com/card-sort/runtime.js?signature=example-signature",
          "contentHash": "9f2c4e71b0d35a86e1f47c2b9d06a58e3c7b12f40e9d68a5b3f1c07e24d9a6b8",
          "bundleUrlExpiresAt": "2026-03-16T08:55:00\u002B00:00"
        },
        "properties": {
          "bundleUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "bundleUrlExpiresAt": {
            "format": "date-time",
            "type": [
              "null",
              "string"
            ]
          },
          "componentUri": {
            "type": "string"
          },
          "contentHash": {
            "type": [
              "null",
              "string"
            ]
          },
          "registered": {
            "type": "boolean"
          },
          "status": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "componentUri",
          "registered",
          "status",
          "bundleUrl",
          "contentHash",
          "bundleUrlExpiresAt"
        ],
        "type": "object"
      },
      "CreateComponentRegistrationCommand": {
        "description": "The body of \u0060POST /v1/components\u0060, which registers an activity component\n(\u0060kind\u0060\u0060activity\u0060, the default) or a presentation block type\n(\u0060kind\u0060\u0060block\u0060). A new registration is \u0060enabled\u0060 and has no\nbundle published, so nothing runs until a bundle is uploaded and activated with\n\u0060:activate-bundle\u0060. A block type\u0027s \u0060componentUri\u0060 must be an absolute\n\u0060http\u0060 or \u0060https\u0060 URI outside \u0060https://serenapp.io/blocks/\u0060, which\nis reserved for built-in block types, and its \u0060allowedSubjects\u0060 (\u0060lesson\u0060,\n\u0060module\u0060) is required; an activity must not send \u0060allowedSubjects\u0060.\nRegistering an activity needs the custom activities feature on the tenant\u0027s plan.\n\u0060componentUri\u0060 must be unique in the tenant, and cannot be changed later.",
        "example": {
          "componentUri": "https://blocks.example.com/callout",
          "title": "Callout",
          "description": "A highlighted note that draws a learner\u0027s eye to one key point.",
          "kind": "block",
          "allowedSubjects": [
            "lesson",
            "module"
          ]
        },
        "properties": {
          "allowedSubjects": {
            "items": {
              "type": "string"
            },
            "type": [
              "null",
              "array"
            ]
          },
          "componentUri": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "kind": {
            "default": "activity",
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "componentUri",
          "title"
        ],
        "type": "object"
      },
      "CreateEmbedRegistrationCommand": {
        "description": "The body of \u0060POST /v1/embeds\u0060, which creates an embed: a public id that\nthird-party pages use to show one piece of content. \u0060elementKind\u0060\n(\u0060activity\u0060, \u0060quiz\u0060, \u0060lesson\u0060, \u0060module\u0060 or \u0060course\u0060)\ndecides which content ids must be given: an activity or quiz names\n\u0060activityVersionId\u0060, and optionally \u0060courseVersionId\u0060,\n\u0060lessonPlacementId\u0060 and \u0060activityPlacementId\u0060 together to show it in a\ncourse; a lesson names \u0060courseVersionId\u0060 and \u0060lessonPlacementId\u0060; a\nmodule names \u0060courseVersionId\u0060 and \u0060moduleId\u0060; a course names\n\u0060courseVersionId\u0060 alone. Any other combination is refused with 422, and\nneither the kind nor these ids can be changed later. \u0060allowedOrigins\u0060 lists\nthe exact \u0060https\u0060 origins that may show the embed (at least one, at most 20).\nThe server assigns the id and the public \u0060embedId\u0060. The tenant\u0027s plan must\ninclude external embeds.",
        "example": {
          "elementKind": "course",
          "allowedOrigins": [
            "https://learning.example.com"
          ],
          "activityVersionId": null,
          "courseVersionId": "019bb1c1-6440-709d-91ff-a96fc4c62cce",
          "moduleId": null,
          "lessonPlacementId": null,
          "activityPlacementId": null,
          "completionSignalEnabled": true,
          "locale": "en-GB",
          "theme": {
            "--accent": "#1f6feb",
            "--fg": "#1b1f24"
          },
          "authClientId": null
        },
        "properties": {
          "activityPlacementId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "activityVersionId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "allowedOrigins": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "authClientId": {
            "type": [
              "null",
              "string"
            ]
          },
          "completionSignalEnabled": {
            "default": false,
            "type": "boolean"
          },
          "courseVersionId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "elementKind": {
            "type": "string"
          },
          "lessonPlacementId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "locale": {
            "type": [
              "null",
              "string"
            ]
          },
          "moduleId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "theme": {
            "additionalProperties": {
              "type": "string"
            },
            "type": [
              "null",
              "object"
            ]
          }
        },
        "required": [
          "elementKind",
          "allowedOrigins"
        ],
        "type": "object"
      },
      "CreateLtiRegistrationCommand": {
        "description": "The body of \u0060POST /v1/lti/registrations\u0060, which registers an external LTI\ntool. \u0060ltiId\u0060 must be unique in the tenant (a duplicate is refused with\n409) and may contain only ASCII letters, digits and \u0060. _ ~ : -\u0060.\n\u0060launchUrl\u0060 must be an absolute \u0060http\u0060 or \u0060https\u0060 URL.\n\u0060ltiVersion\u0060 is \u00601.1\u0060 or \u00601.3\u0060, and defaults to \u00601.1\u0060.\n\u0060sharedSecret\u0060 is stored encrypted and never returned.",
        "example": {
          "ltiId": "virtual-chemistry-lab",
          "name": "Virtual chemistry lab",
          "launchUrl": "https://tools.example.com/lti/launch",
          "consumerKey": "example-consumer-key",
          "sharedSecret": "example-shared-secret",
          "ltiVersion": "1.1"
        },
        "properties": {
          "consumerKey": {
            "type": "string"
          },
          "launchUrl": {
            "type": "string"
          },
          "ltiId": {
            "type": "string"
          },
          "ltiVersion": {
            "type": [
              "null",
              "string"
            ]
          },
          "name": {
            "type": "string"
          },
          "sharedSecret": {
            "type": "string"
          }
        },
        "required": [
          "ltiId",
          "name",
          "launchUrl",
          "consumerKey",
          "sharedSecret"
        ],
        "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": "MDE5Y2FkYzYtOWE4MC03ZGFmLWE3MDYtZGQyZjRkMTk3ZTZm",
          "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"
      },
      "CursorPageOfComponentBundleDto": {
        "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": "019cb885-c360-75c7-b2e1-b595a665fce8",
              "componentId": "019cadd4-5620-718c-87a3-27c01cd74192",
              "contentHash": "9f2c4e71b0d35a86e1f47c2b9d06a58e3c7b12f40e9d68a5b3f1c07e24d9a6b8",
              "sizeBytes": 184320,
              "createdAt": "2026-03-04T11:05:00\u002B00:00",
              "_links": {
                "self": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192/bundles/019cb885-c360-75c7-b2e1-b595a665fce8",
                "list": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192/bundles",
                "component": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "MDE5Y2I4ODUtYzM2MC03NWM3LWIyZTEtYjU5NWE2NjVmY2U4",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192/bundles",
            "component": "/v1/components/019cadd4-5620-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"
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/ComponentBundleDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "CursorPageOfMediaFileDto": {
        "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": "019cb885-c360-74cb-9fdb-f611f3ad6dc8",
              "kind": "image",
              "filename": "lab-safety-poster.png",
              "visibility": "tenant",
              "contentType": "image/png",
              "sizeBytes": 482133,
              "status": "ready",
              "url": "https://media.example.com/files/lab-safety-poster.png",
              "createdAt": "2026-03-04T11:05:00\u002B00:00",
              "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
              "updatedAt": "2026-03-04T11:05:00\u002B00:00",
              "_links": {
                "self": "/v1/media/019cb885-c360-74cb-9fdb-f611f3ad6dc8",
                "list": "/v1/media",
                "update": "/v1/media/019cb885-c360-74cb-9fdb-f611f3ad6dc8",
                "delete": "/v1/media/019cb885-c360-74cb-9fdb-f611f3ad6dc8"
              }
            }
          ],
          "pagination": {
            "limit": 25,
            "hasMore": true,
            "nextCursor": "MDE5Y2I4ODUtYzM2MC03NGNiLTlmZGItZjYxMWYzYWQ2ZGM4",
            "totalCount": null
          },
          "_links": {
            "self": "/v1/media",
            "create": "/v1/media"
          }
        },
        "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/MediaFileDto"
            },
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/CursorPageInfo"
          }
        },
        "required": [
          "data",
          "pagination",
          "_links"
        ],
        "type": "object"
      },
      "EmbedManifestDto": {
        "description": "The public description of an embed that its page script reads before anyone\nhas signed in: which content it shows, the origins that may show it, and its\ndisplay settings. It holds no learner data, content, token or secret, and the\n\u0060embedId\u0060 is not a credential; every later read and write still needs the\nlearner\u0027s own token. \u0060tenantSlug\u0060 names the tenant to sign the learner in\nto, and is \u0060null\u0060 when the tenant has none. With no\n\u0060courseVersionId\u0060, \u0060lessonPlacementId\u0060 and \u0060activityPlacementId\u0060,\nan activity or quiz is shown on its own rather than as part of a course.\n\u0060completionSignalEnabled\u0060 says whether the embed may tell the host page when\na learner finishes.",
        "example": {
          "embedId": "emb_0199044a44007cc19e41ab087439611e5d0c8a3e91f24b67a8e3c1d09f6b2e74",
          "elementKind": "course",
          "tenantSlug": "example-academy",
          "activityVersionId": null,
          "courseVersionId": "019bb1c1-6440-709d-91ff-a96fc4c62cce",
          "moduleId": null,
          "lessonPlacementId": null,
          "activityPlacementId": null,
          "allowedOrigins": [
            "https://learning.example.com"
          ],
          "completionSignalEnabled": true,
          "locale": "en-GB",
          "theme": {
            "--accent": "#1f6feb",
            "--fg": "#1b1f24"
          },
          "authClientId": null
        },
        "properties": {
          "activityPlacementId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "activityVersionId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "allowedOrigins": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "authClientId": {
            "type": [
              "null",
              "string"
            ]
          },
          "completionSignalEnabled": {
            "type": "boolean"
          },
          "courseVersionId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "elementKind": {
            "type": "string"
          },
          "embedId": {
            "type": "string"
          },
          "lessonPlacementId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "locale": {
            "type": [
              "null",
              "string"
            ]
          },
          "moduleId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "tenantSlug": {
            "type": [
              "null",
              "string"
            ]
          },
          "theme": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          }
        },
        "required": [
          "embedId",
          "elementKind",
          "tenantSlug",
          "activityVersionId",
          "courseVersionId",
          "moduleId",
          "lessonPlacementId",
          "activityPlacementId",
          "allowedOrigins",
          "completionSignalEnabled",
          "locale",
          "theme",
          "authClientId"
        ],
        "type": "object"
      },
      "EmbedRegistrationDto": {
        "description": "An embed: a public id that lets third-party pages show one piece of content.\n\u0060id\u0060 addresses the \u0060/v1/embeds/{id}\u0060 management routes; \u0060embedId\u0060\nis the public value, beginning \u0060emb_\u0060, to paste into a page\u0027s markup.\n\u0060elementKind\u0060 and the content ids fix what is shown and never change;\n\u0060allowedOrigins\u0060 lists the origins that may show it.\n\u0060completionSignalEnabled\u0060 says whether the embed tells the host page when a\nlearner finishes. \u0060status\u0060 (\u0060enabled\u0060 or \u0060disabled\u0060) changes only\nthrough \u0060:enable\u0060 and \u0060:disable\u0060; a disabled embed stops showing.",
        "example": {
          "id": "019cadd4-5620-7dce-a467-3ce49765c7f3",
          "embedId": "emb_0199044a44007cc19e41ab087439611e5d0c8a3e91f24b67a8e3c1d09f6b2e74",
          "elementKind": "course",
          "activityVersionId": null,
          "courseVersionId": "019bb1c1-6440-709d-91ff-a96fc4c62cce",
          "moduleId": null,
          "lessonPlacementId": null,
          "activityPlacementId": null,
          "allowedOrigins": [
            "https://learning.example.com"
          ],
          "status": "enabled",
          "completionSignalEnabled": true,
          "locale": "en-GB",
          "theme": {
            "--accent": "#1f6feb",
            "--fg": "#1b1f24"
          },
          "authClientId": null,
          "createdAt": "2026-03-02T09:15:00\u002B00:00",
          "updatedAt": "2026-03-09T14:40:00\u002B00:00",
          "_links": {
            "self": "/v1/embeds/019cadd4-5620-7dce-a467-3ce49765c7f3",
            "list": "/v1/embeds",
            "update": "/v1/embeds/019cadd4-5620-7dce-a467-3ce49765c7f3",
            "disable": "/v1/embeds/019cadd4-5620-7dce-a467-3ce49765c7f3:disable",
            "delete": "/v1/embeds/019cadd4-5620-7dce-a467-3ce49765c7f3",
            "manifest": "/api/embeds/emb_0199044a44007cc19e41ab087439611e5d0c8a3e91f24b67a8e3c1d09f6b2e74/manifest"
          }
        },
        "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"
          },
          "activityPlacementId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "activityVersionId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "allowedOrigins": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "authClientId": {
            "type": [
              "null",
              "string"
            ]
          },
          "completionSignalEnabled": {
            "type": "boolean"
          },
          "courseVersionId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "elementKind": {
            "type": "string"
          },
          "embedId": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lessonPlacementId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "locale": {
            "type": [
              "null",
              "string"
            ]
          },
          "moduleId": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "type": "string"
          },
          "theme": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "embedId",
          "elementKind",
          "activityVersionId",
          "courseVersionId",
          "moduleId",
          "lessonPlacementId",
          "activityPlacementId",
          "allowedOrigins",
          "status",
          "completionSignalEnabled",
          "locale",
          "theme",
          "authClientId",
          "createdAt",
          "updatedAt",
          "_links"
        ],
        "type": "object"
      },
      "EmbedRegistrationMergePatch": {
        "description": "The body of \u0060PATCH /v1/embeds/{id}\u0060, a JSON Merge Patch (RFC 7396): a field\nleft out is unchanged, a field sent is set, and \u0060null\u0060 clears\n\u0060locale\u0060 or \u0060authClientId\u0060. \u0060allowedOrigins\u0060 and \u0060theme\u0060 are\neach replaced whole, never merged key by key. \u0060completionSignalEnabled\u0060\ncannot be \u0060null\u0060. \u0060elementKind\u0060 and the content ids cannot be changed\nand are ignored if sent; create a new embed instead. \u0060status\u0060 is changed\nonly with \u0060:enable\u0060 and \u0060:disable\u0060, and sending it here is rejected.",
        "example": {
          "allowedOrigins": [
            "https://learning.example.com",
            "https://intranet.example.org"
          ]
        },
        "properties": {
          "allowedOrigins": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "authClientId": {
            "type": [
              "null",
              "string"
            ]
          },
          "completionSignalEnabled": {
            "type": "boolean"
          },
          "locale": {
            "type": [
              "null",
              "string"
            ]
          },
          "theme": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "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"
      },
      "LinkedCollectionOfActivityTypeDto": {
        "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": [
            {
              "componentUri": "https://components.example.com/card-sort",
              "title": "Card sort",
              "status": "enabled"
            }
          ],
          "_links": {
            "self": "/v1/components:activity-types"
          }
        },
        "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/ActivityTypeDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfBlockTypeDto": {
        "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": [
            {
              "componentUri": "https://blocks.example.com/callout",
              "title": "Callout",
              "allowedSubjects": [
                "lesson",
                "module"
              ],
              "status": "enabled"
            }
          ],
          "_links": {
            "self": "/v1/components:block-types"
          }
        },
        "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/BlockTypeDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfComponentRegistrationDto": {
        "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": "019cadd4-5620-718c-87a3-27c01cd74192",
              "componentUri": "https://components.example.com/card-sort",
              "kind": "activity",
              "title": "Card sort",
              "description": "Learners drag each card into the group it belongs to, then check their answers.",
              "status": "enabled",
              "runtimeBundleId": "019cb885-c360-75c7-b2e1-b595a665fce8",
              "editorBundleId": "019cb885-c360-7e0a-bc4a-1e0523cf5816",
              "allowedSubjects": null,
              "createdAt": "2026-03-02T09:15:00\u002B00:00",
              "updatedAt": "2026-03-09T14:40:00\u002B00:00",
              "_links": {
                "self": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192",
                "list": "/v1/components",
                "bundles": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192/bundles",
                "update": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192",
                "disable": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:disable",
                "uploadBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192/bundles",
                "activateEditorBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:activate-editor-bundle",
                "activateBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:activate-bundle",
                "deactivateBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:deactivate-bundle",
                "deactivateEditorBundle": "/v1/components/019cadd4-5620-718c-87a3-27c01cd74192:deactivate-editor-bundle"
              }
            }
          ],
          "_links": {
            "self": "/v1/components",
            "create": "/v1/components"
          }
        },
        "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/ComponentRegistrationDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfEmbedRegistrationDto": {
        "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": "019cadd4-5620-7dce-a467-3ce49765c7f3",
              "embedId": "emb_0199044a44007cc19e41ab087439611e5d0c8a3e91f24b67a8e3c1d09f6b2e74",
              "elementKind": "course",
              "activityVersionId": null,
              "courseVersionId": "019bb1c1-6440-709d-91ff-a96fc4c62cce",
              "moduleId": null,
              "lessonPlacementId": null,
              "activityPlacementId": null,
              "allowedOrigins": [
                "https://learning.example.com"
              ],
              "status": "enabled",
              "completionSignalEnabled": true,
              "locale": "en-GB",
              "theme": {
                "--accent": "#1f6feb",
                "--fg": "#1b1f24"
              },
              "authClientId": null,
              "createdAt": "2026-03-02T09:15:00\u002B00:00",
              "updatedAt": "2026-03-09T14:40:00\u002B00:00",
              "_links": {
                "self": "/v1/embeds/019cadd4-5620-7dce-a467-3ce49765c7f3",
                "list": "/v1/embeds",
                "update": "/v1/embeds/019cadd4-5620-7dce-a467-3ce49765c7f3",
                "disable": "/v1/embeds/019cadd4-5620-7dce-a467-3ce49765c7f3:disable",
                "delete": "/v1/embeds/019cadd4-5620-7dce-a467-3ce49765c7f3",
                "manifest": "/api/embeds/emb_0199044a44007cc19e41ab087439611e5d0c8a3e91f24b67a8e3c1d09f6b2e74/manifest"
              }
            }
          ],
          "_links": {
            "self": "/v1/embeds",
            "create": "/v1/embeds"
          }
        },
        "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/EmbedRegistrationDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfLtiOutcomeDto": {
        "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": [
            {
              "courseId": "019bb1c1-6440-7fca-97e7-57f16cfb68cf",
              "ltiId": "virtual-chemistry-lab",
              "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
              "score": 0.85,
              "completed": true,
              "recordedToPlatform": true,
              "receivedAt": "2026-03-16T08:45:00\u002B00:00"
            }
          ],
          "_links": {
            "self": "/v1/lti/outcomes?courseId=019bb1c1-6440-7fca-97e7-57f16cfb68cf\u0026ltiId=virtual-chemistry-lab\u0026userId=0199b9ad-d000-7fed-bccd-fd9c96a18897",
            "launch": "/v1/lti:launch"
          }
        },
        "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/LtiOutcomeDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LinkedCollectionOfLtiRegistrationDto": {
        "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": "019cadd4-5620-7112-8ac9-8e55dbe33168",
              "ltiVersion": "1.1",
              "ltiId": "virtual-chemistry-lab",
              "name": "Virtual chemistry lab",
              "launchUrl": "https://tools.example.com/lti/launch",
              "consumerKey": "example-consumer-key",
              "createdAt": "2026-03-02T09:15:00\u002B00:00",
              "updatedAt": "2026-03-09T14:40:00\u002B00:00",
              "_links": {
                "self": "/v1/lti/registrations/019cadd4-5620-7112-8ac9-8e55dbe33168",
                "list": "/v1/lti/registrations",
                "update": "/v1/lti/registrations/019cadd4-5620-7112-8ac9-8e55dbe33168",
                "delete": "/v1/lti/registrations/019cadd4-5620-7112-8ac9-8e55dbe33168"
              }
            }
          ],
          "_links": {
            "self": "/v1/lti/registrations",
            "create": "/v1/lti/registrations"
          }
        },
        "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/LtiRegistrationDto"
            },
            "type": "array"
          }
        },
        "required": [
          "data",
          "_links"
        ],
        "type": "object"
      },
      "LtiLaunchDto": {
        "description": "A launch into an LTI tool. Load \u0060launchUrl\u0060 in the learner\u0027s browser, as\nit is, before \u0060expiresAt\u0060, about 60 seconds after it was issued; it takes\nthe learner into the tool. The URL is opaque: never build or change one. The\nexpiry applies only to starting the launch, not to the learner\u0027s session in the\ntool.",
        "example": {
          "launchUrl": "https://lms.example.com/api/lti/launch?ticket=example-launch-ticket",
          "expiresAt": "2026-03-16T08:55:00\u002B00:00"
        },
        "properties": {
          "expiresAt": {
            "format": "date-time",
            "type": "string"
          },
          "launchUrl": {
            "type": "string"
          }
        },
        "required": [
          "launchUrl",
          "expiresAt"
        ],
        "type": "object"
      },
      "LtiOutcomeDto": {
        "description": "The latest grade an LTI tool has sent back for one learner (\u0060userId\u0060) in one\ncourse version (\u0060courseId\u0060) and tool (\u0060ltiId\u0060). \u0060score\u0060 is between\n0 and 1, and \u0060completed\u0060 is true when it is 1. Treat this as a display hint:\nthe learner\u0027s completion itself is recorded as their progress, and\n\u0060recordedToPlatform\u0060 says whether that happened for this grade. The same\ntool used twice in one course version shares one grade, and a new course version\nstarts with none.",
        "example": {
          "courseId": "019bb1c1-6440-7fca-97e7-57f16cfb68cf",
          "ltiId": "virtual-chemistry-lab",
          "userId": "0199b9ad-d000-7fed-bccd-fd9c96a18897",
          "score": 0.85,
          "completed": true,
          "recordedToPlatform": true,
          "receivedAt": "2026-03-16T08:45:00\u002B00:00"
        },
        "properties": {
          "completed": {
            "type": "boolean"
          },
          "courseId": {
            "format": "uuid",
            "type": "string"
          },
          "ltiId": {
            "type": "string"
          },
          "receivedAt": {
            "format": "date-time",
            "type": "string"
          },
          "recordedToPlatform": {
            "type": "boolean"
          },
          "score": {
            "format": "double",
            "type": "number"
          },
          "userId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "courseId",
          "ltiId",
          "userId",
          "score",
          "completed",
          "recordedToPlatform",
          "receivedAt"
        ],
        "type": "object"
      },
      "LtiRegistrationDto": {
        "description": "An external LTI tool registered with the tenant, identified within the tenant by\nits \u0060ltiId\u0060. \u0060launchUrl\u0060 is where learners are sent, and\n\u0060consumerKey\u0060 is the OAuth key the tool expects. The shared secret is\nwrite-only: it can be set on create and changed with \u0060PATCH\u0060, but is never\nreturned.",
        "example": {
          "id": "019cadd4-5620-7112-8ac9-8e55dbe33168",
          "ltiVersion": "1.1",
          "ltiId": "virtual-chemistry-lab",
          "name": "Virtual chemistry lab",
          "launchUrl": "https://tools.example.com/lti/launch",
          "consumerKey": "example-consumer-key",
          "createdAt": "2026-03-02T09:15:00\u002B00:00",
          "updatedAt": "2026-03-09T14:40:00\u002B00:00",
          "_links": {
            "self": "/v1/lti/registrations/019cadd4-5620-7112-8ac9-8e55dbe33168",
            "list": "/v1/lti/registrations",
            "update": "/v1/lti/registrations/019cadd4-5620-7112-8ac9-8e55dbe33168",
            "delete": "/v1/lti/registrations/019cadd4-5620-7112-8ac9-8e55dbe33168"
          }
        },
        "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"
          },
          "consumerKey": {
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "launchUrl": {
            "type": "string"
          },
          "ltiId": {
            "type": "string"
          },
          "ltiVersion": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "ltiVersion",
          "ltiId",
          "name",
          "launchUrl",
          "consumerKey",
          "createdAt",
          "updatedAt",
          "_links"
        ],
        "type": "object"
      },
      "LtiRegistrationMergePatch": {
        "description": "The body of \u0060PATCH /v1/lti/registrations/{id}\u0060, a JSON Merge Patch\n(RFC 7396): a field left out is unchanged and a field sent is set. Every field\nis required, so \u0060null\u0060 is refused with 422. Send \u0060sharedSecret\u0060 only to\nchange the secret. \u0060ltiId\u0060 cannot be changed and is ignored if sent;\nregister a new tool instead.",
        "example": {
          "launchUrl": "https://tools.example.com/lti/v2/launch"
        },
        "properties": {
          "consumerKey": {
            "type": "string"
          },
          "launchUrl": {
            "type": "string"
          },
          "ltiVersion": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "sharedSecret": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "MediaFileDto": {
        "description": "A media file hosted for the tenant, such as an image, PDF, audio or video file.\n\u0060status\u0060 is one of four values. A file is \u0060pending\u0060 from creation until\nits upload is confirmed with \u0060:confirm\u0060, then \u0060ready\u0060. A video is\nprocessed by the video service after its upload, so it can also be\n\u0060processing\u0060 (uploaded, not yet playable) before \u0060ready\u0060, or end\n\u0060failed\u0060 (it could not be processed; delete it and upload again). Only a\nvideo is ever \u0060processing\u0060 or \u0060failed\u0060. \u0060url\u0060 is where the file is\nserved, and is \u0060null\u0060 until the file is ready; treat it as opaque.\n\u0060contentType\u0060 is \u0060null\u0060 until the file is ready, and \u0060sizeBytes\u0060 is\nthe declared size until then and the real size after. \u0060visibility\u0060 is \u0060tenant\u0060 (members of\nthe tenant only) or \u0060public\u0060 (anyone with the URL). \u0060createdBy\u0060 is the\nid of whoever created the file, when known.",
        "example": {
          "id": "019cb885-c360-74cb-9fdb-f611f3ad6dc8",
          "kind": "image",
          "filename": "lab-safety-poster.png",
          "visibility": "tenant",
          "contentType": "image/png",
          "sizeBytes": 482133,
          "status": "ready",
          "url": "https://media.example.com/files/lab-safety-poster.png",
          "createdAt": "2026-03-04T11:05:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedAt": "2026-03-04T11:05:00\u002B00:00",
          "_links": {
            "self": "/v1/media/019cb885-c360-74cb-9fdb-f611f3ad6dc8",
            "list": "/v1/media",
            "update": "/v1/media/019cb885-c360-74cb-9fdb-f611f3ad6dc8",
            "delete": "/v1/media/019cb885-c360-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"
          },
          "contentType": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "filename": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "sizeBytes": {
            "format": "int64",
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "url": {
            "type": [
              "null",
              "string"
            ]
          },
          "visibility": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "kind",
          "filename",
          "visibility",
          "contentType",
          "sizeBytes",
          "status",
          "url",
          "createdAt",
          "createdBy",
          "updatedAt",
          "_links"
        ],
        "type": "object"
      },
      "MediaFileMergePatch": {
        "description": "The body of \u0060PATCH /v1/media/{id}\u0060, a JSON Merge Patch (RFC 7396).\n\u0060visibility\u0060 is the only field that can change: \u0060tenant\u0060 or\n\u0060public\u0060, and \u0060null\u0060 is refused with 422. Leaving it out changes\nnothing. \u0060kind\u0060 and \u0060filename\u0060 cannot be changed.",
        "example": {
          "visibility": "public"
        },
        "properties": {
          "visibility": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "MediaSessionDto": {
        "description": "A short-lived ticket for reading the tenant\u0027s media in a browser. Post\n\u0060ticket\u0060 to \u0060exchangeUrl\u0060, with credentials included, before\n\u0060expiresAt\u0060; the response sets a cookie that lets the browser load the\ntenant\u0027s \u0060tenant\u0060-visibility media files. Request a new ticket each time the\naccess token is refreshed.",
        "example": {
          "ticket": "example-media-session-ticket",
          "exchangeUrl": "https://media.example.com/session",
          "expiresAt": "2026-03-16T08:55:00\u002B00:00"
        },
        "properties": {
          "exchangeUrl": {
            "type": "string"
          },
          "expiresAt": {
            "format": "date-time",
            "type": "string"
          },
          "ticket": {
            "type": "string"
          }
        },
        "required": [
          "ticket",
          "exchangeUrl",
          "expiresAt"
        ],
        "type": "object"
      },
      "MediaSweepResultDto": {
        "description": "The result of one media sweep: \u0060swept\u0060 is how many abandoned pending files it\nremoved, \u0060reconciled\u0060 how many videos it moved to the state the video service\nreports, and \u0060hasMore\u0060 is true when the caller should call again.",
        "example": {
          "swept": 3,
          "hasMore": false,
          "reconciled": 1
        },
        "properties": {
          "hasMore": {
            "type": "boolean"
          },
          "reconciled": {
            "format": "int32",
            "type": "integer"
          },
          "swept": {
            "format": "int32",
            "type": "integer"
          }
        },
        "required": [
          "swept",
          "hasMore",
          "reconciled"
        ],
        "type": "object"
      },
      "MediaUploadTarget": {
        "description": "How to upload a media file\u0027s bytes, directly to storage rather than through this\nAPI, before \u0060expiresAt\u0060. The target has one of two shapes, told apart by\n\u0060formField\u0060. When \u0060formField\u0060 is absent \u2014 every kind but video \u2014 send the\nraw file with \u0060method\u0060 (\u0060PUT\u0060) to \u0060url\u0060, which is pre-signed, with\nexactly the \u0060headers\u0060 listed; \u0060headers\u0060 is keyed by the header name, such\nas \u0060Content-Type\u0060, so it can be passed straight to an HTTP client, and a\ndifferent value for a listed header makes the upload fail. When \u0060formField\u0060 is\npresent \u2014 a video \u2014 send one \u0060multipart/form-data\u0060 request with \u0060method\u0060\n(\u0060POST\u0060) to \u0060url\u0060, with the file in the field \u0060formField\u0060 names and no\nother headers; the URL admits one upload, and a video longer than\n\u0060maxDurationSeconds\u0060 is refused.",
        "example": {
          "method": "PUT",
          "url": "https://uploads.example.com/files/periodic-table.pdf?signature=example-signature",
          "headers": {
            "Content-Type": "application/pdf"
          },
          "expiresAt": "2026-03-16T08:55:00\u002B00:00",
          "formField": null,
          "maxDurationSeconds": null
        },
        "properties": {
          "expiresAt": {
            "format": "date-time",
            "type": "string"
          },
          "formField": {
            "type": [
              "null",
              "string"
            ]
          },
          "headers": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "maxDurationSeconds": {
            "format": "int32",
            "type": [
              "null",
              "integer"
            ]
          },
          "method": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "method",
          "url",
          "headers",
          "expiresAt"
        ],
        "type": "object"
      },
      "MintLtiLaunchCommand": {
        "description": "The body of \u0060POST /v1/lti:launch\u0060, which starts a learner\u0027s launch into the\nLTI tool \u0060ltiId\u0060 at placement \u0060placementId\u0060 in course version\n\u0060courseId\u0060. Leave \u0060userId\u0060 out to launch for the caller, or name a\nlearner (or \u0060me\u0060) when the caller may record progress for others. The\nlaunch is refused with 403 unless the course is reachable, the placement hosts\nthis tool and the learner is enrolled. \u0060returnUrl\u0060 is optional; when sent it\nmust be an absolute \u0060http\u0060 or \u0060https\u0060 URL on the allowed list, and the\ntool uses it to send the learner back.",
        "example": {
          "courseId": "019bb1c1-6440-7fca-97e7-57f16cfb68cf",
          "placementId": "019bb1c1-6440-7f03-b768-4def5c5a04ab",
          "ltiId": "virtual-chemistry-lab",
          "returnUrl": "https://learning.example.com/courses/chemistry",
          "userId": null
        },
        "properties": {
          "courseId": {
            "format": "uuid",
            "type": "string"
          },
          "ltiId": {
            "type": "string"
          },
          "placementId": {
            "format": "uuid",
            "type": "string"
          },
          "returnUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "userId": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "courseId",
          "placementId",
          "ltiId"
        ],
        "type": "object"
      },
      "MintMediaFileCommand": {
        "description": "The body of \u0060POST /v1/media\u0060, the first of three steps: create a\n\u0060pending\u0060 media file, upload its bytes to the returned \u0060upload\u0060\ntarget, then call \u0060POST /v1/media/{id}:confirm\u0060. \u0060kind\u0060 is\n\u0060image\u0060, \u0060pdf\u0060, \u0060audio\u0060, \u0060video\u0060 or \u0060document\u0060;\n\u0060filename\u0060 must end in an extension allowed for that kind, and\n\u0060sizeBytes\u0060 must be at least 1 and within that kind\u0027s size limit.\n\u0060contentType\u0060 is a media type such as \u0060image/png\u0060, and every upload but a\nvideo\u0027s must send the same value. \u0060visibility\u0060 is \u0060tenant\u0060 (the default) or\n\u0060public\u0060. The tenant\u0027s plan must include media hosting. A file that would\ntake the tenant over its storage allowance is refused here, and the real size is\nchecked again at confirm; a video is instead counted against the tenant\u0027s video\nminutes, and is refused here when none are left.",
        "example": {
          "kind": "pdf",
          "filename": "periodic-table.pdf",
          "contentType": "application/pdf",
          "sizeBytes": 1048576,
          "visibility": "tenant"
        },
        "properties": {
          "contentType": {
            "type": "string"
          },
          "filename": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "sizeBytes": {
            "format": "int64",
            "type": "integer"
          },
          "visibility": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "required": [
          "kind",
          "filename",
          "contentType",
          "sizeBytes"
        ],
        "type": "object"
      },
      "MintedMediaFileDto": {
        "description": "The response to \u0060POST /v1/media\u0060: the new \u0060pending\u0060 media file, plus\n\u0060upload\u0060, which says where to send the file\u0027s bytes. The upload details are\nreturned only here, never by a later read. A retry with the same\n\u0060Idempotency-Key\u0060 returns this response unchanged, even once the upload has\nexpired; to upload after that, create the file again with a new key.",
        "example": {
          "id": "019cf5d1-e6e0-7068-9012-eb773f7b168b",
          "kind": "pdf",
          "filename": "periodic-table.pdf",
          "visibility": "tenant",
          "contentType": null,
          "sizeBytes": 1048576,
          "status": "pending",
          "url": null,
          "createdAt": "2026-03-16T08:45:00\u002B00:00",
          "createdBy": "0199044e-d7e0-76ce-a468-4c4f0fd784b4",
          "updatedAt": "2026-03-16T08:45:00\u002B00:00",
          "upload": {
            "method": "PUT",
            "url": "https://uploads.example.com/files/periodic-table.pdf?signature=example-signature",
            "headers": {
              "Content-Type": "application/pdf"
            },
            "expiresAt": "2026-03-16T08:55:00\u002B00:00",
            "formField": null,
            "maxDurationSeconds": null
          },
          "_links": {
            "self": "/v1/media/019cf5d1-e6e0-7068-9012-eb773f7b168b",
            "list": "/v1/media",
            "update": "/v1/media/019cf5d1-e6e0-7068-9012-eb773f7b168b",
            "delete": "/v1/media/019cf5d1-e6e0-7068-9012-eb773f7b168b",
            "confirm": "/v1/media/019cf5d1-e6e0-7068-9012-eb773f7b168b:confirm"
          }
        },
        "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"
          },
          "contentType": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "createdBy": {
            "format": "uuid",
            "type": [
              "null",
              "string"
            ]
          },
          "filename": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "sizeBytes": {
            "format": "int64",
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "updatedAt": {
            "format": "date-time",
            "type": "string"
          },
          "upload": {
            "$ref": "#/components/schemas/MediaUploadTarget"
          },
          "url": {
            "type": [
              "null",
              "string"
            ]
          },
          "visibility": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "kind",
          "filename",
          "visibility",
          "contentType",
          "sizeBytes",
          "status",
          "url",
          "createdAt",
          "createdBy",
          "updatedAt",
          "upload",
          "_links"
        ],
        "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"
      },
      "SignMediaUrlsCommand": {
        "description": "The body of \u0060POST /v1/media:sign\u0060: up to 200 of the tenant\u0027s media file\nURLs to sign, for a viewer that cannot use the media cookie. Each URL must be a\nmedia URL of the caller\u0027s own tenant; any that is not is named in a 422.",
        "example": {
          "urls": [
            "https://media.example.com/files/lab-safety-poster.png"
          ]
        },
        "properties": {
          "urls": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "urls"
        ],
        "type": "object"
      },
      "SignedMediaUrlsDto": {
        "description": "The signed media URLs, in the order they were sent, each carrying an access\ntoken for that one file, and \u0060expiresAt\u0060, when every token stops working\n(at most about ten minutes). Do not send an \u0060Idempotency-Key\u0060 when signing:\na retried request would return the first, possibly expired, tokens.",
        "example": {
          "urls": [
            "https://media.example.com/files/lab-safety-poster.png?token=example-signature"
          ],
          "expiresAt": "2026-03-16T08:55:00\u002B00:00"
        },
        "properties": {
          "expiresAt": {
            "format": "date-time",
            "type": "string"
          },
          "urls": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "urls",
          "expiresAt"
        ],
        "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 LMS API serves the learning-management features built on top of the Seren Platform API: custom activity and block components, embeds of a course in another site, LTI 1.1 partner tools, and hosted media. It follows the Platform API\u0027s conventions, below.\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## Routes without a token\n\nTwo routes under \u0060/api\u0060 are called by parties that hold no token and send no tenant header: an embed\u0027s public manifest, where the embed id names the tenant, and the LTI 1.1 outcomes endpoint, which a partner tool calls with its own OAuth 1.0 signature. They are not counted against a tenant\u0027s rate limit and take no \u0060Idempotency-Key\u0060.",
    "title": "Seren LMS API",
    "version": "0.1.0"
  },
  "openapi": "3.1.1",
  "paths": {
    "/api/embeds/{embedId}/manifest": {
      "get": {
        "description": "Resolves a public embed id to its bootstrap manifest \u2014 unauthenticated, no tenant header (the id itself names the tenant). The manifest contains only the record\u0027s public pins, origin policy and presentation defaults: no learner data, content body, token or secret. Every failure mode is the same empty 404.",
        "operationId": "GetEmbedManifest",
        "parameters": [
          {
            "in": "path",
            "name": "embedId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbedManifestDto"
                }
              }
            },
            "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": {
            "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"
                }
              }
            }
          }
        },
        "security": [],
        "summary": "Get an embed\u0027s public manifest",
        "tags": [
          "Embeds"
        ]
      }
    },
    "/api/lti/outcomes": {
      "post": {
        "description": "The LTI 1.1 Basic Outcomes receiver, called by a registered tool rather than by an API client: it takes no bearer token and no tenant header. The tool signs the XML body with OAuth 1.0 (HMAC-SHA1, a body hash, a timestamp within 600 seconds and a nonce used once) using its registration\u0027s shared secret, and the \u0060sourcedId\u0060 it received at launch names the tenant and the tool. Only replaceResult is supported. Every outcome, success or failure, is an HTTP 200 carrying an XML receipt: branch on its \u0060imsx_codeMajor\u0060. A failure records no grade and is safe to retry. A full score from a launch that named a learner and a lesson placement also enrols the learner in the course and records their completion of that lesson placement. The one refusal outside the receipt is a body over 32 KB, a 413 lti.outcome_too_large.",
        "operationId": "LtiOutcomes",
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "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"
                }
              }
            }
          },
          "413": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Payload Too Large",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed 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": [],
        "summary": "Receive a grade from an LTI 1.1 tool",
        "tags": [
          "LTI"
        ]
      }
    },
    "/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/components": {
      "get": {
        "description": "Every registration in the tenant, newest first. \u0060kind\u0060 (activity or block) narrows the list to one kind; absent, both kinds are returned.",
        "operationId": "ListComponentRegistrations",
        "parameters": [
          {
            "in": "query",
            "name": "kind",
            "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/LinkedCollectionOfComponentRegistrationDto"
                }
              }
            },
            "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 components",
        "tags": [
          "Components"
        ]
      },
      "post": {
        "description": "Registers an activity component (the default kind) or, with kind=block, a presentation block type. An activity needs the custom_activities entitlement; a block type does not. A block type\u0027s componentUri must be an absolute http(s) URI outside https://serenapp.io/blocks/ (reserved for built-in block types), it must name allowedSubjects (lesson, module).",
        "operationId": "CreateComponentRegistration",
        "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/CreateComponentRegistrationCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComponentRegistrationDto"
                }
              }
            },
            "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"
                }
              }
            }
          },
          "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": "Register a component",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components/{id}": {
      "get": {
        "description": "Returns one component registration. Requires settings:read. A plan without the custom_activities entitlement never refuses this read; it withholds the links that would put code into service (edit and enable for an activity, and uploading or pinning a bundle for either kind), while disable and un-pinning a bundle stay offered.",
        "operationId": "GetComponentRegistrationById",
        "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/ComponentRegistrationDto"
                }
              }
            },
            "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 component",
        "tags": [
          "Components"
        ]
      },
      "patch": {
        "description": "Edits registration details with an RFC 7396 merge patch. Requires components:manage and the custom-activity entitlement for activity registrations. Block edits do not require that entitlement. Status is changed only through :enable or :disable; a status member is a malformed request (400).",
        "operationId": "UpdateComponentRegistration",
        "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/ComponentRegistrationMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/ComponentRegistrationMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComponentRegistrationDto"
                }
              }
            },
            "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"
                }
              }
            }
          },
          "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 component",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components/{id}/bundles": {
      "get": {
        "description": "Newest first, cursor-paginated (limit/after). 422 component.invalid_cursor: an after cursor that this component\u0027s list did not mint.",
        "operationId": "ListComponentBundles",
        "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": "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/CursorPageOfComponentBundleDto"
                }
              }
            },
            "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 a component\u0027s bundles",
        "tags": [
          "Components"
        ]
      },
      "post": {
        "description": "The body is the raw bundle zip, at most 5 MB, sent as application/zip, application/x-zip-compressed or application/octet-stream; any other Content-Type is a 415. The archive must contain a root index.html; entry paths must not escape the bundle root. Uploading does not activate \u2014 re-pin explicitly with :activate-bundle. Identical content returns 200 with the existing version.",
        "operationId": "UploadComponentBundle",
        "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/octet-stream": {
              "schema": {
                "format": "binary",
                "type": "string"
              }
            },
            "application/x-zip-compressed": {
              "schema": {
                "format": "binary",
                "type": "string"
              }
            },
            "application/zip": {
              "schema": {
                "format": "binary",
                "type": "string"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComponentBundleDto"
                }
              }
            },
            "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/ComponentBundleDto"
                }
              }
            },
            "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"
                }
              }
            }
          },
          "413": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Payload Too Large",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is rejected with 429 and a Retry-After.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix epoch seconds when the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "415": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Unsupported Media Type",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is 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": "Upload a component bundle",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components/{id}/bundles/{bundleId}": {
      "get": {
        "description": "Returns one uploaded bundle version of the component, with its content hash and size; a bundle never changes once uploaded. Requires settings:read. A missing component is a 404 component.not_found, and a bundle that is missing or belongs to another component is a 404 component.bundle_not_found.",
        "operationId": "GetComponentBundleById",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "bundleId",
            "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/ComponentBundleDto"
                }
              }
            },
            "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 component bundle",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components/{id}:activate-bundle": {
      "post": {
        "description": "Pins the runtime bundle learners are served. Uploading never pins; this is the explicit second step. A block type hosts no runtime: 409 component.not_applicable_to_kind, whichever bundle id is named.",
        "operationId": "ActivateComponentBundle",
        "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/ActivateComponentBundleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComponentRegistrationDto"
                }
              }
            },
            "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"
                }
              }
            }
          },
          "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": "Pin a component\u0027s runtime bundle",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components/{id}:activate-editor-bundle": {
      "post": {
        "description": "Pins the editor bundle an authoring client frames to author this component\u0027s definition. The bundle id comes from the same version list the runtime pin uses \u2014 a bundle is content, and the two pins choose which version plays which role. Re-pinning the already-pinned is a no-op 200.",
        "operationId": "ActivateComponentEditorBundle",
        "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/ActivateComponentBundleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComponentRegistrationDto"
                }
              }
            },
            "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": "Pin a component\u0027s editor bundle",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components/{id}:deactivate-bundle": {
      "post": {
        "description": "Un-pins the runtime bundle, so the component stays registered but resolves to no URL. With disabling, one of the two ways to take a component\u0027s code out of service \u2014 both remain available to a tenant without the custom_activities entitlement. Un-pinning the already-unpinned is a no-op 200.",
        "operationId": "DeactivateComponentBundle",
        "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/ComponentRegistrationDto"
                }
              }
            },
            "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": "Unpin a component\u0027s runtime bundle",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components/{id}:deactivate-editor-bundle": {
      "post": {
        "description": "Un-pins the editor bundle, so this component\u0027s definition is authored as raw JSON until another is pinned. Ungated by the custom_activities entitlement: while an editor is pinned it replaces the raw-JSON fallback, so a de-entitled tenant with a broken editor would otherwise have no way to edit anything. Un-pinning the already-unpinned is a no-op 200.",
        "operationId": "DeactivateComponentEditorBundle",
        "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/ComponentRegistrationDto"
                }
              }
            },
            "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": "Unpin a component\u0027s editor bundle",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components/{id}:disable": {
      "post": {
        "description": "Disables this registration. Requires components:manage. Repeating the same status returns the unchanged resource.",
        "operationId": "DisableComponentRegistration",
        "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/ComponentRegistrationDto"
                }
              }
            },
            "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": "Disable a component",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components/{id}:enable": {
      "post": {
        "description": "Enables this registration. Requires components:manage. Repeating the same status returns the unchanged resource.",
        "operationId": "EnableComponentRegistration",
        "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/ComponentRegistrationDto"
                }
              }
            },
            "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": "Enable a component",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components:activity-types": {
      "get": {
        "description": "The tenant\u0027s registered activity components, enabled and disabled, newest first \u2014 the palette a create picker offers beside the built-in activity types. Gated content:read, so content authors without settings:read can use it. A disabled registration is included with its status, so a picker can show it disabled rather than hide it. The custom_activities entitlement is not consulted: existing registered activities remain usable after a downgrade.",
        "operationId": "ListActivityTypes",
        "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/LinkedCollectionOfActivityTypeDto"
                }
              }
            },
            "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": "List the tenant\u0027s activity types",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components:block-types": {
      "get": {
        "description": "The tenant\u0027s registered presentation block types, enabled and disabled, newest first \u2014 the palette a block editor offers. Gated content:read, so content authors without settings:read can use it. A disabled type is included with its status, so an editor can show it disabled rather than hide it.",
        "operationId": "ListBlockTypes",
        "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/LinkedCollectionOfBlockTypeDto"
                }
              }
            },
            "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": "List the tenant\u0027s block types",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components:resolve": {
      "get": {
        "description": "Resolves a componentUri for the learner runtime. A miss is a 200 with registered=false, never a 404 \u2014 the caller falls through to the built-in manifest. A disabled registration never returns a bundle URL. A block type is not served here: it answers registered=false, exactly as a miss does. A URL is served only for an enabled activity with a pinned bundle: it is Seren-hosted, carries a one-hour token, and comes with bundleUrlExpiresAt and contentHash, which are null exactly when bundleUrl is. The embed runner passes its public embedId (emb_\u2026): the embed must be an enabled record in the caller\u0027s tenant with at least one framing origin, or the answer is 404 embed.not_found before the registry is read. Its token then also carries the embed\u0027s allowed origins and the runner\u0027s own origins, which the bundle host serves as the bundle\u0027s frame-ancestors. Omit embedId everywhere else.",
        "operationId": "ResolveComponent",
        "parameters": [
          {
            "in": "query",
            "name": "componentUri",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "embedId",
            "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/ComponentResolutionDto"
                }
              }
            },
            "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"
                }
              }
            }
          },
          "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": "Resolve a component for learners",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/components:resolve-editor": {
      "get": {
        "description": "Resolves the editor bundle an authoring client frames to author a componentUri\u0027s definition. Gated content:read, so the people who author content can use it \u2014 the verb the content-author role bundle actually carries. A miss is a 200 with resolved=false: the caller falls through to the published built-in editor manifest (its address is per-environment configuration for the client, and its shape is the seren-activity-editor/1 contract doc) and then to raw-JSON authoring. \u0060status\u0060 is not consulted \u2014 disabling governs what learners run, not what an author may edit. An optional \u0060kind\u0060 (activity or block) makes a registration of the other kind a miss. The editor URL carries a one-hour token and editorUrlExpiresAt; an unresolved answer has a null expiry.",
        "operationId": "ResolveComponentEditor",
        "parameters": [
          {
            "in": "query",
            "name": "componentUri",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "kind",
            "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/ComponentEditorResolutionDto"
                }
              }
            },
            "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": "Resolve a component\u0027s editor",
        "tags": [
          "Components"
        ]
      }
    },
    "/v1/embeds": {
      "get": {
        "description": "Returns every embed in the tenant, enabled and disabled, newest first, in one unpaged response. Requires settings:read. A plan without the external_embeds entitlement never refuses this read; it withholds the create, edit and enable links, while disable and delete stay offered.",
        "operationId": "ListEmbedRegistrations",
        "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/LinkedCollectionOfEmbedRegistrationDto"
                }
              }
            },
            "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": "List embeds",
        "tags": [
          "Embeds"
        ]
      },
      "post": {
        "description": "Mints an embed record. The server mints the public embed id (returned as \u0027embedId\u0027) \u2014 the copy-and-paste value for third-party markup. \u0027elementKind\u0027 and the pinned version/placement ids are create-only: re-pointing published markup means minting a new embed. \u0027theme\u0027 takes only the embed theme tokens (such as \u0027--accent\u0027) with short CSS values; the embed applies it as the default and the host page\u0027s own init theme overrides it. Requires the external_embeds entitlement.",
        "operationId": "CreateEmbedRegistration",
        "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/CreateEmbedRegistrationCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbedRegistrationDto"
                }
              }
            },
            "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": "Create an embed",
        "tags": [
          "Embeds"
        ]
      }
    },
    "/v1/embeds/{id}": {
      "delete": {
        "description": "Hard delete \u2014 the pre-delete snapshot in domain_events is the surviving record, and the public embed id becomes indistinguishable from one that never existed. To turn an embed off reversibly, disable it instead.",
        "operationId": "DeleteEmbedRegistration",
        "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 an embed",
        "tags": [
          "Embeds"
        ]
      },
      "get": {
        "description": "Returns one embed, including its public \u0060embedId\u0060, the value that goes in the host page\u0027s markup. Requires settings:read. A plan without the external_embeds entitlement never refuses this read; it withholds the edit and enable links, while disable and delete stay offered.",
        "operationId": "GetEmbedRegistrationById",
        "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/EmbedRegistrationDto"
                }
              }
            },
            "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 embed",
        "tags": [
          "Embeds"
        ]
      },
      "patch": {
        "description": "Edits an embed with an RFC 7396 merge patch. Requires embeds:manage and external_embeds. Origins and theme replace their previous values; theme takes the same tokens as create. Status is changed only through :enable or :disable; a status member is a malformed request (400). Disable and delete remain available without the entitlement.",
        "operationId": "UpdateEmbedRegistration",
        "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/EmbedRegistrationMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/EmbedRegistrationMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbedRegistrationDto"
                }
              }
            },
            "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 embed",
        "tags": [
          "Embeds"
        ]
      }
    },
    "/v1/embeds/{id}:disable": {
      "post": {
        "description": "Disables this registration. Requires embeds:manage. Repeating the same status returns the unchanged resource.",
        "operationId": "DisableEmbedRegistration",
        "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/EmbedRegistrationDto"
                }
              }
            },
            "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": "Disable an embed",
        "tags": [
          "Embeds"
        ]
      }
    },
    "/v1/embeds/{id}:enable": {
      "post": {
        "description": "Enables this registration. Requires embeds:manage. Repeating the same status returns the unchanged resource.",
        "operationId": "EnableEmbedRegistration",
        "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/EmbedRegistrationDto"
                }
              }
            },
            "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": "Enable an embed",
        "tags": [
          "Embeds"
        ]
      }
    },
    "/v1/lti/outcomes": {
      "get": {
        "description": "The grade a partner tool has posted back for one (course version, tool, learner) subject (progress:read), as zero or one item. An empty list is the normal answer while the learner is still working \u2014 not an error \u2014 so a client may poll it. courseId is the course VERSION id the launch was minted against. Omit userId for the caller\u0027s own grade, or name a learner with progress:read:tenant.",
        "operationId": "ListLtiOutcomes",
        "parameters": [
          {
            "in": "query",
            "name": "courseId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "ltiId",
            "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/LinkedCollectionOfLtiOutcomeDto"
                }
              }
            },
            "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": "Get the grade an LTI tool posted back",
        "tags": [
          "LTI"
        ]
      }
    },
    "/v1/lti/registrations": {
      "get": {
        "description": "Returns every LTI tool registration in the tenant, newest first, in one unpaged response. Requires settings:read. The shared secret is never included.",
        "operationId": "ListLtiRegistrations",
        "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/LinkedCollectionOfLtiRegistrationDto"
                }
              }
            },
            "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": "List LTI tool registrations",
        "tags": [
          "LTI"
        ]
      },
      "post": {
        "description": "Registers an external LTI tool that learners can be launched into. Requires lti:manage. \u0060ltiId\u0060 must be unique in the tenant (a duplicate is a 409 lti.lti_id_in_use) and can never change afterwards. \u0060sharedSecret\u0060 is stored encrypted and never returned. \u0060ltiVersion\u0060 accepts 1.1 or 1.3, but launches are always signed as LTI 1.1 for now.",
        "operationId": "CreateLtiRegistration",
        "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/CreateLtiRegistrationCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LtiRegistrationDto"
                }
              }
            },
            "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"
                }
              }
            }
          },
          "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": "Register an LTI tool",
        "tags": [
          "LTI"
        ]
      }
    },
    "/v1/lti/registrations/{id}": {
      "delete": {
        "description": "Permanently deletes the registration; nothing blocks it. Requires lti:manage. Launches that name its \u0060ltiId\u0060 then fail, and grades its tool posts back are refused.",
        "operationId": "DeleteLtiRegistration",
        "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 an LTI tool registration",
        "tags": [
          "LTI"
        ]
      },
      "get": {
        "description": "Returns one LTI tool registration. Requires settings:read. The shared secret is never included.",
        "operationId": "GetLtiRegistrationById",
        "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/LtiRegistrationDto"
                }
              }
            },
            "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 LTI tool registration",
        "tags": [
          "LTI"
        ]
      },
      "patch": {
        "description": "RFC 7396 merge patch over the mutable fields (name, launchUrl, consumerKey, sharedSecret, ltiVersion). An absent field is left untouched; none can be set to null, since every one is required. \u0027sharedSecret\u0027 is write-only and rotates only when supplied. \u0027ltiId\u0027 is immutable: re-key by creating a new registration.",
        "operationId": "UpdateLtiRegistration",
        "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/LtiRegistrationMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/LtiRegistrationMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LtiRegistrationDto"
                }
              }
            },
            "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 an LTI tool registration",
        "tags": [
          "LTI"
        ]
      }
    },
    "/v1/lti:launch": {
      "post": {
        "description": "Mints a launch for an LTI activity the caller may reach (progress:write). The returned launchUrl is opaque and whole \u2014 load it in a browser before expiresAt (60 seconds); never compose one. Omit userId to launch for the caller, or name a learner with progress:write:tenant. returnUrl is optional and, when sent, must be on this host\u0027s allowed-origin list: it becomes the tool\u0027s launch_presentation_return_url. 403 covers both an out-of-reach learner and a launch the Platform refuses (unreachable course, a placement that does not host this tool, or a learner who is not enrolled).",
        "operationId": "MintLtiLaunch",
        "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/MintLtiLaunchCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LtiLaunchDto"
                }
              }
            },
            "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": "Launch a learner into an LTI tool",
        "tags": [
          "LTI"
        ]
      }
    },
    "/v1/media": {
      "get": {
        "description": "Newest first, cursor-paginated (limit/after). Filters: kind, visibility, status and q (words in the filename). status is pending, ready, processing or failed, and defaults to ready, so a file still uploading or processing, or a video that failed processing, is listed only when asked for. 422 media.invalid_cursor: an after cursor naming no file this tenant holds, including one deleted since the page was served.",
        "operationId": "ListMediaFiles",
        "parameters": [
          {
            "in": "query",
            "name": "kind",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "visibility",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "q",
            "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/CursorPageOfMediaFileDto"
                }
              }
            },
            "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 media files",
        "tags": [
          "Media"
        ]
      },
      "post": {
        "description": "Mints a pending media file and its one-time upload target. Send the bytes to upload.url with upload.method before upload.expiresAt, then call :confirm. When upload.formField is absent, send the raw file with exactly upload.headers (a PUT, 15 minutes); the URL admits repeated PUTs until it expires, but only ever writes a staging copy nothing serves. When upload.formField is present \u2014 every video, which is hosted on Cloudflare Stream \u2014 send multipart/form-data with the file in that field (a POST, 30 minutes, one upload), and no video longer than upload.maxDurationSeconds is accepted. 403 media.not_entitled: the tenant\u0027s plan has no media hosting. 402 plan.limit_reached: the declared size would exceed the tenant\u0027s media byte cap (advisory, since :confirm enforces the cap on the real size), or, for a video, the tenant\u0027s video minutes are used up. A replay under the same Idempotency-Key returns the first response unchanged, upload target included; once that has expired, mint again under a new key.",
        "operationId": "MintMediaFile",
        "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/MintMediaFileCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MintedMediaFileDto"
                }
              }
            },
            "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"
                }
              }
            }
          },
          "402": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Payment Required",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is 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": "Start a media upload",
        "tags": [
          "Media"
        ]
      }
    },
    "/v1/media/stream-webhook": {
      "post": {
        "description": "Cloudflare Stream\u0027s webhook \u2014 called by Stream, never by a client. Anonymous and without X-Tenant-Id: the Webhook-Signature header (HMAC-SHA256 of the time, a dot and the raw body, keyed by the webhook secret) authenticates it, and the video\u0027s own tags name its tenant and file. Answers 204 whether or not the report changed anything, including for videos this environment does not hold. 403 media.webhook_signature_invalid: missing, malformed, more than five minutes from now, or wrong; an environment holding no webhook secret refuses every delivery the same way. 400 media.webhook_credentials_refused: the request carried a token. 400 media.webhook_malformed: signed, but not a Stream video object. 413 media.webhook_too_large: over 64 KiB.",
        "operationId": "ReceiveStreamWebhook",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {}
            }
          },
          "required": true
        },
        "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"
                }
              }
            }
          },
          "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"
                }
              }
            }
          },
          "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"
                }
              }
            }
          },
          "413": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Payload Too Large",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed 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": [],
        "summary": "Receive a Cloudflare Stream webhook",
        "tags": [
          "Media"
        ]
      }
    },
    "/v1/media/{id}": {
      "delete": {
        "description": "Deletes the file whatever its status. A ready file\u0027s bytes move to a trash area kept for 30 days (recovery is an operator step); the row is removed, and its snapshot is the record. A video\u0027s delete is final: it is deleted at Cloudflare Stream, which keeps no copy. Nothing checks whether content still links the URL, which then answers 404.",
        "operationId": "DeleteMediaFile",
        "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 media file",
        "tags": [
          "Media"
        ]
      },
      "get": {
        "description": "A pending file is readable, with no url and no upload target \u2014 the target is handed out once, by the mint.",
        "operationId": "GetMediaFile",
        "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/MediaFileDto"
                }
              }
            },
            "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 media file",
        "tags": [
          "Media"
        ]
      },
      "patch": {
        "description": "RFC 7396 merge patch; visibility (tenant or public) is the only field, and cannot be null. The URL never changes. A ready file\u0027s served copy is re-stamped whenever visibility is sent, even unchanged, and its URL is purged from the edge cache. A pending file\u0027s patch also removes any copy a failed confirm left published; the upload is kept, and confirm will publish it with the visibility this patch sets. A video\u0027s visibility is also set at Cloudflare Stream, first: a tenant-only video needs a signed URL to play. 409 media.not_ready: a video Stream failed to process \u2014 delete it instead.",
        "operationId": "UpdateMediaFile",
        "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/MediaFileMergePatch"
              }
            },
            "application/merge-patch\u002Bjson": {
              "schema": {
                "$ref": "#/components/schemas/MediaFileMergePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaFileDto"
                }
              }
            },
            "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": "Change a media file\u0027s visibility",
        "tags": [
          "Media"
        ]
      }
    },
    "/v1/media/{id}:confirm": {
      "post": {
        "description": "Inspects the upload and, when it is a file of the declared kind within both caps, publishes it at its url and answers the file, now ready, with the type sniffed from its bytes. A refusal leaves the file pending. 422 media.upload_missing (nothing, or an empty file, was uploaded), 422 media.type_mismatch (the bytes are not the declared kind and extension) and 413 media.too_large (over the kind\u0027s cap) remove the upload: PUT the right file to the same URL while it is valid, then confirm again. 402 plan.limit_reached (the tenant\u0027s media byte cap, on the real size) keeps the upload, so the same file confirms once space is freed or the plan raised. 409 media.upload_changed: the upload was replaced while it was being inspected \u2014 confirm again. 409 media.not_pending: already confirmed. Each attempt needs a new Idempotency-Key: under the same key a refusal is replayed, not re-inspected. A file refused for good should be deleted, since a pending file counts its declared size against the cap at the next mint until it is. A video\u0027s confirm is optional and reports where Stream has got to: 200 with status processing, ready or failed (a video Stream could not process, which should be deleted), or 422 media.upload_missing while Stream is still waiting for the upload. Poll a processing video with a new Idempotency-Key each time; the video also turns ready on its own.",
        "operationId": "ConfirmMediaFile",
        "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/MediaFileDto"
                }
              }
            },
            "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"
                }
              }
            }
          },
          "402": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Payment Required",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is 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"
                }
              }
            }
          },
          "413": {
            "content": {
              "application/problem\u002Bjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            },
            "description": "Payload Too Large",
            "headers": {
              "X-Correlation-Id": {
                "description": "The request correlation id, echoed on every response and matching the problem body\u0027s correlationId; supply it as X-Correlation-Id to correlate your own traces.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "description": "The limit of the window this request was counted in: the tenant\u0027s, which its users and integrations share (by default 1000 per minute), or a delegated agent\u0027s own (by default 300 per minute). Absent when the request was not counted \u2014 a platform-service system caller is exempt.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current window. At zero, the next request is 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": "Confirm a media upload",
        "tags": [
          "Media"
        ]
      }
    },
    "/v1/media:session": {
      "post": {
        "description": "Mints a media session ticket for the caller\u0027s tenant (content:read). POST the ticket to exchangeUrl with credentials before expiresAt (60 seconds); the media host answers with the cookie that lets this browser load the tenant\u0027s tenant-only files. Call once per session and on each token refresh.",
        "operationId": "CreateMediaSession",
        "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"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaSessionDto"
                }
              }
            },
            "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"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Start a media session",
        "tags": [
          "Media"
        ]
      }
    },
    "/v1/media:sign": {
      "post": {
        "description": "Signs up to 200 stored media URLs (each at most 2048 characters) under content:read; system callers are refused. Sign the stored URL, never a signed one. Each URL must start with the configured media-origin prefix (ordinal comparison), have no query or fragment, and contain exactly three path segments decoded once: the caller\u0027s lowercase tenant UUID, a lowercase file UUID, and a nonempty filename other than dot or dot-dot with no slash, backslash or control character. Percent escapes must be well-formed UTF-8. Existence and visibility are not read. The batch is all-or-nothing: 422 media.url_not_signable names every refused urls[i]. Returns the inputs verbatim with ?t= appended and their shared expiresAt, at most ten minutes from the caller\u0027s membership check (including clock allowance). Absent or expired authority returns 403 media.invalid_session_authority. The token binds the tenant and file, not the host: a client may rewrite the canonical media origin to its custom media origin after signing (D22). Signing has no side effect worth deduplicating, so a signer should not send Idempotency-Key; a replay after ten minutes returns expired tokens, and expiresAt says so (D52).",
        "operationId": "SignMediaUrls",
        "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/SignMediaUrlsCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignedMediaUrlsDto"
                }
              }
            },
            "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": "Sign media URLs",
        "tags": [
          "Media"
        ]
      }
    },
    "/v1/media:sweep-abandoned": {
      "post": {
        "description": "Platform-service only (system:media:sweep, under X-Seren-Tenant): removes up to 25 pending files whose upload expired more than an hour ago, with anything they stored \u2014 a video only once Stream confirms nothing was uploaded, otherwise it is moved on \u2014 and reconciles up to 10 videos processing with no report for ten minutes against Stream. reconciled counts the videos moved. Call again while hasMore is true.",
        "operationId": "SweepAbandonedMedia",
        "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"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaSweepResultDto"
                }
              }
            },
            "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"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": []
          }
        ],
        "summary": "Remove abandoned uploads and reconcile stalled videos",
        "tags": [
          "Media"
        ]
      }
    }
  },
  "tags": [
    {
      "description": "Custom activity and block components a tenant registers, their versioned bundles, and resolving the bundle to load.",
      "name": "Components"
    },
    {
      "description": "Records that let a course be embedded in another site, and the public manifest an embed loads.",
      "name": "Embeds"
    },
    {
      "description": "LTI 1.1 partner tools: registering one, launching a learner into it, and the grades it posts back.",
      "name": "LTI"
    },
    {
      "description": "Files a tenant uploads and serves: upload, confirm, visibility, signed access and removal.",
      "name": "Media"
    },
    {
      "description": "What the calling token may do on this API.",
      "name": "Capabilities"
    }
  ]
}
