The following OpenAPI description is the machine-readable KEZA API contract.
openapi: 3.2.0
info:
title: KEZA API
version: 1.0.0
description: |
KEZA evaluates learner eligibility and receives approved enrollments
from authorized partner systems.
contact:
name: KEZA API Support
license:
name: Proprietary
identifier: LicenseRef-Proprietary
servers:
- url: "{BASE_URL}"
description: Environment-specific URL supplied during partner onboarding.
variables:
BASE_URL:
default: https://api.keza.example
description: Replace with the KEZA base URL for the target environment.
tags:
- name: Authorization
description: System-to-system access tokens.
- name: Eligibility
description: Learner eligibility evaluation.
- name: Enrollments
description: Enrollment creation and reconciliation.
paths:
/oauth2/token:
post:
tags:
- Authorization
summary: Create an access token
operationId: createAccessToken
security:
- clientBasic: []
parameters:
- $ref: "#/components/parameters/CorrelationId"
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/AccessTokenRequest"
examples:
eligibilityRead:
summary: Request eligibility access
value:
grant_type: client_credentials
scope: eligibility:read
responses:
"200":
description: Access token issued.
headers:
Cache-Control:
description: Prevents token response storage.
schema:
type: string
const: no-store
Pragma:
description: Prevents token response caching by HTTP/1.0 clients.
schema:
type: string
const: no-cache
X-Correlation-Id:
$ref: "#/components/headers/CorrelationId"
X-KEZA-Request-Id:
$ref: "#/components/headers/KezaRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/AccessTokenResponse"
examples:
issued:
value:
access_token: "<access-token>"
token_type: Bearer
expires_in: 300
scope: eligibility:read
"400":
description: Invalid token request, grant type, or scope.
content:
application/json:
schema:
$ref: "#/components/schemas/OAuthError"
examples:
invalidScope:
value:
error: invalid_scope
error_description: One or more requested scopes are unavailable.
"401":
description: Invalid client credentials.
headers:
WWW-Authenticate:
schema:
type: string
example: 'Basic realm="keza-token"'
content:
application/json:
schema:
$ref: "#/components/schemas/OAuthError"
examples:
invalidClient:
value:
error: invalid_client
error_description: The client credentials are invalid.
"429":
$ref: "#/components/responses/OAuthRateLimited"
"503":
$ref: "#/components/responses/OAuthUnavailable"
/v1/eligibility:
get:
tags:
- Eligibility
summary: Check learner eligibility
description: |
Evaluates whether a recognized learner meets the requirements for
the selected program. An unrecognized learner is returned as a valid
200 business result with found set to false.
operationId: getEligibility
security:
- kezaOAuth:
- eligibility:read
parameters:
- $ref: "#/components/parameters/CorrelationId"
- name: nationalId
in: query
required: true
description: Learner National ID.
schema:
type: string
pattern: '^[0-9]{16}$'
example: "1000000000000001"
- name: program
in: query
required: true
description: Program to evaluate.
schema:
$ref: "#/components/schemas/ProgramCode"
responses:
"200":
description: Eligibility evaluation completed.
headers:
X-Correlation-Id:
$ref: "#/components/headers/CorrelationId"
X-KEZA-Request-Id:
$ref: "#/components/headers/KezaRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/EligibilityResponse"
examples:
eligible:
$ref: "#/components/examples/EligibleResponse"
ineligible:
$ref: "#/components/examples/IneligibleResponse"
learnerNotFound:
$ref: "#/components/examples/LearnerNotFoundResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"429":
$ref: "#/components/responses/RateLimited"
"500":
$ref: "#/components/responses/InternalError"
"502":
$ref: "#/components/responses/BadGateway"
"503":
$ref: "#/components/responses/Unavailable"
"504":
$ref: "#/components/responses/GatewayTimeout"
/v1/enrollments:
post:
tags:
- Enrollments
summary: Create an enrollment
description: Registers an approved learner application in KEZA.
operationId: createEnrollment
security:
- kezaOAuth:
- enrollments:write
parameters:
- $ref: "#/components/parameters/CorrelationId"
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EnrollmentRequest"
examples:
approvedEnrollment:
$ref: "#/components/examples/EnrollmentRequest"
responses:
"201":
description: A new enrollment was recorded.
headers:
Location:
description: URI of the created enrollment.
required: true
schema:
type: string
example: /v1/enrollments/KEZA-REG-0042
X-Correlation-Id:
$ref: "#/components/headers/CorrelationId"
X-KEZA-Request-Id:
$ref: "#/components/headers/KezaRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/EnrollmentResponse"
examples:
created:
$ref: "#/components/examples/EnrollmentCreated"
"200":
description: The same idempotent request was already recorded.
headers:
X-Correlation-Id:
$ref: "#/components/headers/CorrelationId"
X-KEZA-Request-Id:
$ref: "#/components/headers/KezaRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/EnrollmentResponse"
examples:
alreadyReceived:
$ref: "#/components/examples/EnrollmentAlreadyReceived"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
description: The idempotency key was used with different business data.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
idempotencyConflict:
value:
type: https://developer.keza.example/problems/idempotency-conflict
title: Idempotency conflict
status: 409
detail: The idempotency key was already used with different enrollment data.
instance: urn:keza:request:kza-req-01J7A6HVV9P7A8N2P1T7H0Y4Z6
code: IDEMPOTENCY_CONFLICT
correlationId: 742f4fca-8d85-4daa-b583-4abdfdb593ad
errors: []
"415":
description: Request body is not JSON.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
description: A business rule rejected the enrollment.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
learnerNotEligible:
value:
type: https://developer.keza.example/problems/enrollment-rejected
title: Enrollment rejected
status: 422
detail: The application cannot be registered.
instance: urn:keza:request:kza-req-01J7A6Q7W3D6X7K9M0R2F5S8T1
code: LEARNER_NOT_ELIGIBLE
correlationId: 68da5bdf-1af7-4596-a56b-2478df35a5ea
errors:
- pointer: "#/learner/nationalId"
code: LEARNER_NOT_ELIGIBLE
detail: The learner is not eligible for DIGITAL_SKILLS.
"429":
$ref: "#/components/responses/RateLimited"
"500":
$ref: "#/components/responses/InternalError"
"503":
$ref: "#/components/responses/Unavailable"
/v1/enrollments/by-application-reference/{applicationReference}:
get:
tags:
- Enrollments
summary: Retrieve an enrollment by application reference
description: Retrieves an enrollment for reconciliation after an uncertain create outcome.
operationId: getEnrollmentByApplicationReference
security:
- kezaOAuth:
- enrollments:read
parameters:
- $ref: "#/components/parameters/CorrelationId"
- name: applicationReference
in: path
required: true
description: Client reference supplied during enrollment creation.
schema:
$ref: "#/components/schemas/ApplicationReference"
responses:
"200":
description: Enrollment found.
headers:
X-Correlation-Id:
$ref: "#/components/headers/CorrelationId"
X-KEZA-Request-Id:
$ref: "#/components/headers/KezaRequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/EnrollmentLookupResponse"
examples:
found:
value:
found: true
applicationReference: APP-2026-0001
partnerReference: KEZA-REG-0042
status: RECEIVED
receivedAt: "2026-09-03T08:45:01Z"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
description: No enrollment exists for the application reference.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
examples:
notFound:
value:
type: https://developer.keza.example/problems/enrollment-not-found
title: Enrollment not found
status: 404
detail: No enrollment exists for application reference APP-2026-0001.
instance: urn:keza:request:kza-req-01J7A7A4F1B3D5G8H2J4K6M9N0
code: ENROLLMENT_NOT_FOUND
correlationId: 1042f1c7-e8f0-4a27-9830-2995b92a7af7
errors: []
"429":
$ref: "#/components/responses/RateLimited"
"500":
$ref: "#/components/responses/InternalError"
"503":
$ref: "#/components/responses/Unavailable"
components:
securitySchemes:
clientBasic:
type: http
scheme: basic
description: Client ID and client secret issued by KEZA.
kezaOAuth:
type: oauth2
description: OAuth 2.0 Client Credentials.
flows:
clientCredentials:
tokenUrl: /oauth2/token
scopes:
eligibility:read: Check learner eligibility.
enrollments:write: Create enrollments.
enrollments:read: Retrieve enrollments for reconciliation.
parameters:
CorrelationId:
name: X-Correlation-Id
in: header
required: false
description: Client-generated UUID used for end-to-end tracing.
schema:
type: string
format: uuid
example: acb33439-a673-416e-9b5c-45838637d6b4
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
description: Stable key for one logical enrollment. Must equal applicationReference.
schema:
$ref: "#/components/schemas/ApplicationReference"
example: APP-2026-0001
headers:
CorrelationId:
description: Client correlation ID or a KEZA-generated UUID.
required: true
schema:
type: string
format: uuid
KezaRequestId:
description: Unique identifier assigned by KEZA to the HTTP request.
required: true
schema:
type: string
pattern: '^kza-req-[A-Z0-9]+$'
RetryAfter:
description: Minimum number of seconds to wait before retrying.
required: true
schema:
type: integer
minimum: 1
schemas:
AccessTokenRequest:
type: object
required:
- grant_type
- scope
properties:
grant_type:
type: string
const: client_credentials
scope:
type: string
minLength: 1
examples:
- eligibility:read
- enrollments:write enrollments:read
additionalProperties: false
AccessTokenResponse:
type: object
required:
- access_token
- token_type
- expires_in
- scope
properties:
access_token:
type: string
minLength: 1
token_type:
type: string
const: Bearer
expires_in:
type: integer
minimum: 1
example: 300
scope:
type: string
minLength: 1
additionalProperties: false
OAuthError:
type: object
required:
- error
properties:
error:
type: string
enum:
- invalid_request
- unsupported_grant_type
- invalid_scope
- invalid_client
- temporarily_unavailable
error_description:
type: string
additionalProperties: false
ProgramCode:
type: string
enum:
- DIGITAL_SKILLS
- DATA_FOUNDATIONS
ApplicationReference:
type: string
pattern: '^[A-Za-z0-9_-]{1,50}$'
minLength: 1
maxLength: 50
LearnerIdentity:
type: object
required:
- nationalId
- name
properties:
nationalId:
type: string
pattern: '^[0-9]{16}$'
name:
type: string
minLength: 1
maxLength: 100
additionalProperties: false
EnrollmentLearner:
type: object
required:
- nationalId
- name
properties:
nationalId:
type: string
pattern: '^[0-9]{16}$'
name:
type: string
minLength: 1
maxLength: 100
email:
type: string
format: email
maxLength: 254
phoneNumber:
type: string
pattern: '^\+[1-9][0-9]{7,14}$'
anyOf:
- required:
- email
- required:
- phoneNumber
additionalProperties: false
Eligibility:
type: object
required:
- status
- reasonCode
- reason
- programCode
- evaluatedAt
properties:
status:
type: string
enum:
- Eligible
- Ineligible
reasonCode:
type:
- string
- "null"
reason:
type:
- string
- "null"
programCode:
$ref: "#/components/schemas/ProgramCode"
evaluatedAt:
type: string
format: date-time
additionalProperties: false
EligibilityFoundResponse:
type: object
required:
- found
- student
- eligibility
properties:
found:
type: boolean
const: true
student:
$ref: "#/components/schemas/LearnerIdentity"
eligibility:
$ref: "#/components/schemas/Eligibility"
additionalProperties: false
EligibilityNotFoundResponse:
type: object
required:
- found
- code
- message
properties:
found:
type: boolean
const: false
code:
type: string
const: LEARNER_NOT_FOUND
message:
type: string
additionalProperties: false
EligibilityResponse:
oneOf:
- $ref: "#/components/schemas/EligibilityFoundResponse"
- $ref: "#/components/schemas/EligibilityNotFoundResponse"
EnrollmentRequest:
type: object
required:
- applicationReference
- learner
- programCode
- decision
- approvedAt
properties:
applicationReference:
$ref: "#/components/schemas/ApplicationReference"
learner:
$ref: "#/components/schemas/EnrollmentLearner"
programCode:
$ref: "#/components/schemas/ProgramCode"
decision:
type: string
const: APPROVED
approvedAt:
type: string
format: date-time
description: UTC approval time, not in the future and no more than 30 days old.
additionalProperties: false
EnrollmentResponse:
type: object
required:
- accepted
- applicationReference
- partnerReference
- status
- receivedAt
properties:
accepted:
type: boolean
const: true
applicationReference:
$ref: "#/components/schemas/ApplicationReference"
partnerReference:
type: string
pattern: '^KEZA-REG-[0-9]+$'
status:
type: string
enum:
- RECEIVED
- ALREADY_RECEIVED
receivedAt:
type: string
format: date-time
additionalProperties: false
EnrollmentLookupResponse:
type: object
required:
- found
- applicationReference
- partnerReference
- status
- receivedAt
properties:
found:
type: boolean
const: true
applicationReference:
$ref: "#/components/schemas/ApplicationReference"
partnerReference:
type: string
pattern: '^KEZA-REG-[0-9]+$'
status:
type: string
enum:
- RECEIVED
receivedAt:
type: string
format: date-time
additionalProperties: false
ProblemItem:
type: object
required:
- code
- detail
properties:
location:
type: string
enum:
- header
- path
- query
- body
parameter:
type: string
pointer:
type: string
description: JSON Pointer to a request-body property.
code:
type: string
detail:
type: string
additionalProperties: false
ProblemDetails:
type: object
required:
- type
- title
- status
- detail
- instance
- code
- correlationId
- errors
properties:
type:
type: string
format: uri
title:
type: string
status:
type: integer
minimum: 400
maximum: 599
detail:
type: string
instance:
type: string
format: uri-reference
code:
type: string
correlationId:
type: string
format: uuid
errors:
type: array
items:
$ref: "#/components/schemas/ProblemItem"
examples:
EligibleResponse:
summary: Learner is eligible
value:
found: true
student:
nationalId: "1000000000000001"
name: Keza Uwase
eligibility:
status: Eligible
reasonCode: null
reason: null
programCode: DIGITAL_SKILLS
evaluatedAt: "2026-09-03T08:30:00Z"
IneligibleResponse:
summary: Learner is not eligible
value:
found: true
student:
nationalId: "1000000000000002"
name: Mugisha Amani
eligibility:
status: Ineligible
reasonCode: PREREQUISITE_NOT_MET
reason: The learner has not completed the required foundation module.
programCode: DIGITAL_SKILLS
evaluatedAt: "2026-09-03T08:31:00Z"
LearnerNotFoundResponse:
summary: Learner was not found
value:
found: false
code: LEARNER_NOT_FOUND
message: No learner was found for the supplied National ID and program.
EnrollmentRequest:
summary: Approved enrollment
value:
applicationReference: APP-2026-0001
learner:
nationalId: "1000000000000001"
name: Keza Uwase
email: keza.uwase@example.test
phoneNumber: "+250780000001"
programCode: DIGITAL_SKILLS
decision: APPROVED
approvedAt: "2026-09-03T08:45:00Z"
EnrollmentCreated:
summary: New enrollment
value:
accepted: true
applicationReference: APP-2026-0001
partnerReference: KEZA-REG-0042
status: RECEIVED
receivedAt: "2026-09-03T08:45:01Z"
EnrollmentAlreadyReceived:
summary: Idempotent repeat
value:
accepted: true
applicationReference: APP-2026-0001
partnerReference: KEZA-REG-0042
status: ALREADY_RECEIVED
receivedAt: "2026-09-03T08:45:01Z"
responses:
BadRequest:
description: Request validation failed.
headers:
X-Correlation-Id:
$ref: "#/components/headers/CorrelationId"
X-KEZA-Request-Id:
$ref: "#/components/headers/KezaRequestId"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
Unauthorized:
description: Bearer token is missing, expired, revoked, or invalid.
headers:
WWW-Authenticate:
schema:
type: string
example: 'Bearer realm="keza", error="invalid_token"'
X-Correlation-Id:
$ref: "#/components/headers/CorrelationId"
X-KEZA-Request-Id:
$ref: "#/components/headers/KezaRequestId"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
Forbidden:
description: Bearer token lacks the required scope.
headers:
WWW-Authenticate:
schema:
type: string
example: 'Bearer realm="keza", error="insufficient_scope"'
X-Correlation-Id:
$ref: "#/components/headers/CorrelationId"
X-KEZA-Request-Id:
$ref: "#/components/headers/KezaRequestId"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
RateLimited:
description: Client rate limit exceeded.
headers:
Retry-After:
$ref: "#/components/headers/RetryAfter"
X-Correlation-Id:
$ref: "#/components/headers/CorrelationId"
X-KEZA-Request-Id:
$ref: "#/components/headers/KezaRequestId"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
InternalError:
description: Unexpected KEZA failure.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
BadGateway:
description: A KEZA dependency returned an invalid response.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
Unavailable:
description: KEZA is temporarily unavailable.
headers:
Retry-After:
$ref: "#/components/headers/RetryAfter"
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
GatewayTimeout:
description: A KEZA dependency timed out.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
OAuthRateLimited:
description: Token request rate limit exceeded.
headers:
Retry-After:
$ref: "#/components/headers/RetryAfter"
content:
application/json:
schema:
$ref: "#/components/schemas/OAuthError"
examples:
rateLimited:
value:
error: temporarily_unavailable
error_description: Retry after the period in the Retry-After header.
OAuthUnavailable:
description: Authorization service is temporarily unavailable.
headers:
Retry-After:
$ref: "#/components/headers/RetryAfter"
content:
application/json:
schema:
$ref: "#/components/schemas/OAuthError"
examples:
unavailable:
value:
error: temporarily_unavailable
error_description: The authorization service is temporarily unavailable.