{"openapi":"3.1.0","info":{"title":"Gorgon Cyber Training partner API","version":"1.0.0","description":"Automates the provisioning of security training from an HR platform or workflow tool: joiners, role changes (movers), and departures (leavers), all keyed by employee email.\n\nJoiners and movers run the same AI role-matching pipeline as the console and dispatch automatically once a match exists, normally within a couple of minutes. These endpoints return `202` with a pending request; poll `GET /employees` for progress. Requests the pipeline cannot match confidently are parked for human review in the console rather than guessed at, and report as `pending` here.\n\nWrite operations are idempotent where retries are expected: repeating a joiner or mover while provisioning is in flight returns the in-flight request (`deduplicated: true`) rather than creating a duplicate, and offboarding an already-departed employee reports success.\n\n## Authentication\n\nEvery request carries an API key: `Authorization: Bearer gct_live_...`. Keys are created, rotated and revoked by a workspace administrator in the console under Settings, and require billing details on file. Creating the first key includes a one-time US$100 API setup fee, charged to the card on file; rotation and further keys are free. Rotation leaves the old key working for 24 hours.\n\n## Webhooks\n\nConfigure a webhook URL in the console (beside the keys) to receive signed events instead of polling: `request.dispatched`, `request.parked` (an automated request needs human review), and `training.completed` (with the certificate code). Events are signed Stripe-style: header `X-Training-Signature: t=<unix>,v1=<hex>` where v1 is HMAC-SHA256 of `\"<t>.<raw body>\"` with your whsec_ secret; reject anything older than your tolerance. Delivery is at-least-once with three attempts; poll `GET /employees` when you need certainty. Payload shapes are in this document’s `webhooks` section.\n\n## MCP\n\nThe same four operations are exposed as an MCP server (streamable HTTP) at `/partner/mcp`, authenticated with the same header. Tools: `provision_joiner`, `change_role`, `offboard_leaver`, `training_status`."},"servers":[{"url":"https://adaptive-training-api-i5is2ymdbq-ts.a.run.app/partner/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"A workspace API key, format gct_live_..."}},"responses":{"Unauthorized":{"description":"Missing, malformed, revoked or expired API key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable explanation."},"code":{"type":"string","description":"Machine-readable reason, when one exists."}}}}}},"NeedsBilling":{"description":"The workspace has no billing details on file. An administrator must add a payment method in the console.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable explanation."},"code":{"type":"string","description":"Machine-readable reason, when one exists."}}}}}}}},"webhooks":{"request.dispatched":{"post":{"summary":"Training was sent to an employee","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"evt_ id, stable across retries."},"type":{"type":"string","const":"request.dispatched"},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"requestId":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"classes":{"type":"integer"},"dueAt":{"type":"string","format":"date-time"}}}}}}}},"responses":{"200":{"description":"Return any 2xx to acknowledge."}}}},"request.parked":{"post":{"summary":"An automated request needs human review in the console","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","const":"request.parked"},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"requestId":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"reason":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Return any 2xx to acknowledge."}}}},"training.completed":{"post":{"summary":"An employee completed a class; a certificate was issued","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","const":"training.completed"},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"classId":{"type":"string"},"className":{"type":"string"},"completedAt":{"type":"string","format":"date-time"},"onTime":{"type":"boolean"},"certificateCode":{"type":"string"},"verifyUrl":{"type":"string","format":"uri"}}}}}}}},"responses":{"200":{"description":"Return any 2xx to acknowledge."}}}}},"paths":{"/joiners":{"post":{"operationId":"provisionJoiner","summary":"Provision training for a new employee","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"The employee's work email address. The identifier for every operation."},"name":{"type":"string","description":"Full name, as it should appear on certificates."},"role":{"type":"string","description":"Job title, for example \"Office administrator\"."},"roleDescription":{"type":"string","description":"What the person actually does, in a sentence or two. Optional: when omitted, a description is drafted from the role title during analysis. Supplying one improves matching."},"dueAt":{"type":"string","format":"date-time","description":"Completion deadline. Optional: defaults to the workspace's completion window."}},"required":["email","name","role"]}}}},"responses":{"200":{"description":"A provisioning request for this email is already in flight; returned instead of creating a duplicate.","content":{"application/json":{"schema":{"type":"object","properties":{"request":{"type":"object","description":"An in-flight or completed provisioning request.","properties":{"requestId":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"dueAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["pending","dispatched","cancelled"],"description":"pending: analysis or review is in progress and training has not been sent yet. dispatched: training has been sent to the employee. cancelled: withdrawn in the console."}}},"deduplicated":{"type":"boolean"}}}}}},"202":{"description":"Provisioning started. Poll GET /employees for progress.","content":{"application/json":{"schema":{"type":"object","properties":{"request":{"type":"object","description":"An in-flight or completed provisioning request.","properties":{"requestId":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"dueAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["pending","dispatched","cancelled"],"description":"pending: analysis or review is in progress and training has not been sent yet. dispatched: training has been sent to the employee. cancelled: withdrawn in the console."}}}}}}}},"400":{"description":"Missing or invalid fields.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable explanation."},"code":{"type":"string","description":"Machine-readable reason, when one exists."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/NeedsBilling"},"409":{"description":"This person already has active training (code: already_exists). Use /movers.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable explanation."},"code":{"type":"string","description":"Machine-readable reason, when one exists."}}}}}}}}},"/movers":{"post":{"operationId":"changeRole","summary":"Provision training for a role change","description":"The employee keeps everything completed; the new role adds what it needs. No additional seat is charged for an existing trainee.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"The employee's work email address. The identifier for every operation."},"name":{"type":"string","description":"Full name, as it should appear on certificates."},"role":{"type":"string","description":"Job title, for example \"Office administrator\"."},"roleDescription":{"type":"string","description":"What the person actually does, in a sentence or two. Optional: when omitted, a description is drafted from the role title during analysis. Supplying one improves matching."},"dueAt":{"type":"string","format":"date-time","description":"Completion deadline. Optional: defaults to the workspace's completion window."}},"required":["email","role"]}}}},"responses":{"200":{"description":"A request for this email is already in flight.","content":{"application/json":{"schema":{"type":"object","properties":{"request":{"type":"object","description":"An in-flight or completed provisioning request.","properties":{"requestId":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"dueAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["pending","dispatched","cancelled"],"description":"pending: analysis or review is in progress and training has not been sent yet. dispatched: training has been sent to the employee. cancelled: withdrawn in the console."}}},"deduplicated":{"type":"boolean"}}}}}},"202":{"description":"Provisioning started.","content":{"application/json":{"schema":{"type":"object","properties":{"request":{"type":"object","description":"An in-flight or completed provisioning request.","properties":{"requestId":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"dueAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["pending","dispatched","cancelled"],"description":"pending: analysis or review is in progress and training has not been sent yet. dispatched: training has been sent to the employee. cancelled: withdrawn in the console."}}}}}}}},"400":{"description":"Missing or invalid fields.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable explanation."},"code":{"type":"string","description":"Machine-readable reason, when one exists."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/NeedsBilling"},"404":{"description":"No active training record for this email (code: unknown_employee).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable explanation."},"code":{"type":"string","description":"Machine-readable reason, when one exists."}}}}}}}}},"/leavers":{"post":{"operationId":"offboardLeaver","summary":"Offboard a departed employee","description":"Revokes sign-in, cancels open training, keeps every completed record and certificate. Idempotent: offboarding the already-departed reports success.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"The employee's work email address. The identifier for every operation."}},"required":["email"]}}}},"responses":{"200":{"description":"Offboarded (or already was).","content":{"application/json":{"schema":{"type":"object","properties":{"departed":{"type":"boolean"},"alreadyDeparted":{"type":"boolean"},"cancelledEnrollments":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/NeedsBilling"},"404":{"description":"No training record for this email (code: unknown_employee).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable explanation."},"code":{"type":"string","description":"Machine-readable reason, when one exists."}}}}}}}}},"/employees":{"get":{"operationId":"trainingStatus","summary":"Read an employee's training status","parameters":[{"name":"email","in":"query","required":true,"schema":{"type":"string","format":"email"}}],"responses":{"200":{"description":"The training state for this email.","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["active","provisioning","departed"]},"training":{"type":"array","items":{"type":"object","properties":{"classId":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"dueAt":{"type":"string","format":"date-time"},"startedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"}}}},"pendingRequest":{"type":"object","description":"An in-flight or completed provisioning request.","properties":{"requestId":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"dueAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["pending","dispatched","cancelled"],"description":"pending: analysis or review is in progress and training has not been sent yet. dispatched: training has been sent to the employee. cancelled: withdrawn in the console."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/NeedsBilling"},"404":{"description":"No training record for this email (code: unknown_employee).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable explanation."},"code":{"type":"string","description":"Machine-readable reason, when one exists."}}}}}}}}}}}