KEZA’s scholarship service sits behind an OAuth2 server. Irembo is a registered client: it exchanges a client id and secret for a short-lived access token. New idea: auth type CLIENT_CREDENTIALS, with the credentials in the query of the token call.
KEZA API guide · the slice you need
POST /oauth/token?grant_type=client_credentials&client_id=<id>&client_secret=<secret>
200 { "access_token", "token_type": "Bearer", "expires_in": 3600, "scope" }
401 { "error": "invalid_client" }
GET /v1/scholarships?nationalId=<id>
Authorization: Bearer <access_token>
200 { "nationalId", "scholarships": [ { "scholarshipCode", "programCode", "coverage", "status" } ] }
1 · Request: POST /victor/keza/oauth/token
2 · Responses (the first matching rule wins)
Rule 1 · Valid client
| Match on | Key | Operator | Value | |
|---|---|---|---|---|
| If | Query param | grant_type | equals | client_credentials |
| and | Query param | client_id | equals | irembo-hub |
| and | Query param | client_secret | equals | keza-secret-2026 |
Respond with 200 Body type json
{
"access_token": "keza-oauth-9b21",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "scholarships.read"
}
Otherwise — when no rule above matches
Respond with 401 Body type json
{
"error": "invalid_client",
"error_description": "Client authentication failed."
}
3 · Label
| Field | Value |
|---|---|
| Name | Victor KEZA OAuth Token |
| Code | VICTOR_KEZA_OAUTH_TOKEN |
| Service | KEZA |
| Category | victor-course |
| Active | ✔ served by the mock server right away |
1 · Request: GET /victor/keza/v1/scholarships
2 · Responses (the first matching rule wins)
Rule 1 · Aline has a scholarship
| Match on | Key | Operator | Value | |
|---|---|---|---|---|
| If | Header | Authorization | equals | Bearer keza-oauth-9b21 |
| and | Query param | nationalId | equals | 1199880012345678 |
Respond with 200 Body type json
{
"nationalId": "1199880012345678",
"scholarships": [
{
"scholarshipCode": "KZ-SCH-DA-2026",
"programCode": "DATA_ANALYTICS",
"coverage": "FULL",
"status": "AWARDED"
}
]
}
Rule 2 · No scholarship
| Match on | Key | Operator | Value | |
|---|---|---|---|---|
| If | Header | Authorization | equals | Bearer keza-oauth-9b21 |
Respond with 200 Body type jsonDynamic response ON
{
"nationalId": "{{request.query.nationalId}}",
"scholarships": []
}
Otherwise — when no rule above matches
Respond with 401 Body type json
{
"error": "invalid_token",
"error_description": "Access token missing or expired."
}
3 · Label
| Field | Value |
|---|---|
| Name | Victor KEZA Scholarships |
| Code | VICTOR_KEZA_SCHOLARSHIPS |
| Service | KEZA |
| Category | victor-course |
| Active | ✔ served by the mock server right away |
Bearer keza-oauth-9b21, the token issued by mock 1.| Field | Value | |
|---|---|---|
| **Endpoint adapter *** | FMAPPING (fetch) |
locked after save |
| **Endpoint code *** | VICTOR_KEZA_SCHOLARSHIP_FETCH |
locked after save |
| **Endpoint url *** | http://mock-service-engine.apps:8000/victor/keza/v1/scholarships |
|
| **Endpoint type *** | FETCH (set by the adapter) |
|
| **Endpoint name *** | Victor KEZA Scholarships |
|
| **Partner *** | KEZA |
locked after save |
| Active | ON |
|
| **Endpoint description *** | Victor course: scholarship lookup behind OAuth2 client credentials. |
**Endpoint Mapping ***
{
"method": "GET",
"query": { "nationalId": nationalId },
"auth": {
"type": "CLIENT_CREDENTIALS",
"baseUrl": "http://mock-service-engine.apps:8000",
"parameters": {
"authPath": "/victor/keza/oauth/token",
"method": "POST",
"query": {
"grant_type": "client_credentials",
"client_id": "irembo-hub",
"client_secret": "keza-secret-2026"
}
},
"tokenConfig": {
"storageKey": "VICTOR_KEZA_OAUTH",
"headerName": "Authorization",
"tokenPrefix": "Bearer",
"expiryBuffer": 300,
"extractPath": "access_token"
}
}
}
http://mock-service-engine.apps:8000/victor/keza/oauth/token.query, bodyTemplate (with contentType) or headers. The secret is a hard-coded training value (see the caveat below)."expiryBuffer": "expires_in" to use the server’s lifetime.Secrets · training literal. Hard-coded only because KEZA is a training mock. In a real integration use the environment variable instead: keza-secret-2026 → "${KEZA_CLIENT_SECRET}" (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 client secret
keza-secret-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:
"query": {"grant_type": "client_credentials","client_id": "irembo-hub","client_secret": "${KEZA_CLIENT_SECRET}"}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 1.02 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_SCHOLARSHIP_FETCH",
"callerId": "victor-course",
"requester": "victor-course",
"payload": {
"nationalId": "1199880012345678"
}
}' | jq
2 · Real response
{
"status": true,
"responseCode": "integration_870000",
"responseMessage": "integration_870000",
"data": {
"id": "c598cd9f-e6db-4be1-9ec9-98319de9e96d",
"endpointCode": "VICTOR_KEZA_SCHOLARSHIP_FETCH",
"callerId": "victor-course",
"response": "{\n \"nationalId\": \"1199880012345678\",\n \"scholarships\": [\n {\n \"scholarshipCode\": \"KZ-SCH-DA-2026\",\n \"programCode\": \"DATA_ANALYTICS\",\n \"coverage\": \"FULL\",\n \"status\": \"AWARDED\"\n }\n ]\n}",
"adapterCode": "FMAPPING"
},
"timestamp": "2026-09-26T09:48:14.506183964",
"traceId": "6ab778be2627e4454a05ce85ca67a0a8"
}
3 · Decoded data.response
{
"nationalId": "1199880012345678",
"scholarships": [
{
"scholarshipCode": "KZ-SCH-DA-2026",
"programCode": "DATA_ANALYTICS",
"coverage": "FULL",
"status": "AWARDED"
}
]
}
4 · What to notice
HTTP 200 1.21 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_SCHOLARSHIP_FETCH",
"callerId": "victor-course",
"requester": "victor-course",
"payload": {
"nationalId": "1199770098765432"
}
}' | jq
2 · Real response
{
"status": true,
"responseCode": "integration_870000",
"responseMessage": "integration_870000",
"data": {
"id": "70bb9f85-4a27-47ee-8a7c-fe9e07abc884",
"endpointCode": "VICTOR_KEZA_SCHOLARSHIP_FETCH",
"callerId": "victor-course",
"response": "{\n \"nationalId\": \"1199770098765432\",\n \"scholarships\": []\n}",
"adapterCode": "FMAPPING"
},
"timestamp": "2026-09-26T09:48:15.948448651",
"traceId": "6ab778bf8a3b2f4de7698e29f0a2707a"
}
3 · Decoded data.response
{
"nationalId": "1199770098765432",
"scholarships": []
}
4 · What to notice
VICTOR_KEZA_OAUTH and reused while it is younger than expiryBuffer. Kibana shows whether a token call happened: look for Sending HTTP request to …/oauth/token (Lesson 6.3).
Real capture (staging): Request info of VICTOR_KEZA_SCHOLARSHIP_FETCH, an OAuth2 endpoint
Your tech notes · Form-encoded token servers
Many OAuth servers want the credentials as a form body. Use
"contentType": "application/x-www-form-urlencoded"and putgrant_type,client_idandclient_secretinbodyTemplate.
The token server answers {"data":{"accessToken":"abc","ttl":900}}. What do you set?
A. Both are JSONata paths into the token response. Without a valid expiryBuffer the call fails with TOKEN_EXPIRY_EXTRACTION_ERROR.
Your turn · see the token cache work
Copy both mocks to
/${your_first_name}/keza/…and create${YOUR_FIRST_NAME}_KEZA_SCHOLARSHIP_FETCH(FMAPPING) with storageKey${YOUR_FIRST_NAME}_KEZA_OAUTH. Replace the variables before saving (Aline →ALINE_…,/aline/…).
- Call it for Aline twice. Check: both Communication Traces list one call (scholarships). In Kibana, only the first call has a
Sending HTTP request to …/oauth/tokenline (Lesson 6.3).- Predict, then test: change
client_secretto a wrong value and call again. It still answers 200: the cached token is reused.- Change the storageKey to
${YOUR_FIRST_NAME}_KEZA_OAUTH_2. Now the fresh login fails with the token server’sinvalid_client401. Restore the secret.
← 3.2 Basic auth (BASIC): certificate check · 3.4 Choose and troubleshoot authentication →