{"openapi": "3.1.0", "info": {"title": "Ubunya API", "version": "1.0.0", "description": "Ubunya public API, version 1.\n\nAuthenticate with `Authorization: Bearer <key>`: an API key from Manage \u2192 Integrations (ubk_\u2026) or an\nOAuth access token. Every call works on the organisation the credential belongs to. Lists are newest\nlast and paginated with `after` (the last id you saw) and `limit`; the response's `next` is the value\nfor the following page, or null at the end.\n"}, "paths": {"/api/v1/organization": {"get": {"operationId": "publicapi_api_organization", "summary": "The organisation this credential belongs to", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "tags": ["Organisation"], "security": [{"ApiAuth": []}]}}, "/api/v1/members": {"get": {"operationId": "publicapi_api_members", "summary": "People in the community", "parameters": [{"in": "query", "name": "after", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "After"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "joined_since", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Joined Since"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Page"}}}}}, "tags": ["Members"], "security": [{"ApiAuth": []}]}}, "/api/v1/members/{user_id}": {"get": {"operationId": "publicapi_api_member", "summary": "One person, with their intakes", "parameters": [{"in": "path", "name": "user_id", "schema": {"title": "User Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "tags": ["Members"], "security": [{"ApiAuth": []}]}}, "/api/v1/programmes": {"get": {"operationId": "publicapi_api_programme_list", "summary": "Programmes", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"additionalProperties": true, "type": "object"}, "title": "Response", "type": "array"}}}}}, "tags": ["Programmes and intakes"], "security": [{"ApiAuth": []}]}}, "/api/v1/intakes": {"get": {"operationId": "publicapi_api_intake_list", "summary": "Intakes, optionally for one programme", "parameters": [{"in": "query", "name": "programme_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Programme Id"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"additionalProperties": true, "type": "object"}, "title": "Response", "type": "array"}}}}}, "tags": ["Programmes and intakes"], "security": [{"ApiAuth": []}]}}, "/api/v1/intakes/{intake_id}/enrollments": {"get": {"operationId": "publicapi_api_intake_enrollments", "summary": "Who is in an intake", "parameters": [{"in": "path", "name": "intake_id", "schema": {"title": "Intake Id", "type": "integer"}, "required": true}, {"in": "query", "name": "after", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "After"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "status", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Status"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Page"}}}}}, "tags": ["Programmes and intakes"], "security": [{"ApiAuth": []}]}, "post": {"operationId": "publicapi_api_enroll", "summary": "Add someone to an intake (enrols members; invites anyone else by email)", "parameters": [{"in": "path", "name": "intake_id", "schema": {"title": "Intake Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "tags": ["Programmes and intakes"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/EnrollIn"}}}, "required": true}, "security": [{"ApiAuth": []}]}}, "/api/v1/enrollments/{enrollment_id}": {"patch": {"operationId": "publicapi_api_enrollment_status", "summary": "Mark an enrolment completed, withdrawn or active", "parameters": [{"in": "path", "name": "enrollment_id", "schema": {"title": "Enrollment Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "tags": ["Programmes and intakes"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusIn"}}}, "required": true}, "security": [{"ApiAuth": []}]}}, "/api/v1/intakes/{intake_id}/enrollments/import": {"post": {"operationId": "publicapi_api_enroll_many", "summary": "Add many people to an intake by email (review first)", "parameters": [{"in": "path", "name": "intake_id", "schema": {"title": "Intake Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "description": "Members are enrolled; anyone else gets an email invitation. Duplicates and bad addresses are skipped.", "tags": ["Programmes and intakes"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImportIn"}}}, "required": true}, "security": [{"ApiAuth": []}]}}, "/api/v1/applications": {"get": {"operationId": "publicapi_api_application_list", "summary": "Applications", "parameters": [{"in": "query", "name": "after", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "After"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "status", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Status"}, "required": false}, {"in": "query", "name": "intake_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Intake Id"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Page"}}}}}, "tags": ["Applications"], "security": [{"ApiAuth": []}]}}, "/api/v1/applications/{application_id}/decision": {"post": {"operationId": "publicapi_api_decide", "summary": "Accept, waitlist or turn down an application", "parameters": [{"in": "path", "name": "application_id", "schema": {"title": "Application Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "tags": ["Applications"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DecisionIn"}}}, "required": true}, "security": [{"ApiAuth": []}]}}, "/api/v1/sessions": {"get": {"operationId": "publicapi_api_session_list", "summary": "Sessions on the calendar", "parameters": [{"in": "query", "name": "after", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "After"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "starts_after", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Starts After"}, "required": false}, {"in": "query", "name": "starts_before", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Starts Before"}, "required": false}, {"in": "query", "name": "intake_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Intake Id"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Page"}}}}}, "tags": ["Sessions and attendance"], "security": [{"ApiAuth": []}]}}, "/api/v1/sessions/{session_id}/attendance": {"get": {"operationId": "publicapi_api_attendance", "summary": "Attendance marks for a session", "parameters": [{"in": "path", "name": "session_id", "schema": {"title": "Session Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "tags": ["Sessions and attendance"], "security": [{"ApiAuth": []}]}}, "/api/v1/payments": {"get": {"operationId": "publicapi_api_payment_list", "summary": "Fee payments", "parameters": [{"in": "query", "name": "after", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "After"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "paid_since", "schema": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Paid Since"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Page"}}}}}, "tags": ["Fees and payments"], "security": [{"ApiAuth": []}]}, "post": {"operationId": "publicapi_api_record_payment", "summary": "Record a payment (for example from your M-Pesa till)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "tags": ["Fees and payments"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaymentIn"}}}, "required": true}, "security": [{"ApiAuth": []}]}}, "/api/v1/payments/balances": {"get": {"operationId": "publicapi_api_balances", "summary": "What each learner owes", "parameters": [{"in": "query", "name": "after", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "After"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "owing", "schema": {"default": false, "title": "Owing", "type": "boolean"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Page"}}}}}, "tags": ["Fees and payments"], "security": [{"ApiAuth": []}]}}, "/api/v1/certificates": {"get": {"operationId": "publicapi_api_certificate_list", "summary": "Certificates issued", "parameters": [{"in": "query", "name": "after", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "After"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Page"}}}}}, "tags": ["Certificates"], "security": [{"ApiAuth": []}]}}, "/api/v1/hooks/events": {"get": {"operationId": "publicapi_api_hook_events", "summary": "Events you can subscribe to", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"additionalProperties": true, "type": "object"}, "title": "Response", "type": "array"}}}}}, "tags": ["Webhook subscriptions"], "security": [{"ApiAuth": []}]}}, "/api/v1/hooks": {"post": {"operationId": "publicapi_api_subscribe", "summary": "Subscribe a URL to one event (Zapier REST hooks)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "tags": ["Webhook subscriptions"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HookIn"}}}, "required": true}, "security": [{"ApiAuth": []}]}}, "/api/v1/hooks/{hook_id}": {"delete": {"operationId": "publicapi_api_unsubscribe", "summary": "Unsubscribe", "parameters": [{"in": "path", "name": "hook_id", "schema": {"title": "Hook Id", "type": "integer"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "tags": ["Webhook subscriptions"], "security": [{"ApiAuth": []}]}}, "/api/v1/hooks/samples/{event}": {"get": {"operationId": "publicapi_api_samples", "summary": "Example payloads for an event (recent real ones if any)", "parameters": [{"in": "path", "name": "event", "schema": {"title": "Event", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"additionalProperties": true, "type": "object"}, "title": "Response", "type": "array"}}}}}, "tags": ["Webhook subscriptions"], "security": [{"ApiAuth": []}]}}, "/api/v1/intakes/{intake_id}/squads": {"get": {"operationId": "publicapi_api_squad_standings", "summary": "Squad standings for an intake", "parameters": [{"in": "path", "name": "intake_id", "schema": {"title": "Intake Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"additionalProperties": true, "type": "object"}, "title": "Response", "type": "array"}}}}}, "tags": ["Squads"], "security": [{"ApiAuth": []}]}}, "/api/v1/intakes/{intake_id}/squads/split": {"post": {"operationId": "publicapi_api_squad_split", "summary": "Split an intake into balanced squads (review first)", "parameters": [{"in": "path", "name": "intake_id", "schema": {"title": "Intake Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "description": "Squads stay the same size, class times stay together where they can, colleagues from the same paying\ncompany are spread out. If the intake already has squads, count must match them and everyone is reshuffled.", "tags": ["Squads"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SplitIn"}}}, "required": true}, "security": [{"ApiAuth": []}]}}, "/api/v1/intakes/{intake_id}/squads/message": {"post": {"operationId": "publicapi_api_squad_message", "summary": "Message some or all squads, or only captains (review first)", "parameters": [{"in": "path", "name": "intake_id", "schema": {"title": "Intake Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "tags": ["Squads"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SquadMessageIn"}}}, "required": true}, "security": [{"ApiAuth": []}]}}}, "components": {"schemas": {"Page": {"properties": {"data": {"items": {"additionalProperties": true, "type": "object"}, "title": "Data", "type": "array"}, "next": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Next"}}, "required": ["data", "next"], "title": "Page", "type": "object"}, "EnrollIn": {"properties": {"email": {"maxLength": 254, "title": "Email", "type": "string"}, "slot_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Slot Id"}}, "required": ["email"], "title": "EnrollIn", "type": "object"}, "StatusIn": {"properties": {"status": {"title": "Status", "type": "string"}}, "required": ["status"], "title": "StatusIn", "type": "object"}, "ImportIn": {"properties": {"rows": {"items": {"$ref": "#/components/schemas/ImportRow"}, "maxItems": 2000, "title": "Rows", "type": "array"}, "confirm_token": {"default": "", "title": "Confirm Token", "type": "string"}}, "required": ["rows"], "title": "ImportIn", "type": "object"}, "ImportRow": {"properties": {"email": {"title": "Email", "type": "string"}, "slot_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Slot Id"}}, "required": ["email"], "title": "ImportRow", "type": "object"}, "DecisionIn": {"properties": {"decision": {"description": "accept, waitlist or reject", "title": "Decision", "type": "string"}, "message": {"default": "", "description": "Sent to the applicant.", "maxLength": 2000, "title": "Message", "type": "string"}}, "required": ["decision"], "title": "DecisionIn", "type": "object"}, "PaymentIn": {"properties": {"enrollment_id": {"title": "Enrollment Id", "type": "integer"}, "amount": {"description": "e.g. \"15000\" or \"15000.50\"", "title": "Amount", "type": "string"}, "method": {"description": "mpesa, bank, cash, cheque, card or other", "title": "Method", "type": "string"}, "reference": {"default": "", "maxLength": 80, "title": "Reference", "type": "string"}, "paid_on": {"format": "date", "title": "Paid On", "type": "string"}}, "required": ["enrollment_id", "amount", "method", "paid_on"], "title": "PaymentIn", "type": "object"}, "HookIn": {"properties": {"target_url": {"maxLength": 500, "title": "Target Url", "type": "string"}, "event": {"title": "Event", "type": "string"}}, "required": ["target_url", "event"], "title": "HookIn", "type": "object"}, "SplitIn": {"properties": {"count": {"maximum": 50, "minimum": 1, "title": "Count", "type": "integer"}, "confirm_token": {"default": "", "title": "Confirm Token", "type": "string"}}, "required": ["count"], "title": "SplitIn", "type": "object"}, "SquadMessageIn": {"properties": {"body": {"maxLength": 4000, "minLength": 1, "title": "Body", "type": "string"}, "squad_ids": {"default": [], "items": {"type": "integer"}, "title": "Squad Ids", "type": "array"}, "captains_only": {"default": false, "title": "Captains Only", "type": "boolean"}, "confirm_token": {"default": "", "title": "Confirm Token", "type": "string"}}, "required": ["body"], "title": "SquadMessageIn", "type": "object"}}, "securitySchemes": {"ApiAuth": {"type": "http", "scheme": "bearer"}}}, "servers": []}