After the applicant picks a district, the form shows the KEZA training centres there. KEZA wants a channel header, a request id header, and the district as an upper-case query parameter. Users type “ gasabo ”, so the value must be cleaned. New ideas: headers and JSONata functions.
KEZA API guide · the slice you need
GET /v1/centers?district=<DISTRICT_CODE>&program=<PROGRAM_CODE>
X-Channel: IREMBO (required)
X-Request-Id: <your correlation id>
200 { "district", "program", "centers": [ { "centerCode", "name", "seatsLeft", "sessionStart" } ] }
400 { "code": "MISSING_CHANNEL" } when X-Channel is missing
1 · Request: GET /victor/keza/v1/centers
2 · Responses (the first matching rule wins)
Rule 1 · Gasabo data-analytics centres
| Match on | Key | Operator | Value | |
|---|---|---|---|---|
| If | Header | X-Channel | equals | IREMBO |
| and | Query param | district | equals | GASABO |
| and | Query param | program | equals | DATA_ANALYTICS |
Respond with 200 Body type json
{
"district": "GASABO",
"program": "DATA_ANALYTICS",
"centers": [
{
"centerCode": "KZ-GSB-01",
"name": "Kacyiru Digital Hub",
"seatsLeft": 12,
"sessionStart": "2026-10-05"
},
{
"centerCode": "KZ-GSB-02",
"name": "Kimironko Tech Lab",
"seatsLeft": 0,
"sessionStart": "2026-10-12"
}
]
}
Rule 2 · Any other district
| Match on | Key | Operator | Value | |
|---|---|---|---|---|
| If | Header | X-Channel | equals | IREMBO |
Respond with 200 Body type jsonDynamic response ON
{
"district": "{{request.query.district}}",
"program": "{{request.query.program}}",
"centers": []
}
Otherwise — when no rule above matches
Respond with 400 Body type json
{
"code": "MISSING_CHANNEL",
"message": "Header X-Channel is required."
}
3 · Label
| Field | Value |
|---|---|
| Name | Victor KEZA Training Centers |
| Code | VICTOR_KEZA_CENTERS |
| Service | KEZA |
| Category | victor-course |
| Active | ✔ served by the mock server right away |
GASABO matches, which proves the upper-casing worked.{{request.query.district}} copies the district from the request into the answer, so any other district gets an empty list for that district.
The Templating guide in New Mock
| Field | Value | |
|---|---|---|
| **Endpoint adapter *** | FMAPPING (fetch) |
locked after save |
| **Endpoint code *** | VICTOR_KEZA_CENTERS_FETCH |
locked after save |
| **Endpoint url *** | http://mock-service-engine.apps:8000/victor/keza/v1/centers |
|
| **Endpoint type *** | FETCH (set by the adapter) |
|
| **Endpoint name *** | Victor KEZA Training Centres |
|
| **Partner *** | KEZA |
locked after save |
| Active | ON |
|
| **Endpoint description *** | Victor course: list KEZA centres for a district (headers, query, JSONata functions). |
**Endpoint Mapping ***
{
"method": "GET",
"headers": {
"X-Channel": "IREMBO",
"X-Request-Id": applicationNumber
},
"query": {
"district": $uppercase($trim(districtName)),
"program": programCode
}
}
"IREMBO") are sent as is.$trim removes spaces, $uppercase gives GASABO. Others you will use: $lowercase, $substring, $string, $number, $join, $exists.HTTP 200 captured on staging · 26 Sep 2026
1 · Request (curl)
curl -s -X POST "https://integration-gateway.staging.iremboinc.com/integration/v1/endpoints/VICTOR_KEZA_CENTERS_FETCH/mapping/test" \
-H "Authorization: Bearer $TOKEN" \
-H "portal: integration-management-portal" \
-H "Content-Type: application/json" \
-d '{
"districtName": " gasabo ",
"programCode": "DATA_ANALYTICS",
"applicationNumber": "C260926081500VICTR1"
}' | jq '.data'
2 · What the engine would send
{
"method": "GET",
"headers": {
"X-Channel": "IREMBO",
"X-Request-Id": "C260926081500VICTR1"
},
"query": {
"district": "GASABO",
"program": "DATA_ANALYTICS"
}
}
3 · What to notice
" gasabo " became "GASABO" before anything was sent.Content-Type and Accept by default.HTTP 200 0.97 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_CENTERS_FETCH",
"callerId": "victor-course",
"requester": "victor-course",
"payload": {
"districtName": " gasabo ",
"programCode": "DATA_ANALYTICS",
"applicationNumber": "C260926081500VICTR1"
}
}' | jq
2 · Real response
{
"status": true,
"responseCode": "integration_870000",
"responseMessage": "integration_870000",
"data": {
"id": "bdcf8445-77ea-4674-a244-9d69fc71ae63",
"endpointCode": "VICTOR_KEZA_CENTERS_FETCH",
"callerId": "victor-course",
"response": "{\n \"district\": \"GASABO\",\n \"program\": \"DATA_ANALYTICS\",\n \"centers\": [\n {\n \"centerCode\": \"KZ-GSB-01\",\n \"name\": \"Kacyiru Digital Hub\",\n \"seatsLeft\": 12,\n \"sessionStart\": \"2026-10-05\"\n },\n {\n \"centerCode\": \"KZ-GSB-02\",\n \"name\": \"Kimironko Tech Lab\",\n \"seatsLeft\": 0,\n \"sessionStart\": \"2026-10-12\"\n }\n ]\n}",
"adapterCode": "FMAPPING"
},
"timestamp": "2026-09-26T09:48:08.642273201",
"traceId": "6ab778b87a5914ed6edcd125b34e4e3e"
}
3 · Decoded data.response
{
"district": "GASABO",
"program": "DATA_ANALYTICS",
"centers": [
{
"centerCode": "KZ-GSB-01",
"name": "Kacyiru Digital Hub",
"seatsLeft": 12,
"sessionStart": "2026-10-05"
},
{
"centerCode": "KZ-GSB-02",
"name": "Kimironko Tech Lab",
"seatsLeft": 0,
"sessionStart": "2026-10-12"
}
]
}
4 · What to notice
seatsLeft: 0. In Module 4 you could filter it out with a transform.HTTP 200 1.15 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_CENTERS_FETCH",
"callerId": "victor-course",
"requester": "victor-course",
"payload": {
"districtName": "Huye",
"programCode": "DATA_ANALYTICS",
"applicationNumber": "C260926082000VICTR7"
}
}' | jq
2 · Real response
{
"status": true,
"responseCode": "integration_870000",
"responseMessage": "integration_870000",
"data": {
"id": "9422ff0b-439b-4cb2-a4e5-d3ed686889f9",
"endpointCode": "VICTOR_KEZA_CENTERS_FETCH",
"callerId": "victor-course",
"response": "{\n \"district\": \"HUYE\",\n \"program\": \"DATA_ANALYTICS\",\n \"centers\": []\n}",
"adapterCode": "FMAPPING"
},
"timestamp": "2026-09-26T09:52:25.670729227",
"traceId": "6ab779b928f38e97acf921e1f228ccbf"
}
3 · Decoded data.response
{
"district": "HUYE",
"program": "DATA_ANALYTICS",
"centers": []
}
4 · What to notice
"district": "HUYE": the template echoed the query value.Your tech notes · Build the value in the mapping, not in the form
Clean, rename, join and convert values in JSONata:
$uppercase($trim(districtName)),firstName & " " & lastName,$string(age). Header and query values should be strings: wrap numbers in$string()rather than relying on conversion, and never pass objects.
KEZA suddenly answers 400 MISSING_CHANNEL for every district. The mapping test shows "headers": {"X-Request-Id": "C26…"}. What happened?
B. Only X-Request-Id is in the headers, so X-Channel is missing, and the mock’s Otherwise answers 400 MISSING_CHANNEL.
Your turn · change one thing
KEZA now also needs
X-Lang: enand wants the district in lower case. Copy the reference mock to/${your_first_name}/keza/v1/centersand the endpoint to${YOUR_FIRST_NAME}_KEZA_CENTERS_FETCH(pointing at your mock). AddX-Lang: ento the headers, lower-case the district query value, and update your mock’s rule 1 to expect both. Replace the variables before saving (Aline →ALINE_…,/aline/…). Predict the mapping-test output, then run it.Done when: the mapping test shows
X-Lang: enand a lower-case district, and a real call answers 200.
← 2.1 Path parameters: prefill the learner profile · 2.3 Push a JSON body: enroll the learner →