The service needs KEZA’s programme catalogue. This API does not use a login: KEZA gave Irembo a fixed API key that goes in the X-API-Key header. New idea: auth type TOKEN.
KEZA API guide · the slice you need
GET /v1/programs
X-API-Key: <key issued by KEZA>
200 { "programs": [ { "code", "title", "durationWeeks", "fee": { "amount", "currency" },
"minAge", "maxAge", "active" } ] }
401 { "code": "INVALID_API_KEY" }
1 · Request: GET /victor/keza/v1/programs
2 · Responses (the first matching rule wins)
Rule 1 · Valid API key
| Match on | Key | Operator | Value | |
|---|---|---|---|---|
| If | Header | X-API-Key | equals | keza-api-key-victor-2026 |
Respond with 200 Body type json
{
"programs": [
{
"code": "DATA_ANALYTICS",
"title": "Data Analytics Foundations",
"durationWeeks": 12,
"fee": {
"amount": 0,
"currency": "RWF"
},
"minAge": 18,
"maxAge": 35,
"active": true
},
{
"code": "WEB_BASICS",
"title": "Web Development Basics",
"durationWeeks": 8,
"fee": {
"amount": 15000,
"currency": "RWF"
},
"minAge": 16,
"maxAge": 40,
"active": true
},
{
"code": "CLOUD_OPS",
"title": "Cloud Operations",
"durationWeeks": 16,
"fee": {
"amount": 30000,
"currency": "RWF"
},
"minAge": 20,
"maxAge": 45,
"active": false
}
]
}
Otherwise — when no rule above matches
Respond with 401 Body type json
{
"code": "INVALID_API_KEY",
"message": "X-API-Key header is missing or wrong."
}
3 · Label
| Field | Value |
|---|---|
| Name | Victor KEZA Programs |
| Code | VICTOR_KEZA_PROGRAMS |
| Service | KEZA |
| Category | victor-course |
| Active | ✔ served by the mock server right away |
| Field | Value | |
|---|---|---|
| **Endpoint adapter *** | FMAPPING (fetch) |
locked after save |
| **Endpoint code *** | VICTOR_KEZA_PROGRAMS_FETCH |
locked after save |
| **Endpoint url *** | http://mock-service-engine.apps:8000/victor/keza/v1/programs |
|
| **Endpoint type *** | FETCH (set by the adapter) |
|
| **Endpoint name *** | Victor KEZA Programmes |
|
| **Partner *** | KEZA |
locked after save |
| Active | ON |
|
| **Endpoint description *** | Victor course: list KEZA programmes (API key header, TOKEN auth). |
**Endpoint Mapping ***
{
"method": "GET",
"auth": {
"type": "TOKEN",
"parameters": { "token": "keza-api-key-victor-2026" },
"tokenConfig": { "headerName": "X-API-Key", "tokenPrefix": "" }
}
}
X-API-Key: keza-api-key-victor-2026. For a bearer key, use "tokenPrefix": "Bearer".Secrets · training literal. Hard-coded only because KEZA is a training mock. In a real integration use the environment variable instead: keza-api-key-victor-2026 → "${KEZA_API_KEY}" (the whole quoted string, nothing before or after; see Lesson 3.4).
Secrets · training value, not production practice
Clear-text value in this lesson: the API key
keza-api-key-victor-2026. The value is hard-coded only because this is a training sandbox with a fake partner. In real integrations, keep secrets out of the mapping: use an environment variable that the platform team defines per environment.Proper configuration:
"parameters": { "token": "${KEZA_API_KEY}" }The format must be exact: the whole JSON string is
"${NAME}"(quotes included, nothing before or after)."Bearer ${NAME}","$NAME"and"{{NAME}}"are not resolved: they are sent as plain text. An unquoted$NAMEor$env.NAMEis an undefined JSONata variable, so the key is dropped from the request. A variable that is not defined on the environment stops every call with 500 MISSING_ENVIRONMENT_VARIABLE (Lesson 6.3 shows it in Kibana).
HTTP 200 0.62 s captured on staging · 26 Sep 2026
1 · Request (curl)
curl -s -X POST "https://integration-gateway.staging.iremboinc.com/integration/v1/fetch/sync" \
-H "Authorization: Bearer $TOKEN" \
-H "portal: integration-management-portal" \
-H "Content-Type: application/json" \
-d '{
"endpointCode": "VICTOR_KEZA_PROGRAMS_FETCH",
"callerId": "victor-course",
"requester": "victor-course",
"payload": {}
}' | jq
2 · Real response
{
"status": true,
"responseCode": "integration_870000",
"responseMessage": "integration_870000",
"data": {
"id": "b2ee94ef-b471-4131-9f29-ddc2f0f8b8e3",
"endpointCode": "VICTOR_KEZA_PROGRAMS_FETCH",
"callerId": "victor-course",
"response": "{\n \"programs\": [\n {\n \"code\": \"DATA_ANALYTICS\",\n \"title\": \"Data Analytics Foundations\",\n \"durationWeeks\": 12,\n \"fee\": {\n \"amount\": 0,\n \"currency\": \"RWF\"\n },\n \"minAge\": 18,\n \"maxAge\": 35,\n \"active\": true\n },\n {\n \"code\": \"WEB_BASICS\",\n \"title\": \"Web Development Basics\",\n \"durationWeeks\": 8,\n \"fee\": {\n \"amount\": 15000,\n \"currency\": \"RWF\"\n },\n \"minAge\": 16,\n \"maxAge\": 40,\n \"active\": true\n },\n {\n \"code\": \"CLOUD_OPS\",\n \"title\": \"Cloud Operations\",\n \"durationWeeks\": 16,\n \"fee\": {\n \"amount\": 30000,\n \"currency\": \"RWF\"\n },\n \"minAge\": 20,\n \"maxAge\": 45,\n \"active\": false\n }\n ]\n}",
"adapterCode": "FMAPPING"
},
"timestamp": "2026-09-26T09:48:12.06889056",
"traceId": "6ab778bb12c1339f7fb737b2126585dd"
}
3 · Decoded data.response
{
"programs": [
{
"code": "DATA_ANALYTICS",
"title": "Data Analytics Foundations",
"durationWeeks": 12,
"fee": {
"amount": 0,
"currency": "RWF"
},
"minAge": 18,
"maxAge": 35,
"active": true
},
{
"code": "WEB_BASICS",
"title": "Web Development Basics",
"durationWeeks": 8,
"fee": {
"amount": 15000,
"currency": "RWF"
},
"minAge": 16,
"maxAge": 40,
"active": true
},
{
"code": "CLOUD_OPS",
"title": "Cloud Operations",
"durationWeeks": 16,
"fee": {
"amount": 30000,
"currency": "RWF"
},
"minAge": 20,
"maxAge": 45,
"active": false
}
]
}
4 · What to notice
{}): a catalogue needs no input.**Gotcha · Secrets in the portal show as *****
When you open this mapping in the portal,
token,password,client_secretand similar keys insideauthshow as***. Leave them as***when you save and the engine keeps the stored value. Secrets placed outside auth (for example inheaders) are not hidden: always use the auth block. Hiding is not protection: anyone with the right grant can still change the value, and the mapping test prints it. That is why real secrets belong in environment variables such as"${KEZA_API_KEY}".
A partner wants Authorization: ApiKey 7Hf2…. Which tokenConfig is right?
C. The header value is tokenPrefix + space + token. A headers entry with the full value ApiKey 7Hf2… would also reach the partner, but it is not redacted, and the last option also lacks the ApiKey prefix.
Your turn · change one thing
Change your programmes mock to expect
Authorization: ApiKey ${your_training_key}(pick any training key, e.g.aline-key-2026) and update your endpoint${YOUR_FIRST_NAME}_KEZA_PROGRAMS_FETCHuntil the call returns 200. Replace the variables before saving, and write in your evidence pack which environment variable you would use in production.
← 2.3 Push a JSON body: enroll the learner · 3.2 Basic auth (BASIC): certificate check →