{
  "openapi": "3.1.0",
  "info": {
    "title": "LPAI integration API",
    "version": "0.5.0",
    "license": {
      "name": "Apache-2.0",
      "identifier": "Apache-2.0"
    },
    "description": "The endpoints a learning platform uses to send activity to LPAI and read what it may. Every write is authenticated: a sensor token on the xAPI pack, a tenant assertion (an EdDSA JWT signed with the tenant's registered key, single-use, at most 60 seconds) on the native endpoints. Errors are { error: { code, message } } with the HTTP status; 429 carries Retry-After."
  },
  "servers": [
    {
      "url": "https://{project}.supabase.co/functions/v1/lpai-v1",
      "variables": {
        "project": {
          "default": "your-project"
        }
      }
    }
  ],
  "components": {
    "securitySchemes": {
      "sensorToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The token LPAI issued for your xAPI sensor."
      },
      "tenantAssertion": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "alg EdDSA, typ lpai-assertion+jwt, kid = your issuer key id; iss = tenant id, aud = lpai-v1, jti single-use, exp - iat <= 60 s, claim purpose."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "schema": {
                "type": "string",
                "description": "On 422: the payload schema's id"
              },
              "pointer": {
                "type": "string"
              }
            }
          }
        }
      },
      "Outcome": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "enum": [
              "sealed",
              "held",
              "unmapped",
              "duplicate"
            ]
          },
          "event_id": {
            "type": "string",
            "format": "uuid"
          },
          "mapped_to": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Statement": {
        "type": "object",
        "description": "An xAPI 1.0.3 statement; see the profile.",
        "required": [
          "actor",
          "verb",
          "object"
        ]
      },
      "AlignmentStatus": {
        "type": "object",
        "description": "How a content version is mapped to skills, at what authority, and what that serves.",
        "required": [
          "content_ref",
          "content_version",
          "mapping_status",
          "mapping_version",
          "alignment_type",
          "authority",
          "authority_required",
          "usable_for",
          "skills",
          "etag"
        ],
        "properties": {
          "content_ref": {
            "type": "string"
          },
          "content_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "mapping_status": {
            "enum": [
              "none",
              "provisional",
              "inferred",
              "effective"
            ]
          },
          "mapping_version": {
            "type": "integer"
          },
          "alignment_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "model_embedding",
              "model_judged",
              "imported",
              "author_declared",
              "reviewed",
              "validated",
              null
            ]
          },
          "authority": {
            "type": "number"
          },
          "authority_required": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "The lowest alignment type each use needs"
          },
          "usable_for": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "skills": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "kc_id",
                "role",
                "weight"
              ],
              "properties": {
                "kc_id": {
                  "type": "string"
                },
                "role": {
                  "enum": [
                    "assesses",
                    "practises",
                    "instructs",
                    "requires",
                    "extends",
                    "mentions"
                  ]
                },
                "weight": {
                  "type": "number"
                }
              }
            }
          },
          "etag": {
            "type": "string"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "When a requested type was above the caller's cap"
          }
        }
      },
      "ContentRegistration": {
        "type": "object",
        "required": [
          "content_ref"
        ],
        "properties": {
          "content_ref": {
            "type": "string",
            "description": "The content's URL or IRI; an author owns references inside its tenant's registered namespaces"
          },
          "content_version": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "resource_type": {
            "type": "string"
          },
          "parent_ref": {
            "type": "string"
          },
          "standards": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "framework:id"
            }
          },
          "extract": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              },
              "text": {
                "type": "string"
              },
              "stem": {
                "type": "string"
              }
            }
          },
          "allow_model": {
            "type": "boolean",
            "description": "The owner's permission to send the extract to the alignment model"
          },
          "licence": {
            "type": "string"
          }
        }
      },
      "ContentAssertion": {
        "type": "object",
        "required": [
          "content_ref",
          "alignments"
        ],
        "properties": {
          "content_ref": {
            "type": "string"
          },
          "content_version": {
            "type": "string",
            "description": "Omitted: every version"
          },
          "alignments": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "required": [
                "role"
              ],
              "description": "Name exactly one of kc_id or standard",
              "properties": {
                "kc_id": {
                  "type": "string"
                },
                "standard": {
                  "type": "string",
                  "description": "framework:id, expanded through the skills anchored to it"
                },
                "role": {
                  "enum": [
                    "assesses",
                    "practises",
                    "instructs",
                    "requires",
                    "extends",
                    "mentions"
                  ]
                },
                "weight": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "centrality": {
                  "enum": [
                    "primary",
                    "secondary",
                    "incidental"
                  ]
                },
                "coverage": {
                  "enum": [
                    "full",
                    "partial",
                    "introductory"
                  ]
                },
                "dok": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 4
                },
                "confidence": {
                  "type": "number"
                },
                "rationale": {
                  "type": "string"
                },
                "alignment_type": {
                  "type": "string",
                  "description": "Requested; recorded at the caller's cap at most"
                }
              }
            }
          }
        }
      },
      "IngestRequest": {
        "type": "object",
        "required": [
          "event_type",
          "learner_id",
          "occurred_at",
          "origin_tool_class",
          "idempotency_key",
          "payload"
        ],
        "properties": {
          "event_type": {
            "type": "string"
          },
          "learner_id": {
            "type": "string",
            "format": "uuid"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "origin_tool_class": {
            "type": "string"
          },
          "idempotency_key": {
            "type": "string"
          },
          "references_event_id": {
            "type": "string",
            "format": "uuid"
          },
          "payload": {
            "oneOf": [
              {
                "$ref": "https://id.learnerrecord.org/schemas/credential.issued/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/credential.substantiated/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/credential.revoked/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/performance.artifact/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/state.snapshot/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/external.attested/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/observation.human/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/organic.signal/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/companion.dissection/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/session.rollup/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/assertion.learner/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/assertion.educator/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/dispute.raised/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/dispute.resolved/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/instrumentation.excluded/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/learner.provisioned/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/assessment.response/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/event.voided/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/consent.granted/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/consent.revoked/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/erasure.executed/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/learner.identity.linked/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/learner.identity.unlinked/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/learner.merged/v1.json"
              },
              {
                "$ref": "https://id.learnerrecord.org/schemas/learner.merge_reverted/v1.json"
              }
            ]
          }
        }
      },
      "ProvisionRequest": {
        "type": "object",
        "required": [
          "external_ref",
          "notice_version",
          "basis"
        ],
        "properties": {
          "external_ref": {
            "type": "string",
            "description": "The learner's identifier in your namespace"
          },
          "namespace": {
            "type": "string",
            "format": "uri",
            "description": "Your registered identifier namespace (for xAPI, your homePage)"
          },
          "binding_kind": {
            "enum": [
              "institutional",
              "federated",
              "contact",
              "external_learning",
              "wallet"
            ],
            "default": "institutional"
          },
          "notice_version": {
            "type": "string"
          },
          "basis": {
            "enum": [
              "contract",
              "legitimate_interest",
              "consent",
              "legal_obligation",
              "public_task"
            ]
          },
          "grant_instruction_read": {
            "type": "boolean",
            "default": true
          }
        }
      }
    }
  },
  "paths": {
    "/v1/packs/xapi/statements": {
      "post": {
        "summary": "Send xAPI statements (1 to 100)",
        "security": [
          {
            "sensorToken": []
          }
        ],
        "parameters": [
          {
            "name": "X-Experience-API-Version",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "1.0.3"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Statement"
                  },
                  {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "$ref": "#/components/schemas/Statement"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The statement ids, in order (xAPI). x-lpai-outcomes: what became of each; x-lpai-alignment: the alignment status of content the statements named, where this sensor has not seen its ETag.",
            "headers": {
              "x-lpai-outcomes": {
                "schema": {
                  "type": "string",
                  "description": "JSON array of Outcome"
                }
              },
              "x-lpai-alignment": {
                "schema": {
                  "type": "string",
                  "description": "JSON object keyed by content ref"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid statement",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unknown or revoked sensor"
          },
          "409": {
            "description": "A different statement with this id was already received"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          }
        }
      },
      "get": {
        "summary": "A statement as received",
        "security": [
          {
            "sensorToken": []
          }
        ],
        "parameters": [
          {
            "name": "statementId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The statement"
          },
          "404": {
            "description": "Not this sensor's"
          },
          "410": {
            "description": "Expired unresolved"
          }
        }
      }
    },
    "/v1/packs/xapi/about": {
      "get": {
        "summary": "Version, the profile, extensions and verbs",
        "security": [
          {
            "sensorToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "extensions[\"https://id.learnerrecord.org/packs/xapi\"] names the profile"
          }
        }
      }
    },
    "/v1/content": {
      "post": {
        "summary": "Register content and a version (an author token owns content in its namespaces)",
        "security": [
          {
            "sensorToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContentRegistration"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The object, the version, the alignment status, provisional links and whether the judge was queued"
          },
          "403": {
            "description": "content_not_owned"
          },
          "409": {
            "description": "version_conflict: this version already has a different extract"
          }
        }
      }
    },
    "/v1/content/alignments": {
      "post": {
        "summary": "Declare skill links for content (author_declared for an author on its own content; imported otherwise)",
        "security": [
          {
            "sensorToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContentAssertion"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The alignment status after the change",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlignmentStatus"
                }
              }
            }
          },
          "400": {
            "description": "invalid_target"
          }
        }
      }
    },
    "/v1/content/alignment": {
      "get": {
        "summary": "The alignment status of one content version",
        "security": [
          {
            "sensorToken": []
          }
        ],
        "parameters": [
          {
            "name": "ref",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "version",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The status, with an ETag",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlignmentStatus"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged since that ETag"
          }
        }
      }
    },
    "/v1/content/alignment/lookup": {
      "post": {
        "summary": "The alignment status of up to 200 content versions",
        "security": [
          {
            "sensorToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "refs"
                ],
                "properties": {
                  "refs": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "object",
                      "required": [
                        "content_ref"
                      ],
                      "properties": {
                        "content_ref": {
                          "type": "string"
                        },
                        "content_version": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ statuses: AlignmentStatus[] }"
          }
        }
      }
    },
    "/v1/admin/provision": {
      "post": {
        "summary": "Provision a learner in your namespace",
        "security": [
          {
            "tenantAssertion": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProvisionRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The learner, the provisioning event and the instruction grant"
          },
          "403": {
            "description": "namespace_not_owned"
          },
          "404": {
            "description": "unknown_namespace"
          },
          "409": {
            "description": "binding_exists (no learner id is returned)"
          }
        }
      }
    },
    "/v1/events": {
      "post": {
        "summary": "Send a native LPAI event",
        "security": [
          {
            "tenantAssertion": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An idempotent replay"
          },
          "201": {
            "description": "The event skeleton"
          },
          "422": {
            "description": "The payload fails its schema; error.schema names it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/learners/{learner_id}/events": {
      "get": {
        "summary": "Events you originated for a learner you serve (payloads need an evidence grant; the learner's whole-record grant crosses origins)",
        "security": [
          {
            "tenantAssertion": []
          }
        ],
        "parameters": [
          {
            "name": "learner_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "include",
            "in": "query",
            "schema": {
              "enum": [
                "payload"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Events"
          },
          "403": {
            "description": "consent_required"
          }
        }
      }
    },
    "/v1/learners/{learner_id}/proficiency": {
      "get": {
        "summary": "Proficiency against standards (a mastery grant)",
        "security": [
          {
            "tenantAssertion": []
          }
        ],
        "parameters": [
          {
            "name": "learner_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "months",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "target",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The proficiency read"
          }
        }
      }
    },
    "/v1/learners/{learner_id}/load": {
      "get": {
        "summary": "The whole-learner load view (your activity and one aggregate of the rest)",
        "security": [
          {
            "tenantAssertion": []
          }
        ],
        "parameters": [
          {
            "name": "learner_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The load view; shadowed: true while your tenant is in engagement shadow"
          }
        }
      }
    }
  }
}
