Read the SRD: Commercial Premise Trading Licence. A business owner applies for a trading licence for a commercial premise. Seven lookups fill and check the form, the partner system (DLS) quotes the fee, and five pushes keep DLS in step with the workflow: create the application, confirm the payment, book the inspection, record the decision and issue the licence PDF. This build is also your capstone: Lesson 7.1 assesses it.
You build the endpoints; the service manager uses them
In Course 1 (Service Manager Certification) the service manager configures this kind of service with endpoints that already exist. Here you are the integration manager: you build every endpoint the SRD lists in sections 6 and 7, with mocks behind them that follow section 9, so a service manager can configure the form and workflow with your endpoint codes. Use your first name as the prefix everywhere (
/${your_first_name}/…,${YOUR_FIRST_NAME}_…, application numbers).
From the SRD to your integration worksheet
| SRD | Endpoint code | Mapping · mode | Where it runs | Request reads | Saves back · failure |
|---|---|---|---|---|---|
| L1 · 6.4 | ${YOUR_FIRST_NAME}_ID_LOOKUP_FETCH |
FMAPPING · sync | Form, field 1 nationalId |
nationalId |
surname, otherNames, gender, dateOfBirth · responseCode ≠ "00" |
| L2 · 6.5 | ${YOUR_FIRST_NAME}_ID_PHONE_MATCH_FETCH |
FMAPPING · sync | Form, field 6 phoneNumber |
nationalId, phoneNumber (useMultiplePayloadKeys) |
Check only · ≠ "00" or data.match ≠ true |
| L3 · 6.6 | ${YOUR_FIRST_NAME}_TIN_LOOKUP_FETCH |
FMAPPING · sync | Form, field 8 tin |
tin |
businessName, taxpayerCategory, registeredAddress |
| L4 · 6.7 | ${YOUR_FIRST_NAME}_TAX_COMPLIANCE_FETCH |
FMAPPING · sync | Form, hidden field 15 | tin (FIELD_LISTENER) |
Check only · ≠ "00" or data.hasArrears = true |
| L5 · 6.8 | ${YOUR_FIRST_NAME}_BUSINESS_RECORD_FETCH |
FMAPPING · sync | Form, hidden field 12 | tin (FIELD_LISTENER) |
businessId, companyStatus · blocks when not ACTIVE (the provider still answers 00) |
| L6 · 6.9 | ${YOUR_FIRST_NAME}_BUSINESS_ACTIVITIES_FETCH |
FMAPPING · sync, paginated | Form, dropdown field 16 | tin, page, size, search |
items: code (bindValue), description (bindLabel) |
| L7 · 6.10 | ${YOUR_FIRST_NAME}_LAND_PARCEL_FETCH |
FMAPPING · sync | Form, field 17 landUpi |
upi ← landUpi, in the body, never the URL |
owners, location, districtCode, landUse · blocks on no owners or land use not COMMERCIAL / MIXED_USE |
| F1 · 7.6 | ${YOUR_FIRST_NAME}_DLS_LICENCE_FEE_QUOTE_FETCH |
FMAPPING · sync, no Idempotency-Key | Form, hidden field 29 | businessId, tin, activityCode ← businessActivity, districtCode, floorArea | quoteReference, licenceFee · message from responseMessage |
| P1 · 7.7 | ${YOUR_FIRST_NAME}_DLS_CREATE_APPLICATION_PUSH |
PMAPPING · sync (INTEGRATION_UPDATE on APPLY) | Workflow T1 | Whole application + documents (getAttachmentContent), quoteReference |
dlsApplicationRef; amount → amountToPay (a number, parametersToRetain) |
| P2 · 7.8 | ${YOUR_FIRST_NAME}_DLS_PAYMENT_CONFIRMATION_PUSH |
PMAPPING · async | Workflow T5 | dlsApplicationRef (path), applicationNumber, billNumber, amountPaid, paymentDate, callbackUrl | Callback PAYMENT_CONFIRMED → dlsPaymentStatus |
| P3 · 7.9 | ${YOUR_FIRST_NAME}_DLS_INSPECTION_BOOKING_PUSH |
PMAPPING · async | Workflow T12 | dlsApplicationRef (path), inspectionDateTime, addressConfirmed, requestedBy | Callback INSPECTION_BOOKED → inspectionSlotId, inspectorName |
| P4 · 7.10 | ${YOUR_FIRST_NAME}_DLS_DECISION_PUSH |
PMAPPING · async | Workflow T19 | F2 and F3 fields, inspection report file, inspectionSlotId | Callback DECISION_RECORDED → decisionStatus, licenceNumber, partnerDecisionReason |
| P5 · 7.11 | ${YOUR_FIRST_NAME}_DLS_LICENCE_DOCUMENT_FETCH |
PMAPPING · async | Workflow T22 | licenceNumber (path), dlsApplicationRef, format PDF | Callback LICENCE_ISSUED → LICENCE-Certificate-fileName, licenceIssueDate, licenceExpiryDate |
| P6, P7 · 7.12–7.13 | ${YOUR_FIRST_NAME}_DLS_CANCEL_APPLICATION_PUSH, ${YOUR_FIRST_NAME}_DLS_RESUBMIT_APPLICATION_PUSH |
PMAPPING · sync, non-breaking | Optional (distinction) | reason / the P1 body without quoteReference (PUT, no Idempotency-Key) | Logged only; never blocks the applicant |
The mocks you build (SRD section 9; the lookup records are TBC in UAT, so use the SRD sample values)
| Mock | Input | Answer |
|---|---|---|
| Token server | POST /oauth/token, client_credentials, scope licensing.write |
Wrong secret → 401; right secret → 200 with access_token, token_type Bearer, expires_in 3600 (TA1) |
| Every provider | Missing or expired token (e.g. a rule on Authorization: Bearer EXPIRED) |
401 DLS-401-01 / ID-401-01… (TA2) |
| Identity (L1, L2) | ID-OK 1199080012345678 with PHONE-OK 0788100200; ID-UNKNOWN; PHONE-OTHER |
200 with the four identity fields; 404 ID-404-01; 422 ID-422-01 |
| Tax (L3, L4) | TIN-OK 100123456; TIN-UNKNOWN; TIN-ARREARS |
200 Inzozi Retail Ltd, hasArrears false; 404 TAX-404-01; 422 TAX-422-01 |
| Business (L5, L6) | TIN-OK; TIN-INACTIVE; L6 with size 1, pages 0 and 1 | businessId BR-104523, ACTIVE, two activities (4711, 5610); a status other than ACTIVE, still 00; one item per page, totalItems 2 (TL6) |
| Land (L7) | UPI-OK 1/03/02/01/1234; UPI-NO-OWNER; UPI-RESIDENTIAL |
Owners + COMMERCIAL + districtCode 0102; no owners; RESIDENTIAL (all 00) |
| DLS F1 | floorArea 1–50 / 51–200 / 201–9000 | licenceFee 100,000 / 150,000 / 250,000 with a quoteReference and validUntil at least 72 h ahead (TA3) |
| DLS F1 errors | floorArea 9001 / 9002 / 9003; no floorArea | 404 DLS-404-01 / 422 DLS-422-01 / 422 DLS-422-02; 400 DLS-400-01 with errors[].field floorArea (TA4) |
| DLS F1 for P1 cases | floorArea 9004 / 9005 / 9006 | 200, licenceFee 250,000 (9004: validUntil already in the past) |
| DLS P1 | floorArea 9004 / 9005 / 9006; no fire safety certificate; otherwise | 410 DLS-410-01 / 422 DLS-422-03 / 503 DLS-500-01; 422 DLS-422-04 (TA7); 201 DLS-APP-TEST-<n> with amount = the quote |
| DLS P1 repeats | Rules on the Idempotency-Key header: a repeated key; the same key with another body |
200 with the same dlsApplicationRef (TA5); 409 DLS-409-01 (TA6) |
| DLS P2–P5 | Any valid request | 202 with data.status ACCEPTED; then you send the callback (gotcha below) (TA8) |
| P3 / P4 special cases | inspectionDateTime at 13:00; decisionComments containing MOCK_REJECT |
FAILED callback DLS-422-06; DECISION_RECORDED with decisionStatus REJECTED and a reason (TA10) |
| DLS P5 | A licenceNumber P4 did not approve | 409 DLS-409-02 (TA11) |
| DLS P6, P7 (optional) | Any reason, twice; any valid body | 200 CANCELLED both times (TA13); 200 UPDATED |
Gotcha · an async push is only half the job
P2 to P5 answer 202 and the state changes at once; the application only moves on when the callback arrives and fires PUSH. Your mock answers the 202, so you play DLS for the second half: post the callback body from section 7.5 (same
requestId,statusSUCCESS or FAILED,data) to the integration notification endpoint for each push. A REJECTED decision is a SUCCESS callback that the workflow routes back to level 2; FAILED is only for technical errors. Resend one callback with the samerequestIdand show that it changes nothing (TC38). Callback retries (TA9) are DLS behaviour: a mock cannot show them, so write in your go-live note how you will check them with DLS.
Gotcha · keys, numbers and idempotency
Every DLS POST except F1 sends
Idempotency-Key: {applicationNumber}-{operation}(CREATE, PAYMENT, INSPECTION, DECISION, LICENCE, CANCEL; a second booking or decision adds-2): prefix your application numbers, or an old push is replayed. P7 is a PUT and sends no key. P1’samountmust reach pricing as a plain number (150000), not "150,000 RWF". F1 fires only when businessId, businessActivity, districtCode and floorArea all have a value, so L5 and L7 must always return them. National IDs, TINs and UPIs travel in the body, never in a URL or query string (TL8).
Your mock answers P3 with 202 and the application moves to PENDING_INSPECTION_BOOKING, then stays there. What is missing?
D. An async push only finishes when the callback arrives and fires PUSH. With a mock, you send that callback.
DLS answers P4 with a callback: status SUCCESS, decisionStatus REJECTED. What should happen?
B. FAILED is only for technical errors. A partner rejection is a valid result that the workflow routes back to level 2.
Your turn · deliverable
Your trainer reviews the Jira ticket: the completed worksheet, screenshots of each mock, the mapping text and mapping-test output of every endpoint, the curl commands with their real output (including the callbacks you sent), the traceIds and one Kibana line for a failing call, and a short go-live note (production URLs, every secret as an environment variable, Use mock off, callback retries checked with DLS). P6 and P7 are optional and count towards a distinction.
Submit: open the IremboHub service desk, choose Submit an assignment and link your evidence. Share the ticket link with your trainer.
← 6.5 Scheduled jobs: pull the KEZA exam schedule regularly · 7.1 Capstone: build and prove a full integration →