{
  "openapi": "3.1.0",
  "info": { "title": "Eduvo Integration API", "version": "1.0.0", "description": "System-agnostic domain API for external clients. This contract does not imply deployment." },
  "servers": [{ "url": "https://eduvo-api-sandbox.web.app", "description": "Eduvo development sandbox with synthetic data; OAuth required." }],
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/oauth/token": { "post": { "security": [], "summary": "Issue a client credentials token", "description": "Authenticate by HTTP Basic (client ID and secret) or form fields. Optional scope is a space-separated subset of assigned scopes. Signed JWT expires after 3600 seconds.", "requestBody": { "required": true, "content": { "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/TokenRequest" } } } }, "responses": { "200": { "description": "Access token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Token" } } } }, "400": { "$ref": "#/components/responses/Error" }, "401": { "$ref": "#/components/responses/Error" } } } },
    "/v1/users": {
      "get": { "summary": "List users", "x-required-scopes": ["users:read"], "parameters": [{ "$ref": "#/components/parameters/cursor" }, { "$ref": "#/components/parameters/limit" }], "responses": { "200": { "description": "Users", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserList" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" } } },
      "post": { "summary": "Create a user", "x-required-scopes": ["users:write"], "parameters": [{ "$ref": "#/components/parameters/idempotency" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserInput" } } } }, "responses": { "201": { "description": "Created user", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } }, "400": { "$ref": "#/components/responses/Error" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "409": { "$ref": "#/components/responses/Error" } } }
    },
    "/v1/users/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/id" }],
      "get": { "summary": "Get a user", "x-required-scopes": ["users:read"], "responses": { "200": { "description": "User", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } },
      "patch": { "summary": "Update a user", "x-required-scopes": ["users:write"], "parameters": [{ "$ref": "#/components/parameters/idempotency" }], "requestBody": { "required": true, "content": { "application/merge-patch+json": { "schema": { "$ref": "#/components/schemas/UserPatch" } } } }, "responses": { "200": { "description": "Updated user", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } }, "400": { "$ref": "#/components/responses/Error" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } },
      "delete": { "summary": "Anonymize user personal data", "description": "Anonymizes personal data; records may be retained in anonymized form where required.", "x-required-scopes": ["users:write"], "parameters": [{ "$ref": "#/components/parameters/idempotency" }], "responses": { "204": { "description": "User anonymized" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } }
    },
    "/v1/users/external/{externalId}": { "put": { "summary": "Upsert user by caller external ID", "description": "System-agnostic source identifier. URL-encode the value.", "x-required-scopes": ["users:write"], "parameters": [{ "name": "externalId", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1 } }, { "$ref": "#/components/parameters/idempotency" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserInput" } } } }, "responses": { "200": { "description": "Updated user", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } }, "201": { "description": "Created user", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } }, "400": { "$ref": "#/components/responses/Error" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "409": { "$ref": "#/components/responses/Error" } } } },
    "/v1/courses": { "get": { "summary": "List published courses", "x-required-scopes": ["courses:read"], "parameters": [{ "$ref": "#/components/parameters/cursor" }, { "$ref": "#/components/parameters/limit" }], "responses": { "200": { "description": "Courses", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CourseList" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" } } } },
    "/v1/courses/{id}": { "get": { "summary": "Get a published course", "x-required-scopes": ["courses:read"], "parameters": [{ "$ref": "#/components/parameters/id" }], "responses": { "200": { "description": "Course", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Course" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/v1/enrollments": {
      "get": { "summary": "List enrollments", "x-required-scopes": ["enrollments:read"], "parameters": [{ "$ref": "#/components/parameters/cursor" }, { "$ref": "#/components/parameters/limit" }], "responses": { "200": { "description": "Enrollments", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnrollmentList" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" } } },
      "post": { "summary": "Enroll a user in a course", "x-required-scopes": ["enrollments:write"], "parameters": [{ "$ref": "#/components/parameters/idempotency" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnrollmentInput" } } } }, "responses": { "201": { "description": "Created enrollment", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Enrollment" } } } }, "400": { "$ref": "#/components/responses/Error" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" }, "409": { "$ref": "#/components/responses/Error" } } }
    },
    "/v1/enrollments/{id}": {
      "parameters": [{ "$ref": "#/components/parameters/id" }],
      "get": { "summary": "Get an enrollment", "x-required-scopes": ["enrollments:read"], "responses": { "200": { "description": "Enrollment", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Enrollment" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } },
      "delete": { "summary": "Cancel an enrollment", "x-required-scopes": ["enrollments:write"], "parameters": [{ "$ref": "#/components/parameters/idempotency" }], "responses": { "204": { "description": "Enrollment cancelled" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } }
    },
    "/v1/enrollments/{id}/progress": { "get": { "summary": "Get integration-safe enrollment progress", "description": "Excludes internal telemetry and answers.", "x-required-scopes": ["enrollments:read"], "parameters": [{ "$ref": "#/components/parameters/id" }], "responses": { "200": { "description": "Progress", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Progress" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/v1/completions": { "get": { "summary": "List completions", "x-required-scopes": ["completions:read"], "parameters": [{ "$ref": "#/components/parameters/cursor" }, { "$ref": "#/components/parameters/limit" }], "responses": { "200": { "description": "Completions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompletionList" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" } } } },
    "/v1/completions/{id}": { "get": { "summary": "Get a completion", "x-required-scopes": ["completions:read"], "parameters": [{ "$ref": "#/components/parameters/id" }], "responses": { "200": { "description": "Completion", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Completion" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/v1/certificates": { "get": { "summary": "List issued certificates", "x-required-scopes": ["certificates:read"], "parameters": [{ "$ref": "#/components/parameters/cursor" }, { "$ref": "#/components/parameters/limit" }], "responses": { "200": { "description": "Certificates", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CertificateList" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" } } } },
    "/v1/certificates/{id}": { "get": { "summary": "Get a certificate record", "x-required-scopes": ["certificates:read"], "parameters": [{ "$ref": "#/components/parameters/id" }], "responses": { "200": { "description": "Certificate", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Certificate" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/v1/webhooks": {
      "get": { "summary": "List subscriptions without secrets", "x-required-scopes": ["webhooks:manage"], "parameters": [{ "$ref": "#/components/parameters/cursor" }, { "$ref": "#/components/parameters/limit" }], "responses": { "200": { "description": "Subscriptions", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookList" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" } } },
      "post": { "summary": "Create webhook subscription", "description": "The signing secret is returned once only.", "x-required-scopes": ["webhooks:manage"], "parameters": [{ "$ref": "#/components/parameters/idempotency" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookInput" } } } }, "responses": { "201": { "description": "Subscription and one-time secret", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookCreated" } } } }, "400": { "$ref": "#/components/responses/Error" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" } } }
    },
    "/v1/webhooks/{id}": { "delete": { "summary": "Delete webhook subscription", "x-required-scopes": ["webhooks:manage"], "parameters": [{ "$ref": "#/components/parameters/id" }, { "$ref": "#/components/parameters/idempotency" }], "responses": { "204": { "description": "Deleted" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/v1/webhooks/{id}/ping": { "post": { "summary": "Queue a test webhook delivery", "description": "202 means queued, not delivered.", "x-required-scopes": ["webhooks:manage"], "parameters": [{ "$ref": "#/components/parameters/id" }, { "$ref": "#/components/parameters/idempotency" }], "responses": { "202": { "description": "Delivery queued" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/healthz": { "get": { "security": [], "summary": "Health probe", "responses": { "200": { "description": "Healthy", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "const": "ok" } }, "required": ["status"] } } } } } } },
    "/openapi.json": { "get": { "security": [], "summary": "OpenAPI contract", "responses": { "200": { "description": "This document", "content": { "application/json": { "schema": { "type": "object" } } } } } } },
    "/docs": { "get": { "security": [], "summary": "Static documentation portal", "responses": { "200": { "description": "HTML portal", "content": { "text/html": { "schema": { "type": "string" } } } } } } }
  },
  "components": {
    "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } },
    "parameters": {
      "id": { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1 } },
      "cursor": { "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "Opaque cursor from nextCursor; omit for first page." },
      "limit": { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } },
      "idempotency": { "name": "Idempotency-Key", "in": "header", "schema": { "type": "string", "maxLength": 200 }, "description": "Recommended for writes; reuse only for an identical retry." }
    },
    "responses": { "Error": { "description": "Standard error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } },
    "schemas": {
      "TokenRequest": { "type": "object", "properties": { "grant_type": { "const": "client_credentials" }, "client_id": { "type": "string" }, "client_secret": { "type": "string", "writeOnly": true }, "scope": { "type": "string" } }, "required": ["grant_type"] },
      "Token": { "type": "object", "properties": { "access_token": { "type": "string" }, "token_type": { "const": "Bearer" }, "expires_in": { "const": 3600 }, "scope": { "type": "string" } }, "required": ["access_token", "token_type", "expires_in"] },
      "UserInput": { "type": "object", "description": "Activated organizations require their chosen participant identifier. externalId remains an independent integration key.", "properties": { "employeeId": { "type": "string", "maxLength": 128 }, "customId": { "type": "string", "maxLength": 128 }, "identifierType": { "type": "string", "enum": ["email", "employeeId", "customId"] }, "identifierValue": { "type": "string", "maxLength": 254 }, "externalId": { "type": "string" }, "displayName": { "type": "string", "minLength": 1, "maxLength": 200 }, "email": { "type": "string", "format": "email" } }, "required": ["displayName"] },
      "UserPatch": { "type": "object", "minProperties": 1, "properties": { "employeeId": { "type": "string", "maxLength": 128 }, "customId": { "type": "string", "maxLength": 128 }, "identifierType": { "type": "string", "enum": ["email", "employeeId", "customId"] }, "identifierValue": { "type": "string", "maxLength": 254 }, "externalId": { "type": "string" }, "displayName": { "type": "string", "minLength": 1 }, "email": { "type": ["string", "null"], "format": "email" } } },
      "User": { "type": "object", "properties": { "employeeId": { "type": "string", "maxLength": 128 }, "customId": { "type": "string", "maxLength": 128 }, "identifierType": { "type": "string", "enum": ["email", "employeeId", "customId"] }, "identifierValue": { "type": "string", "maxLength": 254 }, "id": { "type": "string" }, "externalId": { "type": ["string", "null"] }, "displayName": { "type": "string" }, "email": { "type": ["string", "null"] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" }, "anonymized": { "type": "boolean" } }, "required": ["id", "displayName", "createdAt", "updatedAt", "anonymized"] },
      "Course": { "type": "object", "properties": { "id": { "type": "string" }, "publishedVersionId": { "type": "string" }, "title": { "type": "string" }, "description": { "type": ["string", "null"] } }, "required": ["id", "publishedVersionId", "title"] },
      "EnrollmentInput": { "type": "object", "properties": { "userId": { "type": "string" }, "courseId": { "type": "string" }, "externalId": { "type": "string" } }, "required": ["userId", "courseId"] },
      "Enrollment": { "type": "object", "properties": { "id": { "type": "string" }, "userId": { "type": "string" }, "courseId": { "type": "string" }, "externalId": { "type": ["string", "null"] }, "status": { "type": "string", "enum": ["active", "completed", "cancelled"] }, "createdAt": { "type": "string", "format": "date-time" } }, "required": ["id", "userId", "courseId", "status", "createdAt"] },
      "Progress": { "type": "object", "properties": { "enrollmentId": { "type": "string" }, "state": { "type": "string", "enum": ["not_started", "in_progress", "completed"] }, "status": { "type": "string", "enum": ["active", "completed", "cancelled"] }, "startedAt": { "type": ["string", "null"], "format": "date-time" }, "completedAt": { "type": ["string", "null"], "format": "date-time" }, "percentComplete": { "type": "number", "minimum": 0, "maximum": 100 } }, "required": ["enrollmentId", "state", "status"] },
      "Completion": { "type": "object", "properties": { "id": { "type": "string" }, "enrollmentId": { "type": "string" }, "userId": { "type": "string" }, "courseId": { "type": "string" }, "completedAt": { "type": "string", "format": "date-time" } }, "required": ["id", "enrollmentId", "userId", "courseId", "completedAt"] },
      "Certificate": { "type": "object", "properties": { "id": { "type": "string" }, "completionId": { "type": "string" }, "userId": { "type": "string" }, "courseId": { "type": "string" }, "issuedAt": { "type": "string", "format": "date-time" }, "verificationUrl": { "type": ["string", "null"], "format": "uri" } }, "required": ["id", "completionId", "userId", "courseId", "issuedAt"] },
      "WebhookEvent": { "type": "string", "enum": ["enrollment.created", "enrollment.cancelled", "course.started", "course.completed", "certificate.issued"] },
      "WebhookInput": { "type": "object", "properties": { "url": { "type": "string", "format": "uri", "pattern": "^https://" }, "events": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookEvent" }, "minItems": 1, "uniqueItems": true } }, "required": ["url", "events"] },
      "Webhook": { "type": "object", "properties": { "id": { "type": "string" }, "url": { "type": "string", "format": "uri" }, "events": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookEvent" } }, "active": { "type": "boolean" }, "createdAt": { "type": "string", "format": "date-time" } }, "required": ["id", "url", "events", "active", "createdAt"] },
      "WebhookCreated": { "allOf": [{ "$ref": "#/components/schemas/Webhook" }, { "type": "object", "properties": { "secret": { "type": "string", "writeOnly": true, "description": "Shown once; used to validate X-Eduvo-Signature HMAC SHA-256 over the raw body." } }, "required": ["secret"] }] },
      "Error": { "type": "object", "properties": { "error": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" } }, "required": ["code", "message"] }, "requestId": { "type": "string" } }, "required": ["error", "requestId"] },
      "UserList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/User" } }, "nextCursor": { "type": ["string", "null"] } }, "required": ["data", "nextCursor"] },
      "CourseList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Course" } }, "nextCursor": { "type": ["string", "null"] } }, "required": ["data", "nextCursor"] },
      "EnrollmentList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Enrollment" } }, "nextCursor": { "type": ["string", "null"] } }, "required": ["data", "nextCursor"] },
      "CompletionList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Completion" } }, "nextCursor": { "type": ["string", "null"] } }, "required": ["data", "nextCursor"] },
      "CertificateList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Certificate" } }, "nextCursor": { "type": ["string", "null"] } }, "required": ["data", "nextCursor"] },
      "WebhookList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Webhook" } }, "nextCursor": { "type": ["string", "null"] } }, "required": ["data", "nextCursor"] }
    }
  }
}
