Service Requirements Document (SRD). Everything needed to configure this service is on this page. If something is not written here, the answer is "not required"; do not invent it. Items still waiting for the institution are marked TBC and listed in section 11.
The partner system is called the District Licensing System (DLS) in this document. It is a placeholder for whichever institution system receives the pushes. Every API the service uses is listed in section 6 (APIs to use). The form lookup APIs L1 to L7 are documented in section 6; the DLS APIs (codes starting with
DLS_) in section 7. Both are proposed contracts, to be agreed with the providers and built in the integration engine.
| Item | Value |
|---|---|
| Institution | QT/HUZA |
| Partner | District Licensing System (DLS), placeholder |
| Who applies | A business owner (individual account) or an Irembo agent acting for them |
| Pattern | Identify applicant, business and premise → DLS quotes the fee on the form (F1) → submit: DLS records the application and returns the amount (P1) → pay → DLS confirms the payment (P2) → level 1 review → inspection booked in DLS (P3) → inspection report → level 2 approval → DLS records the decision (P4) → DLS issues the licence PDF (P5) → "Licence Issued" |
| Integrations | 7 form lookup APIs (L1 to L7), 1 fee quote fetch (F1), 5 new workflow pushes (P1 to P5) and 2 optional ones (P6, P7) |
| Payment | RWF, dynamic (Price From Attribute amountToPay from P1), valid 72 hours |
| Officer levels | 2: level 1 (review, inspection booking, inspection report) and level 2 (final approval) |
| Certificate | None from IremboHub. The licence PDF comes from DLS and is attached to N8 |
| SLA | 5 working days |
| Version | V.2.1.0 (draft for review) |
| Item | Value |
|---|---|
| Service | Commercial Premise Trading Licence |
| Institution | QT/HUZA |
| Partner system | District Licensing System (DLS), placeholder |
| Document type | Service Requirements Document (SRD) with partner API specification |
| Version | V.2.1.0 (draft for review) |
| Date | 17 October 2026 |
A business owner applies for a trading licence for a commercial premise. The fee is quoted by the partner system. The application is reviewed by an officer, inspected on site, approved at a second level and recorded in the partner system, which issues the licence document.
The service combines data fetches on the form with five integration pushes in the workflow.
| In scope | Not in scope |
|---|---|
| Online application by citizens and agents | Business applicants (Business: off) |
| 7 form lookup APIs (L1 to L7, section 6) and 1 fee quote fetch (F1) | Licence renewal (possible follow-up service, open question 5) |
| Dynamic fee quoted by DLS, paid through IremboPay | IremboHub-generated certificate (the licence PDF comes from DLS) |
| Level 1 review, inspection booking and report, level 2 approval | Slot booking in IremboHub (the inspection is booked in DLS) |
| 5 workflow integrations with DLS, 2 optional | |
| Notifications in English, French and Kinyarwanda |
| Term | Meaning |
|---|---|
| DLS | District Licensing System, the partner system (placeholder) |
| UPI | Unique Parcel Identifier of the land, from the land parcel lookup (L7) |
| TIN | Taxpayer Identification Number, from the taxpayer lookup (L3) |
| Sync push | The engine waits for the partner's answer before changing state |
| Async push | The state changes at once; the partner answers later through a callback, which fires the PUSH event |
applicationDetails |
The object in an integration response whose fields are saved onto the application |
| TBC | To be confirmed by the institution |
| Item | English | French | Kinyarwanda |
|---|---|---|---|
| Service name | Commercial Premise Trading Licence | Licence d'exploitation d'un local commercial | Uruhushya rwo gucururiza mu nyubako y'ubucuruzi |
| Summary | This service allows business owners to apply for a trading licence for a commercial premise. You enter your ID, your company TIN and the land UPI of the premise, choose the activity carried out there and upload the required documents. The licence fee is calculated by the licensing authority and paid on IremboGov. Your application is reviewed, the premise is inspected, and the licence is sent to you by email once approved. | Ce service permet aux propriétaires d'entreprise de demander une licence d'exploitation pour un local commercial. Vous saisissez votre numéro d'identité, le NIF de votre entreprise et l'UPI du terrain du local, choisissez l'activité exercée et téléversez les documents requis. Les frais de licence sont calculés par l'autorité de délivrance et payés sur IremboGov. Votre demande est examinée, le local est inspecté et la licence vous est envoyée par e-mail une fois approuvée. | Iyi serivisi ifasha ba nyir'ubucuruzi gusaba uruhushya rwo gucururiza mu nyubako y'ubucuruzi. Wandika nomero y'indangamuntu yawe, TIN y'ikigo cyawe na UPI y'ubutaka inyubako iriho, ugahitamo igikorwa gikorerwa aho kandi ukohereza inyandiko zisabwa. Amafaranga y'uruhushya abarwa n'urwego rutanga impushya kandi yishyurwa kuri IremboGov. Dosiye yawe irasuzumwa, inyubako igakorerwa igenzura, maze uruhushya rukoherezwa kuri imeyili rumaze kwemezwa. |
| Setting | Value |
|---|---|
| Category | Trade and Industry |
| Who can apply | Individual: on. Business: off. Roles ROLE_CITIZEN, ROLE_AGENT |
| Languages | English, French, Kinyarwanda |
| Attachments | Lease or land title, occupancy permit, fire safety certificate, floor plan, other (section 3.6) |
| Certificate | None generated by IremboHub. The licence PDF comes from DLS (P5) and is attached to the approval email |
| Expected processing time (SLA) | 5 working days |
Rules (wiki: Form configuration · Rules):
"required": false, "defaultRequired": true, plus the standard props.required expression.expressions.hide with the negated condition. hide clears the value. props.hideField: true keeps the value and is used for helper fields that fetches fill.YES, not Yes).| Section (key) | Block (key) | Visible when |
|---|---|---|
Applicant details (applicantSection) |
Applicant (applicantBlock) |
always |
Business details (businessSection) |
Business (businessBlock) |
always |
Premise details (premiseSection) |
Premise (premiseBlock) |
always |
Licence fee (feeSection) |
Fee (feeBlock) |
always |
Supporting documents (documentsSection) |
Documents (documentsBlock) |
always |
| # | Block | Key | Label (EN) | Type | Req. | Validation and messages (EN) | Options / fetch | Visible when |
|---|---|---|---|---|---|---|---|---|
| 1 | Applicant | nationalId |
National ID | customgenericdatafetch |
Yes | 16 digits. Fetch error: the API message (6.4) | ID_LOOKUP_FETCH (L1). Populates surname, otherNames, gender, dateOfBirth |
always |
| 2 | Applicant | surname |
Surname | input, read-only | No | Filled by 1 | nationalId has a value |
|
| 3 | Applicant | otherNames |
Other names | input, read-only | No | Filled by 1 | nationalId has a value |
|
| 4 | Applicant | gender |
Gender | input, read-only | No | Filled by 1 | nationalId has a value |
|
| 5 | Applicant | dateOfBirth |
Date of birth | input, read-only | No | Filled by 1 | nationalId has a value |
|
| 6 | Applicant | phoneNumber |
Phone number | customotpdatafetch or phone input |
Yes | Pattern ^07[2389][0-9]{7}$: "Enter a 10-digit Rwandan phone number, e.g. 0788100200." Fetch error: the API message (6.5) |
ID_PHONE_MATCH_FETCH (L2) with nationalId and phoneNumber. Blocks if they don't match |
always |
| 7 | Applicant | email |
input | Yes | Lowercase email pattern: "Enter a valid email address." | always | ||
| 8 | Business | tin |
TIN | customtininput |
Yes | 9 digits. Fetch error: the API message (6.6) | TIN_LOOKUP_FETCH (L3). Populates businessName, taxpayerCategory, registeredAddress |
always |
| 9 | Business | businessName |
Business name | input, read-only | No | Filled by 8 | tin has a value |
|
| 10 | Business | taxpayerCategory |
Taxpayer category | input, read-only | No | Filled by 8 | tin has a value |
|
| 11 | Business | registeredAddress |
Registered address | input, read-only | No | Filled by 8 | tin has a value |
|
| 12 | Business | businessRecord |
(hidden fetch) | customgenericdatafetch, hideField: true |
No | "This company is not active. Only active companies can apply." | BUSINESS_RECORD_FETCH (L5), FIELD_LISTENER on tin. Populates businessId, companyStatus |
never shown |
| 13 | Business | businessId |
Business registration ID | input, read-only | No | Filled by 12 | tin has a value |
|
| 14 | Business | companyStatus |
Company status | input, read-only | No | Filled by 12 | tin has a value |
|
| 15 | Business | taxArrearsCheck |
(hidden fetch) | customgenericdatafetch, hideField: true |
No | The API message, shown as returned (6.7) | TAX_COMPLIANCE_FETCH (L4), FIELD_LISTENER on tin |
never shown |
| 16 | Business | businessActivity |
Business activity at the premise | customdropdowndatafetchpaginated |
Yes | "Select the activity carried out at the premise." | BUSINESS_ACTIVITIES_FETCH (L6), FIELD_LISTENER on tin, dataField items, bindLabel description, bindValue code |
tin has a value |
| 17 | Premise | landUpi |
Land UPI | customgenericdatafetch, TRIGGER_BUTTON |
Yes | Fetch error: the API message (6.10) | LAND_PARCEL_FETCH (L7). Populates landOwners, province, district, districtCode, sector, cell, village, landUse |
always |
| 18 | Premise | landOwners |
Land owners | input, read-only | No | Filled by 17 | landUpi has a value |
|
| 19 | Premise | province |
Province | input, read-only | No | Filled by 17 | landUpi has a value |
|
| 20 | Premise | district |
District | input, read-only | No | Filled by 17 | landUpi has a value |
|
| 21 | Premise | districtCode |
District code (helper) | input, hideField: true |
No | Filled by 17 (data.districtCode of L7). Used by the fee quote |
never shown | |
| 22 | Premise | sector |
Sector | input, read-only | No | Filled by 17 | landUpi has a value |
|
| 23 | Premise | cell |
Cell | input, read-only | No | Filled by 17 | landUpi has a value |
|
| 24 | Premise | village |
Village | input, read-only | No | Filled by 17 | landUpi has a value |
|
| 25 | Premise | landUse |
Land use | input, read-only | No | Filled by 17 | landUpi has a value |
|
| 26 | Premise | ownsPremise |
Do you own the premise? | radio | Yes | "This field is required." | YES: Yes · NO: No |
always |
| 27 | Premise | floorArea |
Floor area (square metres) | input, number | Yes | Minimum 1: "Enter a floor area of at least 1 square metre." | always | |
| 28 | Premise | openingHours |
Opening hours | input | Yes | "This field is required." | always | |
| 29 | Fee | feeQuote |
(hidden fetch) | customgenericdatafetch, hideField: true |
No | "We could not get the licence fee. Please try again in a few minutes." | DLS_LICENCE_FEE_QUOTE_FETCH (section 7.6), FIELD_LISTENER, useMultiplePayloadKeys: true, mappings businessId←businessId, activityCode←businessActivity, districtCode←districtCode, floorArea←floorArea. Populates quoteReference, licenceFee |
never shown |
| 30 | Fee | quoteReference |
Quote reference (helper) | input, hideField: true |
Yes | Filled by 29 | never shown | |
| 31 | Fee | licenceFee |
Licence fee (RWF) | number, read-only | Yes | Filled by 29 | quoteReference has a value |
|
| 32 | Documents | leaseAgreement |
Notarised lease agreement | custominternalfileupload |
Yes | PDF, JPG or PNG, up to 10 MB | ownsPremise = NO |
|
| 33 | Documents | landTitle |
Land title | custominternalfileupload |
Yes | PDF, JPG or PNG, up to 10 MB | ownsPremise = YES |
|
| 34 | Documents | occupancyPermit |
Occupancy permit | custominternalfileupload |
Yes | PDF, JPG or PNG, up to 10 MB | always | |
| 35 | Documents | fireSafetyCertificate |
Fire safety certificate | custominternalfileupload |
Yes | PDF, JPG or PNG, up to 10 MB | always | |
| 36 | Documents | floorPlan |
Floor plan of the premise | custominternalfileupload |
Yes | PDF, JPG or PNG, up to 10 MB | always | |
| 37 | Documents | otherDocuments |
Other documents | custominternalfileupload |
No | PDF, JPG or PNG, up to 10 MB | always |
Notes:
quoteReference and licenceFee are required, so the applicant can't continue without a quote (TC12).licenceFee is for display only. The amount charged comes from P1 (amountToPay), see section 4.| API | Used by field | Fills in | Blocks the form when | Documentation |
|---|---|---|---|---|
| L1 Identity lookup | nationalId |
surname, otherNames, gender, dateOfBirth |
ID not found | 6.4 |
| L2 Phone ownership check | phoneNumber |
Phone number not registered to the ID | 6.5 | |
| L3 Taxpayer lookup | tin |
businessName, taxpayerCategory, registeredAddress |
TIN not found | 6.6 |
| L4 Tax compliance check | taxArrearsCheck |
Business has tax arrears | 6.7 | |
| L5 Business record lookup | businessRecord |
businessId, companyStatus |
Company is not active | 6.8 |
| L6 Business activities | businessActivity |
Dropdown of activities. Value stored: activity code | 6.9 | |
| L7 Land parcel lookup | landUpi |
landOwners, province, district, districtCode, sector, cell, village, landUse |
UPI has no owners, or land use is not commercial or mixed use | 6.10 |
| F1 Licence fee quote | feeQuote |
quoteReference, licenceFee |
DLS returns an error | 7.6 |
For every fetch that must block the applicant (fields 1, 6, 12, 15, 17, 29):
response.validation.failureCondition on the integration endpoint.errorMapping on the form field with detectionType: "advanced", useApiMessage: true and errorMessagePath: "responseMessage".A JSONata $error() in the response transform does not reach the form. The LAND_PARCEL_FETCH (L7) failure condition covers two cases: no owners, and landUse that is not commercial or mixed use.
The DLS error body puts its message in responseMessage (section 7.4), so the same errorMessagePath works for the fee quote.
A FIELD_LISTENER fetch with several payload keys fires only when every field in its payload mapping has a value. If one is null, the quote never runs.
businessId, businessActivity, districtCode, floorArea) must always get a value, or a fallback.feeQuote after all four fields, so the controls exist when the fetch starts.districtCode (L7), map it from district in the L7 response transform.| Document | Key | DLS documentType |
Required when |
|---|---|---|---|
| Notarised lease agreement | leaseAgreement |
LEASE_AGREEMENT |
The applicant does not own the premise (ownsPremise = NO) |
| Land title | landTitle |
LAND_TITLE |
The applicant owns the premise (ownsPremise = YES) |
| Occupancy permit | occupancyPermit |
OCCUPANCY_PERMIT |
Always |
| Fire safety certificate | fireSafetyCertificate |
FIRE_SAFETY_CERTIFICATE |
Always |
| Floor plan of the premise | floorPlan |
FLOOR_PLAN |
Always |
| Other documents | otherDocuments |
OTHER |
Optional |
| Situation | EN | FR | RW |
|---|---|---|---|
| Phone not registered to the ID | Identity API message, shown as returned | (endpoint message, shown as returned) | (endpoint message, shown as returned) |
| Tax arrears | Tax API message, shown as returned | (endpoint message, shown as returned) | (endpoint message, shown as returned) |
| UPI with no owners or wrong land use | UPI message, shown as returned | (endpoint message, shown as returned) | (endpoint message, shown as returned) |
| Company not active | "This company is not active. Only active companies can apply." | "Cette entreprise n'est pas active. Seules les entreprises actives peuvent faire une demande." | "Iki kigo ntigikora. Ibigo bikora gusa ni byo bishobora gusaba." |
| Fee unavailable | "We could not get the licence fee. Please try again in a few minutes." | "Impossible d'obtenir les frais de licence. Réessayez dans quelques minutes." | "Ntitwabashije kubona amafaranga y'uruhushya. Ongera ugerageze mu minota mike." |
Activity not licensable (DLS DLS-422-02) |
DLS message, shown as returned | (endpoint message, shown as returned) | (endpoint message, shown as returned) |
| Setting | Value |
|---|---|
| Type | Paid, dynamic, one scenario with no condition (4.1) |
| Currency | RWF only |
| When the applicant pays | On submission (APPLY), after P1 and EXECUTE_PRICING |
| Payment expiration time | 72 hours |
| Merchant | TBC (from the institution), fixed |
| Payment account | TBC (the institution's RWF account), fixed |
| Service payment code | TBC (from the institution), fixed |
| Invoice description | Trading licence ${dlsApplicationRef!""} - ${businessName!""} |
| Payment methods | All methods offered by IremboPay (Next step 1) |
| Late payment | Not accepted. PAYMENT_EXPIRED is final; the applicant applies again and gets a new quote (N3) |
| Refunds | None through IremboHub (open question 14) |
| # | Condition | Set price to | Attribute | Currency | Minimum | Maximum |
|---|---|---|---|---|---|---|
| 1 | None (every application) | Price From Attribute | amountToPay |
RWF | TBC from DLS (UAT mock: 100,000) | TBC from DLS (UAT mock: 250,000) |
amountToPay is retained from P1 with parametersToRetain (DLS amount → amountToPay). It must be a plain number (150000), not a formatted string ("150,000 RWF").licenceFee (field 31) comes from the F1 quote. The amount charged is always P1's amount, which DLS calculates from the same quote. If they differ, the bill follows P1.EXECUTE_PRICING, so pricing never runs without amountToPay.Worked examples (UAT mock, 9.2):
| Floor area | DLS amount | Bill |
|---|---|---|
| 30 m² | 100,000 | 100,000 RWF |
| 85 m² | 150,000 | 150,000 RWF |
| 250 m² | 250,000 | 250,000 RWF |
| Setting | Type | Rule |
|---|---|---|
| Merchant | Fixed | TBC |
| Payment account | Fixed | TBC (one RWF account) |
| Service payment code | Fixed | TBC |
There is only one currency, so the account and payment code do not depend on any field.
!""). Without it, bill generation fails with BILL_ID_GENERATION_FAILED_NON_HTTP_CLIENT_EXCEPTION_ERROR.validUntil in 7.6), so DLS accepts P2 for any bill paid in time.DLS-422-05 if it differs from the quoted amount.| State code | State name EN | FR | RW |
|---|---|---|---|
NEW |
New | Nouveau | Gishya |
PAYMENT_PENDING |
Payment Pending | Paiement en attente | Gutegereza kwishyura |
PAYMENT_EXPIRED |
Payment Expired | Paiement expiré | Igihe cyo kwishyura cyarangiye |
PAID |
Paid | Payé | Byishyuwe |
PENDING_PAYMENT_CONFIRMATION |
Pending Payment Confirmation | Confirmation du paiement en attente | Gutegereza kwemezwa kw'ubwishyu |
PENDING_ASSIGN_OFFICE |
Pending Office Assignment | Attribution au bureau en attente | Gutegereza koherezwa ku biro |
PENDING_OFFICER_LEVEL_1 |
Under Review | En cours d'examen | Irimo gusuzumwa |
PENDING_RESUBMISSION |
Pending Resubmission | En attente de resoumission | Gutegereza kongera kohereza |
APPOINTMENT_SET |
Inspection Appointment Set | Rendez-vous d'inspection fixé | Igihe cy'igenzura cyashyizweho |
PENDING_INSPECTION_BOOKING |
Pending Inspection Booking | Réservation de l'inspection en attente | Gutegereza guteganya igenzura |
PENDING_INSPECTION |
Pending Inspection | Inspection en attente | Gutegereza igenzura |
PENDING_OFFICER_LEVEL_2 |
Pending Final Approval | Approbation finale en attente | Gutegereza kwemezwa bwa nyuma |
LEVEL_2_APPROVED |
Approved by Officer | Approuvé par l'agent | Byemejwe n'umukozi |
PENDING_PARTNER_DECISION |
Pending Licensing Decision | Décision de licence en attente | Gutegereza icyemezo cy'uruhushya |
PENDING_LICENCE_DOCUMENT |
Pending Licence Document | Document de licence en attente | Gutegereza inyandiko y'uruhushya |
PENDING_LICENCE_FETCH |
Licence Being Issued | Licence en cours de délivrance | Uruhushya rurimo gutangwa |
REJECTED |
Rejected | Rejeté | Yanzwe |
APPROVED |
Licence Issued | Licence délivrée | Uruhushya rwatanzwe |
Final states: PAYMENT_EXPIRED, REJECTED, APPROVED.
| # | Start | Event | Who | End | Breaking action | Non-breaking | Next event |
|---|---|---|---|---|---|---|---|
| T1 | NEW | APPLY | ROLE_CITIZEN, ROLE_AGENT |
PAYMENT_PENDING | breakingActions in order: INTEGRATION_UPDATE { "endpointCode": "DLS_CREATE_APPLICATION_PUSH" } (P1, sync), EXECUTE_PRICING, BILL_ID_GENERATION |
N1 | |
| T2 | NEW | APPLY, end state two | ROLE_CITIZEN, ROLE_AGENT |
PAYMENT_PENDING | Same as T1 | N1 | |
| T3 | PAYMENT_PENDING | PAY | system authorised | PAID | N2 | INITIATE_PUSH | |
| T4 | PAYMENT_PENDING | EXPIRE_PAYMENT | system authorised | PAYMENT_EXPIRED | N3. Optional P6 (sync) | ||
| T5 | PAID | INITIATE_PUSH | system authorised | PENDING_PAYMENT_CONFIRMATION | INTEGRATION { "endpointCode": "DLS_PAYMENT_CONFIRMATION_PUSH", "async": true } (P2) |
||
| T6 | PENDING_PAYMENT_CONFIRMATION | PUSH | system authorised (callback) | PENDING_ASSIGN_OFFICE | ASSIGN_OFFICE | ||
| T7 | PENDING_ASSIGN_OFFICE | ASSIGN_OFFICE | system authorised | PENDING_OFFICER_LEVEL_1 | OFFICE_ASSIGNMENT, level 1 |
O1 | |
| T8 | PENDING_OFFICER_LEVEL_1 | REQUEST_FOR_ACTION | Level 1 officer | PENDING_RESUBMISSION | N4 | ||
| T9 | PENDING_RESUBMISSION | SUBMIT_RFA | ROLE_CITIZEN, ROLE_AGENT |
PENDING_OFFICER_LEVEL_1 | N5 | ||
| T10 | PENDING_OFFICER_LEVEL_1 | REJECT | Level 1 officer | REJECTED | N6 | ||
| T11 | PENDING_OFFICER_LEVEL_1 | SET_APPOINTMENT | Level 1 officer (form F1) | APPOINTMENT_SET | N7 | INITIATE_PUSH | |
| T12 | APPOINTMENT_SET | INITIATE_PUSH | system authorised | PENDING_INSPECTION_BOOKING | INTEGRATION { "endpointCode": "DLS_INSPECTION_BOOKING_PUSH", "async": true } (P3) |
||
| T13 | PENDING_INSPECTION_BOOKING | PUSH | system authorised (callback) | PENDING_INSPECTION | |||
| T14 | PENDING_INSPECTION | APPROVE_WITH_FORM | Level 1 officer (form F2) | PENDING_OFFICER_LEVEL_2 | OFFICE_ASSIGNMENT, level 2 |
O2 | |
| T15 | PENDING_INSPECTION | REJECT | Level 1 officer | REJECTED | N6 | ||
| T16 | PENDING_OFFICER_LEVEL_2 | TRANSITION | Level 2 officer | PENDING_OFFICER_LEVEL_1 | OFFICE_ASSIGNMENT, level 1 (send back) |
O1 | |
| T17 | PENDING_OFFICER_LEVEL_2 | REJECT | Level 2 officer | REJECTED | N6 | ||
| T18 | PENDING_OFFICER_LEVEL_2 | APPROVE | Level 2 officer (form F3) | LEVEL_2_APPROVED | INITIATE_PUSH | ||
| T19 | LEVEL_2_APPROVED | INITIATE_PUSH | system authorised | PENDING_PARTNER_DECISION | INTEGRATION { "endpointCode": "DLS_DECISION_PUSH", "async": true } (P4) |
||
| T20 | PENDING_PARTNER_DECISION | PUSH, end state one: decisionStatus EQ APPROVED |
system authorised (callback) | PENDING_LICENCE_DOCUMENT | INITIATE_PUSH | ||
| T21 | PENDING_PARTNER_DECISION | PUSH, end state two: decisionStatus not APPROVED |
system authorised (callback) | PENDING_OFFICER_LEVEL_2 | OFFICE_ASSIGNMENT, level 2 |
O3 | |
| T22 | PENDING_LICENCE_DOCUMENT | INITIATE_PUSH | system authorised | PENDING_LICENCE_FETCH | INTEGRATION { "endpointCode": "DLS_LICENCE_DOCUMENT_FETCH", "async": true } (P5) |
||
| T23 | PENDING_LICENCE_FETCH | PUSH | system authorised (callback) | APPROVED | N8 (licence attached) |
Checks:
INITIATE_PUSH; the push runs on the next system transition (T12, T19).decisionStatus, which P4 returns.Only if DLS needs them (open question 3). Both are sync and non-breaking, so no callback is waited for and a DLS failure never blocks the applicant.
| Push | Change |
|---|---|
| P6 on payment expiry | Add P6 to the non-breaking actions of T4 (already a system transition) |
| P6 on reject | T10, T15, T17 end in REJECTION_RECORDED with next event INITIATE_PUSH. Add REJECTION_RECORDED → INITIATE_PUSH (system) → REJECTED with P6 non-breaking and N6 |
| P7 on resubmit | T9 ends in RESUBMITTED with next event INITIATE_PUSH. Add RESUBMITTED → INITIATE_PUSH (system) → PENDING_OFFICER_LEVEL_1 with P7 non-breaking and N5 |
applicationDetails are saved onto the application straight away. If the call fails, the transition fails and the application stays in its current state. On submit, the applicant sees the partner error and no application reaches PAYMENT_PENDING (TC18 to TC20)./v1/integration-notification, and the state changes immediately. When DLS answers (section 7.5), the callback triggers the PUSH event. Fields in the callback's applicationDetails are saved onto the application.PUSH event routes with an end state condition on a field the partner returned (decisionStatus EQ APPROVED). The condition needs an end state two.parametersToRetain maps partner response fields to application fields. For P1, amount → amountToPay, which pricing then uses.| Value | Exists from |
|---|---|
dlsApplicationRef, amountToPay |
T1 (P1 response) |
billReferenceNumber, price, paymentExpiration |
T1 (bill generation) |
inspectionDateTime |
T11 (officer form F1) |
inspectionSlotId, inspectorName |
T13 (P3 callback) |
decisionStatus, licenceNumber, partnerDecisionReason |
T20 / T21 (P4 callback) |
LICENCE-Certificate-fileName, licenceIssueDate, licenceExpiryDate |
T23 (P5 callback) |
Notifications only use values that exist at their step. N7 is sent on T11, before P3 returns, so it shows the inspection date but not the inspector name.
| # | Key | Fields |
|---|---|---|
| F1 | PENDING_OFFICER_LEVEL_1-SET_APPOINTMENT |
inspectionDateTime: Inspection date and time (date-time, required, not in the past) · inspectionAddressConfirmed: I confirm the inspection address (checkbox, required) |
| F2 | PENDING_INSPECTION-APPROVE_WITH_FORM |
inspectionReport: Inspection report (file upload, PDF, required) · inspectionOutcome: Inspection outcome (radio, required: COMPLIANT: Compliant · COMPLIANT_WITH_CONDITIONS: Compliant with conditions) · inspectionConditions: Conditions (text area, required when inspectionOutcome = COMPLIANT_WITH_CONDITIONS) |
| F3 | PENDING_OFFICER_LEVEL_2-APPROVE |
decisionComments: Decision comments (text area) |
| Level | Assigned on | Responsibilities | Assignment logic |
|---|---|---|---|
| Level 1 | T7, T16 | Review, request for action, inspection booking, inspection report | TBC (for example by district of the premise) |
| Level 2 | T14, T21 | Final approval, send back, reject | TBC |
Office assignment steps also send an office assignment notification to the officers (O1, O2, O3).
Every placeholder carries !"": write ${applicantName!""}, never ${applicantName}. Only use values that exist at that step (section 5.5).
| Lang | SMS | Email subject | Email body |
|---|---|---|---|
| EN | Dear ${applicantName!""}, your application for ${serviceName!""} was submitted. Bill number: ${billReferenceNumber!""}. Amount: ${price!""} ${currency!""}. Pay before ${paymentExpiration!""}. For support, call 9099. | Application submitted | Dear ${applicantName!""}, Your application for ${serviceName!""} for ${businessName!""} was submitted. Application number: ${applicationNumber!""} Licensing reference: ${dlsApplicationRef!""} Bill number: ${billReferenceNumber!""} Amount due: ${price!""} ${currency!""} Pay before: ${paymentExpiration!""} Need help? Email support@irembo.com or call 9099. |
| FR | Cher(e) ${applicantName!""}, votre demande de ${serviceName!""} a été soumise. Facture : ${billReferenceNumber!""}. Montant : ${price!""} ${currency!""}. Payez avant le ${paymentExpiration!""}. Assistance : 9099. | Demande soumise | Cher(e) ${applicantName!""}, Votre demande de ${serviceName!""} pour ${businessName!""} a été soumise. Numéro de demande : ${applicationNumber!""} Référence de licence : ${dlsApplicationRef!""} Numéro de facture : ${billReferenceNumber!""} Montant à payer : ${price!""} ${currency!""} À payer avant le : ${paymentExpiration!""} Besoin d'aide ? Écrivez à support@irembo.com ou appelez le 9099. |
| RW | Kuri ${applicantName!""}, dosiye yanyu isaba ${serviceName!""} yoherejwe. Nomero yo kwishyuriraho: ${billReferenceNumber!""}. Amafaranga: ${price!""} ${currency!""}. Mwishyure mbere ya ${paymentExpiration!""}. Ubufasha: 9099. | Dosiye yoherejwe | Kuri ${applicantName!""}, Dosiye yanyu isaba ${serviceName!""} ya ${businessName!""} yoherejwe. Nomero ya dosiye: ${applicationNumber!""} Nomero y'uruhushya: ${dlsApplicationRef!""} Nomero yo kwishyuriraho: ${billReferenceNumber!""} Amafaranga yo kwishyura: ${price!""} ${currency!""} Mwishyure mbere ya: ${paymentExpiration!""} Mukeneye ubufasha, mwandikire support@irembo.com cyangwa muhamagare 9099. |
| Lang | SMS | Email subject | Email body |
|---|---|---|---|
| EN | Dear ${applicantName!""}, we received your payment of ${price!""} ${currency!""} for ${serviceName!""}. Application: ${applicationNumber!""}. Your application will now be reviewed. | Payment received | Dear ${applicantName!""}, We received your payment of ${price!""} ${currency!""} for ${serviceName!""}. Application number: ${applicationNumber!""} Your application will now be reviewed. We will contact you to book the inspection of your premise. Need help? Email support@irembo.com or call 9099. |
| FR | Cher(e) ${applicantName!""}, nous avons reçu votre paiement de ${price!""} ${currency!""} pour ${serviceName!""}. Demande : ${applicationNumber!""}. Votre demande va être examinée. | Paiement reçu | Cher(e) ${applicantName!""}, Nous avons reçu votre paiement de ${price!""} ${currency!""} pour ${serviceName!""}. Numéro de demande : ${applicationNumber!""} Votre demande va être examinée. Nous vous contacterons pour planifier l'inspection de votre local. Besoin d'aide ? Écrivez à support@irembo.com ou appelez le 9099. |
| RW | Kuri ${applicantName!""}, twakiriye ubwishyu bwanyu bwa ${price!""} ${currency!""} kuri ${serviceName!""}. Dosiye: ${applicationNumber!""}. Dosiye yanyu igiye gusuzumwa. | Ubwishyu bwakiriwe | Kuri ${applicantName!""}, Twakiriye ubwishyu bwanyu bwa ${price!""} ${currency!""} kuri ${serviceName!""}. Nomero ya dosiye: ${applicationNumber!""} Dosiye yanyu igiye gusuzumwa. Tuzabamenyesha igihe cy'igenzura ry'inyubako yanyu. Mukeneye ubufasha, mwandikire support@irembo.com cyangwa muhamagare 9099. |
| Lang | SMS | Email subject | Email body |
|---|---|---|---|
| EN | Dear ${applicantName!""}, the bill ${billReferenceNumber!""} for ${serviceName!""} has expired. Please apply again. For support, call 9099. | Payment expired | Dear ${applicantName!""}, The payment time for bill ${billReferenceNumber!""} (${serviceName!""}) has ended. Please submit a new application to get a new fee quote. Need help? Call 9099. |
| FR | Cher(e) ${applicantName!""}, la facture ${billReferenceNumber!""} pour ${serviceName!""} a expiré. Veuillez faire une nouvelle demande. Assistance : 9099. | Paiement expiré | Cher(e) ${applicantName!""}, Le délai de paiement de la facture ${billReferenceNumber!""} (${serviceName!""}) est dépassé. Soumettez une nouvelle demande pour obtenir de nouveaux frais. Besoin d'aide ? Appelez le 9099. |
| RW | Kuri ${applicantName!""}, igihe cyo kwishyura ${billReferenceNumber!""} kuri ${serviceName!""} cyarangiye. Mwongere musabe. Ubufasha: 9099. | Igihe cyo kwishyura cyarangiye | Kuri ${applicantName!""}, Igihe cyo kwishyura ${billReferenceNumber!""} (${serviceName!""}) cyarangiye. Mwongere musabe kugira ngo mubone igiciro gishya. Mukeneye ubufasha, muhamagare 9099. |
| Lang | SMS | Email subject | Email body |
|---|---|---|---|
| EN | Dear ${applicantName!""}, your application ${applicationNumber!""} for ${serviceName!""} needs changes. Log in to IremboGov to see the officer's comments and resubmit. | Action required on your application | Dear ${applicantName!""}, Your application ${applicationNumber!""} for ${serviceName!""} needs changes before it can continue. Log in to IremboGov, open the application to see the officer's comments, make the changes and resubmit. Need help? Email support@irembo.com or call 9099. |
| FR | Cher(e) ${applicantName!""}, votre demande ${applicationNumber!""} de ${serviceName!""} doit être modifiée. Connectez-vous à IremboGov pour voir les commentaires et la soumettre à nouveau. | Action requise sur votre demande | Cher(e) ${applicantName!""}, Votre demande ${applicationNumber!""} de ${serviceName!""} doit être modifiée avant de continuer. Connectez-vous à IremboGov, ouvrez la demande pour voir les commentaires de l'agent, faites les modifications et soumettez-la à nouveau. Besoin d'aide ? Écrivez à support@irembo.com ou appelez le 9099. |
| RW | Kuri ${applicantName!""}, dosiye yanyu ${applicationNumber!""} isaba ${serviceName!""} ikeneye gukosorwa. Mwinjire kuri IremboGov murebe ibisobanuro maze mwongere muyohereze. | Dosiye yanyu ikeneye gukosorwa | Kuri ${applicantName!""}, Dosiye yanyu ${applicationNumber!""} isaba ${serviceName!""} ikeneye gukosorwa mbere yo gukomeza. Mwinjire kuri IremboGov, mufungure dosiye murebe ibisobanuro by'umukozi, mukosore maze mwongere muyohereze. Mukeneye ubufasha, mwandikire support@irembo.com cyangwa muhamagare 9099. |
| Lang | SMS | Email subject | Email body |
|---|---|---|---|
| EN | Dear ${applicantName!""}, your application ${applicationNumber!""} for ${serviceName!""} was resubmitted and will be reviewed again. | Application resubmitted | Dear ${applicantName!""}, Your application ${applicationNumber!""} for ${serviceName!""} was resubmitted and will be reviewed again. Need help? Email support@irembo.com or call 9099. |
| FR | Cher(e) ${applicantName!""}, votre demande ${applicationNumber!""} de ${serviceName!""} a été soumise à nouveau et sera réexaminée. | Demande soumise à nouveau | Cher(e) ${applicantName!""}, Votre demande ${applicationNumber!""} de ${serviceName!""} a été soumise à nouveau et sera réexaminée. Besoin d'aide ? Écrivez à support@irembo.com ou appelez le 9099. |
| RW | Kuri ${applicantName!""}, dosiye yanyu ${applicationNumber!""} isaba ${serviceName!""} yongeye koherezwa kandi izongera isuzumwe. | Dosiye yongeye koherezwa | Kuri ${applicantName!""}, Dosiye yanyu ${applicationNumber!""} isaba ${serviceName!""} yongeye koherezwa kandi izongera isuzumwe. Mukeneye ubufasha, mwandikire support@irembo.com cyangwa muhamagare 9099. |
| Lang | SMS | Email subject | Email body |
|---|---|---|---|
| EN | Dear ${applicantName!""}, your application ${applicationNumber!""} for ${serviceName!""} was rejected. Log in to IremboGov to see the reason. For support, call 9099. | Application rejected | Dear ${applicantName!""}, Your application ${applicationNumber!""} for ${serviceName!""} was rejected. Log in to IremboGov and open the application to see the reason. Need help? Email support@irembo.com or call 9099. |
| FR | Cher(e) ${applicantName!""}, votre demande ${applicationNumber!""} de ${serviceName!""} a été rejetée. Connectez-vous à IremboGov pour voir le motif. Assistance : 9099. | Demande rejetée | Cher(e) ${applicantName!""}, Votre demande ${applicationNumber!""} de ${serviceName!""} a été rejetée. Connectez-vous à IremboGov et ouvrez la demande pour voir le motif. Besoin d'aide ? Écrivez à support@irembo.com ou appelez le 9099. |
| RW | Kuri ${applicantName!""}, dosiye yanyu ${applicationNumber!""} isaba ${serviceName!""} yanzwe. Mwinjire kuri IremboGov murebe impamvu. Ubufasha: 9099. | Dosiye yanzwe | Kuri ${applicantName!""}, Dosiye yanyu ${applicationNumber!""} isaba ${serviceName!""} yanzwe. Mwinjire kuri IremboGov mufungure dosiye murebe impamvu. Mukeneye ubufasha, mwandikire support@irembo.com cyangwa muhamagare 9099. |
Sent on T11, before P3 returns: the inspector name is not available yet.
| Lang | SMS | Email subject | Email body |
|---|---|---|---|
| EN | Dear ${applicantName!""}, the inspection of your premise for application ${applicationNumber!""} is set for ${inspectionDateTime!""}. Please be available at the premise. | Inspection appointment | Dear ${applicantName!""}, The inspection of your premise for application ${applicationNumber!""} (${serviceName!""}) is set for ${inspectionDateTime!""}. Please be available at the premise with the original documents you uploaded. Need help? Email support@irembo.com or call 9099. |
| FR | Cher(e) ${applicantName!""}, l'inspection de votre local pour la demande ${applicationNumber!""} est prévue le ${inspectionDateTime!""}. Merci d'être présent(e) au local. | Rendez-vous d'inspection | Cher(e) ${applicantName!""}, L'inspection de votre local pour la demande ${applicationNumber!""} (${serviceName!""}) est prévue le ${inspectionDateTime!""}. Merci d'être présent(e) au local avec les originaux des documents téléversés. Besoin d'aide ? Écrivez à support@irembo.com ou appelez le 9099. |
| RW | Kuri ${applicantName!""}, igenzura ry'inyubako yanyu kuri dosiye ${applicationNumber!""} riteganyijwe ku wa ${inspectionDateTime!""}. Muzabe muri aho. | Igihe cy'igenzura | Kuri ${applicantName!""}, Igenzura ry'inyubako yanyu kuri dosiye ${applicationNumber!""} (${serviceName!""}) riteganyijwe ku wa ${inspectionDateTime!""}. Muzabe muri ku nyubako mufite inyandiko z'umwimerere mwohereje. Mukeneye ubufasha, mwandikire support@irembo.com cyangwa muhamagare 9099. |
The email lists LICENCE-Certificate-fileName in attachmentFields. If that field is missing when the email is sent, the whole email is dropped, so N8 must only run on T23, after P5 has returned.
| Lang | SMS | Email subject | Email body |
|---|---|---|---|
| EN | Dear ${applicantName!""}, your trading licence ${licenceNumber!""} for ${businessName!""} has been issued. It is attached to the email we sent you. Application: ${applicationNumber!""}. | Trading licence issued | Dear ${applicantName!""}, Your application ${applicationNumber!""} for ${serviceName!""} was approved. Business: ${businessName!""} Licence number: ${licenceNumber!""} Valid until: ${licenceExpiryDate!""} Your trading licence is attached to this email. Need help? Email support@irembo.com or call 9099. |
| FR | Cher(e) ${applicantName!""}, votre licence ${licenceNumber!""} pour ${businessName!""} a été délivrée. Elle est jointe à l'e-mail envoyé. Demande : ${applicationNumber!""}. | Licence d'exploitation délivrée | Cher(e) ${applicantName!""}, Votre demande ${applicationNumber!""} de ${serviceName!""} a été approuvée. Entreprise : ${businessName!""} Numéro de licence : ${licenceNumber!""} Valable jusqu'au : ${licenceExpiryDate!""} Votre licence d'exploitation est jointe à cet e-mail. Besoin d'aide ? Écrivez à support@irembo.com ou appelez le 9099. |
| RW | Kuri ${applicantName!""}, uruhushya rwanyu ${licenceNumber!""} rwa ${businessName!""} rwatanzwe. Ruri kuri imeyili twaboherereje. Dosiye: ${applicationNumber!""}. | Uruhushya rwo gucuruza rwatanzwe | Kuri ${applicantName!""}, Dosiye yanyu ${applicationNumber!""} isaba ${serviceName!""} yemejwe. Ikigo: ${businessName!""} Nomero y'uruhushya: ${licenceNumber!""} Rizarangira: ${licenceExpiryDate!""} Uruhushya rwanyu rwo gucuruza ruri kuri iyi imeyili. Mukeneye ubufasha, mwandikire support@irembo.com cyangwa muhamagare 9099. |
| # | Transition | Email subject | Email body |
|---|---|---|---|
| O1 | T7, T16 | New trading licence application to review | Application ${applicationNumber!""} (${businessName!""}) has been assigned to your office for level 1 review. |
| O2 | T14 | Trading licence application for final approval | Application ${applicationNumber!""} (${businessName!""}) passed inspection and is waiting for level 2 approval. |
| O3 | T21 | Licensing decision not approved by DLS | DLS did not approve application ${applicationNumber!""} (${businessName!""}). Status: ${decisionStatus!""}. Reason: ${partnerDecisionReason!""}. |
Support routing: form lookup errors (L1 to L7) go to the institution that owns the data (provider in 6.1); payment and application problems go to Irembo (9099).
| Item | Value |
|---|---|
| Issued by | DLS, through P5 (section 7.11) |
| Saved as | LICENCE-Certificate-fileName (wiki: configure integration endpoint mapping to allow files in the response to be considered as certificates) |
| Format | |
| Sent to the applicant | Attached to N8 |
| Language | One document from DLS. TBC whether DLS issues it in several languages |
| Validity | licenceExpiryDate from DLS. Duration TBC (open question 5) |
| # | Title EN / FR / RW | EN | FR | RW |
|---|---|---|---|---|
| 1 | Payment / Paiement / Kwishyura | Use your bill number to pay within 72 hours with any method in the Payment options section. | Utilisez votre numéro de facture pour payer dans les 72 heures avec l'un des moyens de la section Options de paiement. | Koresha nomero yo kwishyuriraho wishyure mu masaha 72 ukoresheje bumwe mu buryo buri mu gice cy'Amahitamo yo kwishyura. |
| 2 | Inspection / Inspection / Igenzura | After review, you receive an SMS and an email with the date of the inspection of your premise. | Après l'examen, vous recevez un SMS et un e-mail avec la date de l'inspection de votre local. | Nyuma yo gusuzuma, uzabona ubutumwa bugufi na imeyili bikwereka itariki y'igenzura ry'inyubako yawe. |
| 3 | Licence / Licence / Uruhushya | Once approved, your trading licence is sent to you by email. | Une fois approuvée, votre licence d'exploitation vous est envoyée par e-mail. | Numara kwemezwa, uruhushya rwawe rwo gucuruza ruzakoherezwa kuri imeyili. |
| # | API | Endpoint code | Provider | Where | Mode | Operation | Documentation |
|---|---|---|---|---|---|---|---|
| L1 | Identity lookup | ID_LOOKUP_FETCH |
Identity registry (TBC) | Form, field 1 | Sync fetch | POST /identities/lookup |
6.4 |
| L2 | Phone ownership check | ID_PHONE_MATCH_FETCH |
Identity registry (TBC) | Form, field 6 | Sync fetch | POST /identities/phone-match |
6.5 |
| L3 | Taxpayer lookup | TIN_LOOKUP_FETCH |
Tax authority (TBC) | Form, field 8 | Sync fetch | POST /taxpayers/lookup |
6.6 |
| L4 | Tax compliance check | TAX_COMPLIANCE_FETCH |
Tax authority (TBC) | Form, field 15 | Sync fetch | POST /taxpayers/compliance |
6.7 |
| L5 | Business record lookup | BUSINESS_RECORD_FETCH |
Business registry (TBC) | Form, field 12 | Sync fetch | POST /businesses/lookup |
6.8 |
| L6 | Business activities | BUSINESS_ACTIVITIES_FETCH |
Business registry (TBC) | Form, field 16 | Sync fetch, paginated | POST /businesses/activities |
6.9 |
| L7 | Land parcel lookup | LAND_PARCEL_FETCH |
Land registry (TBC) | Form, field 17 | Sync fetch | POST /parcels/lookup |
6.10 |
| F1 | Licence fee quote | DLS_LICENCE_FEE_QUOTE_FETCH |
DLS | Form, field 29 | Sync fetch | POST /licence-fees/quotes |
7.6 |
| P1 | Create an application | DLS_CREATE_APPLICATION_PUSH |
DLS | T1 APPLY | Sync, INTEGRATION_UPDATE |
POST /applications |
7.7 |
| P2 | Confirm a payment | DLS_PAYMENT_CONFIRMATION_PUSH |
DLS | T5 | Async | POST /applications/{dlsApplicationRef}/payments |
7.8 |
| P3 | Book an inspection | DLS_INSPECTION_BOOKING_PUSH |
DLS | T12 | Async | POST /applications/{dlsApplicationRef}/inspections |
7.9 |
| P4 | Record the licensing decision | DLS_DECISION_PUSH |
DLS | T19 | Async | POST /applications/{dlsApplicationRef}/decisions |
7.10 |
| P5 | Get the licence document | DLS_LICENCE_DOCUMENT_FETCH |
DLS | T22 | Async | POST /licences/{licenceNumber}/document-requests |
7.11 |
| P6 | Cancel an application (optional) | DLS_CANCEL_APPLICATION_PUSH |
DLS | T4, reject (5.3.1) | Sync, non-breaking | POST /applications/{dlsApplicationRef}/cancellations |
7.12 |
| P7 | Update after resubmission (optional) | DLS_RESUBMIT_APPLICATION_PUSH |
DLS | Resubmit (5.3.1) | Sync, non-breaking | PUT /applications/{dlsApplicationRef} |
7.13 |
| # | Request mapping reads | Response → applicationDetails |
parametersToRetain |
Failure condition |
|---|---|---|---|---|
| F1 | Form: businessId, tin, businessActivity, districtCode, floorArea |
data.quoteReference → quoteReference, data.licenceFee → licenceFee |
responseCode ≠ "00", message from responseMessage |
|
| P1 | Application: all form fields in section 3.2 and the documents in 3.6 | data.dlsApplicationRef → dlsApplicationRef, data.amount → amount |
amount → amountToPay |
responseCode ≠ "00" |
| P2 | dlsApplicationRef, applicationNumber, payment transaction (bill number, amount, date) |
Callback: data.paymentStatus → dlsPaymentStatus |
Ack or callback status = FAILED |
|
| P3 | dlsApplicationRef, inspectionDateTime, inspectionAddressConfirmed, officer name |
Callback: inspectionSlotId, inspectorName, inspectionDateTime |
Ack or callback status = FAILED |
|
| P4 | dlsApplicationRef, F2 and F3 fields |
Callback: decisionStatus, licenceNumber, partnerDecisionReason |
Ack or callback status = FAILED (a REJECTED decision is a success, routed by T21) |
|
| P5 | dlsApplicationRef, licenceNumber |
Callback: document → LICENCE-Certificate-fileName, licenceIssueDate, licenceExpiryDate |
Ack or callback status = FAILED |
|
| P6 | dlsApplicationRef, reason |
None | responseCode ≠ "00" (logged only) |
|
| P7 | Same as P1 | None | responseCode ≠ "00" (logged only) |
L1 to L7 are configured as described in the IremboHub configuration table under each API (6.4 to 6.10).
Proposed contracts. The providers of L1 to L7 are TBC (open question 7). The contracts below give the integration team one starting point; if a provider's real contract differs, keep the IremboHub field keys in section 3.2 and adapt the request and response mappings.
| Item | Value |
|---|---|
| Base URL | One per provider, TBC: $IDENTITY_BASE_URL (L1, L2), $TAX_BASE_URL (L3, L4), $BUSINESS_BASE_URL (L5, L6), $LAND_BASE_URL (L7), each ending in /api/v1 |
| Method | POST with a JSON body for every lookup, so national IDs, TINs and UPIs never appear in URLs or logs of query strings |
| Formats | As in 7.1: JSON UTF-8, YYYY-MM-DD dates, identifiers as strings |
| Authentication | As in 7.2 (OAuth 2.0 client credentials) unless the provider uses another pattern the integration engine supports. Scope: read only |
| Headers | Authorization, Content-Type, Accept, X-Request-Id (7.3). Lookups are read only, so no Idempotency-Key |
| Response envelope | As in 7.4: responseCode, responseMessage, requestId, data. Error codes start with ID-, TAX-, BUS- or LAND- |
| Timeout | 10 seconds. The applicant waits on the form, so no automatic retry; they can trigger the lookup again |
| Messages | responseMessage is shown to the applicant (3.4), so providers return it in plain language. EN only from the providers; FR and RW are shown as returned |
POST /identities/lookup
Endpoint code ID_LOOKUP_FETCH · sync fetch · form field 1 (nationalId)
Returns the identity details of the person who holds a national ID. The ID is sent in the body, never in the URL.
Request properties
| Property | Type | Required | Source | Description |
|---|---|---|---|---|
nationalId |
string | Yes | nationalId |
16 digits |
Sample request
curl --request POST \
--url "$IDENTITY_BASE_URL/identities/lookup" \
--header "Authorization: Bearer $IDENTITY_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--data '{"nationalId":"1199080012345678"}'
200 response
{
"responseCode": "00",
"responseMessage": "Success",
"requestId": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"data": {
"nationalId": "1199080012345678",
"surname": "UWASE",
"otherNames": "Aline",
"gender": "F",
"dateOfBirth": "1990-04-12"
}
}
Responses
| Status | responseCode |
Meaning | Shown to the applicant |
|---|---|---|---|
| 200 OK | 00 |
Person found | |
| 404 Not Found | ID-404-01 |
No person with this national ID | "We could not find a person with this ID number." |
| 400 Bad Request | ID-400-01 |
Missing or invalid field | Generic error |
| 401 / 403 | ID-401-01 |
Token missing, expired or without the scope | Generic error (support) |
| 500 / 503 | ID-500-01 |
Provider unavailable | "The service is not available. Please try again in a few minutes." |
IremboHub configuration
| Item | Value |
|---|---|
| Request mapping | nationalId ← field nationalId |
| Response → form fields | data.surname → surname, data.otherNames → otherNames, data.gender → gender, data.dateOfBirth → dateOfBirth |
| Failure condition | responseCode ≠ "00", message from responseMessage |
POST /identities/phone-match
Endpoint code ID_PHONE_MATCH_FETCH · sync fetch · form field 6 (phoneNumber)
Checks that a phone number is registered to the national ID entered in field 1.
Request properties
| Property | Type | Required | Source | Description |
|---|---|---|---|---|
nationalId |
string | Yes | nationalId |
16 digits |
phoneNumber |
string | Yes | phoneNumber |
10 digits, e.g. 0788100200 |
Sample request
curl --request POST \
--url "$IDENTITY_BASE_URL/identities/phone-match" \
--header "Authorization: Bearer $IDENTITY_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--data '{"nationalId":"1199080012345678","phoneNumber":"0788100200"}'
200 response
{
"responseCode": "00",
"responseMessage": "Success",
"requestId": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"data": {
"match": true
}
}
Responses
| Status | responseCode |
Meaning | Shown to the applicant |
|---|---|---|---|
| 200 OK | 00 |
The phone is registered to the ID (data.match true) |
|
| 422 Unprocessable | ID-422-01 |
The phone is not registered to the ID | "This phone number is not registered to this ID number." |
| 404 Not Found | ID-404-01 |
No person with this national ID | "We could not find a person with this ID number." |
| 400 Bad Request | ID-400-01 |
Missing or invalid field | Generic error |
| 401 / 403 | ID-401-01 |
Token missing, expired or without the scope | Generic error (support) |
| 500 / 503 | ID-500-01 |
Provider unavailable | "The service is not available. Please try again in a few minutes." |
IremboHub configuration
| Item | Value |
|---|---|
| Request mapping | nationalId ← nationalId, phoneNumber ← phoneNumber (useMultiplePayloadKeys: true) |
| Response → form fields | None (check only) |
| Failure condition | responseCode ≠ "00" or data.match ≠ true |
POST /taxpayers/lookup
Endpoint code TIN_LOOKUP_FETCH · sync fetch · form field 8 (tin)
Returns the registered name, category and address of a taxpayer.
Request properties
| Property | Type | Required | Source | Description |
|---|---|---|---|---|
tin |
string | Yes | tin |
9 digits |
Sample request
curl --request POST \
--url "$TAX_BASE_URL/taxpayers/lookup" \
--header "Authorization: Bearer $TAX_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--data '{"tin":"100123456"}'
200 response
{
"responseCode": "00",
"responseMessage": "Success",
"requestId": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"data": {
"tin": "100123456",
"taxpayerName": "Inzozi Retail Ltd",
"taxpayerCategory": "Small",
"registeredAddress": "Kicukiro, Niboye"
}
}
Responses
| Status | responseCode |
Meaning | Shown to the applicant |
|---|---|---|---|
| 200 OK | 00 |
Taxpayer found | |
| 404 Not Found | TAX-404-01 |
No taxpayer with this TIN | "We could not find a taxpayer with this TIN." |
| 400 Bad Request | TAX-400-01 |
Missing or invalid field | Generic error |
| 401 / 403 | TAX-401-01 |
Token missing, expired or without the scope | Generic error (support) |
| 500 / 503 | TAX-500-01 |
Provider unavailable | "The service is not available. Please try again in a few minutes." |
IremboHub configuration
| Item | Value |
|---|---|
| Request mapping | tin ← tin |
| Response → form fields | data.taxpayerName → businessName, data.taxpayerCategory → taxpayerCategory, data.registeredAddress → registeredAddress |
| Failure condition | responseCode ≠ "00", message from responseMessage |
POST /taxpayers/compliance
Endpoint code TAX_COMPLIANCE_FETCH · sync fetch · form field 15 (taxArrearsCheck, hidden)
Tells whether the taxpayer has unpaid taxes. A business with arrears cannot apply.
Request properties
| Property | Type | Required | Source | Description |
|---|---|---|---|---|
tin |
string | Yes | tin |
9 digits |
Sample request
curl --request POST \
--url "$TAX_BASE_URL/taxpayers/compliance" \
--header "Authorization: Bearer $TAX_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--data '{"tin":"100123456"}'
200 response
{
"responseCode": "00",
"responseMessage": "Success",
"requestId": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"data": {
"tin": "100123456",
"hasArrears": false
}
}
Responses
| Status | responseCode |
Meaning | Shown to the applicant |
|---|---|---|---|
| 200 OK | 00 |
No arrears (data.hasArrears false) |
|
| 422 Unprocessable | TAX-422-01 |
The taxpayer has arrears | "This business has unpaid taxes. Settle them before you apply." |
| 404 Not Found | TAX-404-01 |
No taxpayer with this TIN | "We could not find a taxpayer with this TIN." |
| 400 Bad Request | TAX-400-01 |
Missing or invalid field | Generic error |
| 401 / 403 | TAX-401-01 |
Token missing, expired or without the scope | Generic error (support) |
| 500 / 503 | TAX-500-01 |
Provider unavailable | "The service is not available. Please try again in a few minutes." |
IremboHub configuration
| Item | Value |
|---|---|
| Request mapping | tin ← tin (FIELD_LISTENER) |
| Response → form fields | None (check only) |
| Failure condition | responseCode ≠ "00" or data.hasArrears = true |
POST /businesses/lookup
Endpoint code BUSINESS_RECORD_FETCH · sync fetch · form field 12 (businessRecord, hidden)
Returns the business registration ID and status of the company that holds the TIN. Only active companies can apply.
Request properties
| Property | Type | Required | Source | Description |
|---|---|---|---|---|
tin |
string | Yes | tin |
9 digits |
Sample request
curl --request POST \
--url "$BUSINESS_BASE_URL/businesses/lookup" \
--header "Authorization: Bearer $BUSINESS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--data '{"tin":"100123456"}'
200 response
{
"responseCode": "00",
"responseMessage": "Success",
"requestId": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"data": {
"businessId": "BR-104523",
"businessName": "Inzozi Retail Ltd",
"companyStatus": "ACTIVE",
"registrationDate": "2019-03-14"
}
}
Responses
| Status | responseCode |
Meaning | Shown to the applicant |
|---|---|---|---|
| 200 OK | 00 |
Company found. companyStatus: ACTIVE, SUSPENDED, DISSOLVED or IN_LIQUIDATION |
|
| 404 Not Found | BUS-404-01 |
No company with this TIN | "We could not find a company with this TIN." |
| 400 Bad Request | BUS-400-01 |
Missing or invalid field | Generic error |
| 401 / 403 | BUS-401-01 |
Token missing, expired or without the scope | Generic error (support) |
| 500 / 503 | BUS-500-01 |
Provider unavailable | "The service is not available. Please try again in a few minutes." |
IremboHub configuration
| Item | Value |
|---|---|
| Request mapping | tin ← tin (FIELD_LISTENER) |
| Response → form fields | data.businessId → businessId, data.companyStatus → companyStatus |
| Failure condition | responseCode ≠ "00" or data.companyStatus ≠ "ACTIVE". Form message: "This company is not active. Only active companies can apply." |
POST /businesses/activities
Endpoint code BUSINESS_ACTIVITIES_FETCH · sync fetch · form field 16 (businessActivity, paginated dropdown)
Lists the registered activity lines of the company, one page at a time, for the activity dropdown.
Request properties
| Property | Type | Required | Source | Description |
|---|---|---|---|---|
tin |
string | Yes | tin |
9 digits |
page |
number | No | Dropdown | First page is 0. Default 0 |
size |
number | No | Dropdown | Items per page, 1 to 100. Default 20 |
search |
string | No | Dropdown | Text typed by the applicant, matched on code or description |
Sample request
curl --request POST \
--url "$BUSINESS_BASE_URL/businesses/activities" \
--header "Authorization: Bearer $BUSINESS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--data '{"tin":"100123456","page":0,"size":20}'
200 response
{
"responseCode": "00",
"responseMessage": "Success",
"requestId": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"data": {
"items": [
{
"code": "4711",
"description": "Retail sale in non-specialised stores"
},
{
"code": "5610",
"description": "Restaurants and mobile food service activities"
}
],
"page": 0,
"size": 20,
"totalItems": 2
}
}
Responses
| Status | responseCode |
Meaning | Shown to the applicant |
|---|---|---|---|
| 200 OK | 00 |
Page of activities. An empty items list is a success |
|
| 404 Not Found | BUS-404-01 |
No company with this TIN | "We could not find a company with this TIN." |
| 400 Bad Request | BUS-400-01 |
Missing or invalid field | Generic error |
| 401 / 403 | BUS-401-01 |
Token missing, expired or without the scope | Generic error (support) |
| 500 / 503 | BUS-500-01 |
Provider unavailable | "The service is not available. Please try again in a few minutes." |
IremboHub configuration
| Item | Value |
|---|---|
| Request mapping | tin ← tin (FIELD_LISTENER), paging from the dropdown |
| Response → form fields | dataField items, bindLabel description, bindValue code (stored in businessActivity). Empty list message: "No registered activity was found for this company." |
| Failure condition | responseCode ≠ "00" |
POST /parcels/lookup
Endpoint code LAND_PARCEL_FETCH · sync fetch · form field 17 (landUpi, trigger button)
Returns the owners, location and land use of a parcel. The premise must be on land with owners and a commercial or mixed land use.
Request properties
| Property | Type | Required | Source | Description |
|---|---|---|---|---|
upi |
string | Yes | landUpi |
UPI, e.g. 1/03/02/01/1234 |
Sample request
curl --request POST \
--url "$LAND_BASE_URL/parcels/lookup" \
--header "Authorization: Bearer $LAND_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--data '{"upi":"1/03/02/01/1234"}'
200 response
{
"responseCode": "00",
"responseMessage": "Success",
"requestId": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"data": {
"upi": "1/03/02/01/1234",
"owners": [
{
"fullName": "Jean Claude HABIMANA",
"share": "100%"
}
],
"province": "Kigali City",
"district": "Kicukiro",
"districtCode": "0102",
"sector": "Niboye",
"cell": "Gatare",
"village": "Amahoro",
"landUse": "COMMERCIAL"
}
}
Responses
| Status | responseCode |
Meaning | Shown to the applicant |
|---|---|---|---|
| 200 OK | 00 |
Parcel found. landUse: COMMERCIAL, MIXED_USE, RESIDENTIAL, AGRICULTURAL, INDUSTRIAL or PUBLIC |
|
| 404 Not Found | LAND-404-01 |
No parcel with this UPI | "We could not find land with this UPI." |
| 400 Bad Request | LAND-400-01 |
Missing or invalid field | Generic error |
| 401 / 403 | LAND-401-01 |
Token missing, expired or without the scope | Generic error (support) |
| 500 / 503 | LAND-500-01 |
Provider unavailable | "The service is not available. Please try again in a few minutes." |
IremboHub configuration
| Item | Value |
|---|---|
| Request mapping | upi ← landUpi |
| Response → form fields | data.owners[].fullName joined with ", " → landOwners; data.province, data.district, data.districtCode, data.sector, data.cell, data.village, data.landUse → fields 19 to 25 |
| Failure condition | responseCode ≠ "00", or data.owners empty ("This land has no registered owner."), or data.landUse not COMMERCIAL or MIXED_USE ("The land use of this parcel does not allow a commercial premise.") |
responseCode |
HTTP | API | Meaning | Shown to |
|---|---|---|---|---|
00 |
200 | All | Success | |
ID-400-01, TAX-400-01, BUS-400-01, LAND-400-01 |
400 | All | Missing or invalid field | Applicant (generic error) |
ID-401-01, TAX-401-01, BUS-401-01, LAND-401-01 |
401 / 403 | All | Token missing, expired or without the scope | Support |
ID-404-01 |
404 | L1, L2 | No person with this national ID | Applicant |
ID-422-01 |
422 | L2 | Phone number not registered to the ID | Applicant |
TAX-404-01 |
404 | L3, L4 | No taxpayer with this TIN | Applicant |
TAX-422-01 |
422 | L4 | Taxpayer has arrears | Applicant |
BUS-404-01 |
404 | L5, L6 | No company with this TIN | Applicant |
LAND-404-01 |
404 | L7 | No parcel with this UPI | Applicant |
ID-500-01, TAX-500-01, BUS-500-01, LAND-500-01 |
500 / 503 | All | Provider unavailable | Applicant ("try again in a few minutes") |
Inactive company (L5) and land without owners or with the wrong land use (L7) are not provider errors: the provider answers 00 and the IremboHub failure condition blocks the form with the message in 6.8 and 6.10.
Proposed, not yet agreed. This contract is written by Irembo so that DLS and the integration team have one starting point. Paths, field names and codes may change once DLS confirms them (open question 1). If they change, keep the IremboHub field names in section 6.2 and adapt the mappings in the integration engine.
| Item | Value |
|---|---|
| Base URL | https://{dls-host}/api/v1 (host TBC, one for UAT and one for production) |
| Protocol | HTTPS, TLS 1.2 or higher |
| Data format | JSON, UTF-8. Requests send Content-Type: application/json and Accept: application/json |
| Dates | YYYY-MM-DD |
| Date-times | RFC 3339 with offset, for example 2026-10-20T09:00:00+02:00 |
| Amounts | Whole numbers in RWF, as JSON numbers (150000, never "150,000 RWF") |
| Identifiers | Strings, even when they contain only digits (national ID, TIN, UPI) |
| Compatibility | Clients ignore response properties they do not recognise |
Proposed: OAuth 2.0 client credentials. Any authentication pattern the integration engine supports is acceptable if DLS prefers another one.
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&scope=licensing.write
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "licensing.write"
}
| Header | Required | Value |
|---|---|---|
Authorization |
Yes | Bearer {access_token} |
Content-Type |
Yes (with a body) | application/json |
X-Request-Id |
Yes | A UUID per call, echoed in the response and the callback, used for tracing |
Idempotency-Key |
Yes on POST, except F1 (7.6) |
{applicationNumber}-{operation}, for example APP-2026-000123-CREATE. A repeat with the same key returns the first result |
Every response, success or error, uses the same envelope. IremboHub reads responseCode for the failure condition and responseMessage for the message shown to the applicant.
{
"responseCode": "00",
"responseMessage": "Success",
"requestId": "6f1c2a0e-5b7d-4d1e-9a51-2f0b8c3e7d10",
"data": { }
}
{
"responseCode": "DLS-422-02",
"responseMessage": "This business activity does not need a trading licence for a commercial premise.",
"requestId": "6f1c2a0e-5b7d-4d1e-9a51-2f0b8c3e7d10",
"errors": [
{ "field": "activityCode", "message": "Activity 4711 is not licensable" }
]
}
| HTTP status | Meaning |
|---|---|
200 |
Success (sync), or a repeated request with the same Idempotency-Key |
201 |
Resource created (P1) |
202 |
Accepted for async processing; the result comes in the callback (P2 to P5) |
400 |
Malformed request or missing field |
401 / 403 |
Token missing, expired or without the scope |
404 |
Business, application or licence not found |
409 |
Idempotency-Key reused with a different body |
410 |
Fee quote expired |
422 |
Business rule rejected the request |
500 / 503 |
DLS error. IremboHub retries |
P2 to P5 are asynchronous:
callbackUrl (set by the integration engine) and applicationNumber.202 Accepted at once.POST {callbackUrl} with the result.data into applicationDetails and notifies the application engine (/v1/integration-notification), which fires the PUSH event.202 acknowledgement:
{
"responseCode": "00",
"responseMessage": "Accepted",
"requestId": "0b9e4c7a-1f2d-4a3b-8c5d-6e7f8a9b0c1d",
"data": { "status": "ACCEPTED" }
}
Callback body (common to all async operations):
| Property | Type | Required | Description |
|---|---|---|---|
requestId |
string | Yes | X-Request-Id of the original request |
applicationNumber |
string | Yes | IremboHub application number |
dlsApplicationRef |
string | Yes | DLS reference |
event |
string | Yes | PAYMENT_CONFIRMED, INSPECTION_BOOKED, DECISION_RECORDED or LICENCE_ISSUED |
status |
string | Yes | SUCCESS or FAILED |
responseMessage |
string | Yes | Readable result, shown in logs and to support |
data |
object | When SUCCESS |
Operation result (see each operation) |
timestamp |
date-time | Yes | When DLS produced the result |
Callback rules:
PENDING_ state forever. DLS must always call back, including on failure (status = FAILED).2xx, with increasing delays, for at least 24 hours.requestId must not change the result.POST /licence-fees/quotes · endpoint code DLS_LICENCE_FEE_QUOTE_FETCH · sync · called from the form (field 29)
Returns the fee for one business, activity, district and floor area, and a quote reference that P1 must send back.
Request properties
| Property | Type | Required | Source | Description |
|---|---|---|---|---|
businessId |
string | Yes | businessId |
Business registration ID |
tin |
string | No | tin |
9-digit TIN |
activityCode |
string | Yes | businessActivity |
Activity code from L6 |
districtCode |
string | Yes | districtCode |
District of the premise |
floorArea |
number | Yes | floorArea |
Square metres, minimum 1 |
{
"businessId": "BR-104523",
"tin": "100123456",
"activityCode": "4711",
"districtCode": "0102",
"floorArea": 85
}
200 response
| Property | Type | Description | Saved as |
|---|---|---|---|
data.quoteReference |
string | Quote to send in P1 | quoteReference |
data.licenceFee |
number | Fee in RWF | licenceFee |
data.currency |
string | Always RWF |
|
data.validUntil |
date-time | End of quote validity. At least 72 hours after issue | |
data.feeBreakdown |
array | { item, amount } lines, for display only |
{
"responseCode": "00",
"responseMessage": "Success",
"requestId": "a3f1c9d2-7e4b-4c2a-9f10-1b2c3d4e5f60",
"data": {
"quoteReference": "DLS-Q-2026-000871",
"licenceFee": 150000,
"currency": "RWF",
"validUntil": "2026-10-23T10:15:00+02:00",
"feeBreakdown": [
{ "item": "Base trading licence fee", "amount": 100000 },
{ "item": "Floor area (85 m²)", "amount": 50000 }
]
}
}
Errors
| HTTP | responseCode |
When |
|---|---|---|
| 404 | DLS-404-01 |
Business not known to DLS |
| 422 | DLS-422-01 |
District code unknown |
| 422 | DLS-422-02 |
Activity not licensable at a commercial premise |
Sample request
F1 is called from the form, before an application number exists, so it sends no Idempotency-Key. Asking for the same quote twice is harmless.
curl --request POST \
--url "$DLS_BASE_URL/licence-fees/quotes" \
--header "Authorization: Bearer $DLS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--data '{"businessId":"BR-104523","tin":"100123456","activityCode":"4711","districtCode":"0102","floorArea":85}'
POST /applications · endpoint code DLS_CREATE_APPLICATION_PUSH · sync, INTEGRATION_UPDATE on T1
Records the application in DLS, locks the quote and returns the amount to pay. If it fails, submission fails.
Request properties
| Property | Type | Required | Source |
|---|---|---|---|
applicationNumber |
string | Yes | IremboHub application number |
quoteReference |
string | Yes | quoteReference |
applicant.nationalId |
string | Yes | nationalId |
applicant.surname, applicant.otherNames |
string | Yes | surname, otherNames |
applicant.gender, applicant.dateOfBirth |
string, date | Yes | gender, dateOfBirth |
applicant.phoneNumber, applicant.email |
string | Yes | phoneNumber, email |
business.tin, business.businessId, business.name |
string | Yes | tin, businessId, businessName |
business.taxpayerCategory, business.registeredAddress |
string | No | taxpayerCategory, registeredAddress |
business.activityCode |
string | Yes | businessActivity |
premise.upi |
string | Yes | landUpi |
premise.province / district / districtCode / sector / cell / village |
string | Yes | Fields 19 to 24 |
premise.landUse |
string | Yes | landUse |
premise.ownsPremise |
boolean | Yes | ownsPremise (YES → true) |
premise.floorArea |
number | Yes | floorArea |
premise.openingHours |
string | Yes | openingHours |
documents[] |
array | Yes | Files in section 3.6: documentType, fileName, contentType, content (base64). In the mapping, content = getAttachmentContent(<field key>) (see the note under the sample request). TBC with DLS |
{
"applicationNumber": "APP-2026-000123",
"quoteReference": "DLS-Q-2026-000871",
"applicant": {
"nationalId": "1199080012345678",
"surname": "UWASE",
"otherNames": "Aline",
"gender": "F",
"dateOfBirth": "1990-04-12",
"phoneNumber": "0788100200",
"email": "aline.uwase@example.com"
},
"business": {
"tin": "100123456",
"businessId": "BR-104523",
"name": "Inzozi Retail Ltd",
"taxpayerCategory": "Small",
"registeredAddress": "Kicukiro, Niboye",
"activityCode": "4711"
},
"premise": {
"upi": "1/03/02/01/1234",
"province": "Kigali City",
"district": "Kicukiro",
"districtCode": "0102",
"sector": "Niboye",
"cell": "Gatare",
"village": "Amahoro",
"landUse": "COMMERCIAL",
"ownsPremise": false,
"floorArea": 85,
"openingHours": "Mon-Sat 08:00-20:00"
},
"documents": [
{ "documentType": "LEASE_AGREEMENT", "fileName": "lease.pdf", "contentType": "application/pdf", "content": "JVBERi0xLjcK..." },
{ "documentType": "OCCUPANCY_PERMIT", "fileName": "occupancy.pdf", "contentType": "application/pdf", "content": "JVBERi0xLjcK..." },
{ "documentType": "FIRE_SAFETY_CERTIFICATE", "fileName": "fire.pdf", "contentType": "application/pdf", "content": "JVBERi0xLjcK..." },
{ "documentType": "FLOOR_PLAN", "fileName": "plan.png", "contentType": "image/png", "content": "iVBORw0KGgo..." }
]
}
201 response
| Property | Type | Description | Saved as |
|---|---|---|---|
data.dlsApplicationRef |
string | DLS reference, used in every later call | dlsApplicationRef |
data.amount |
number | Amount to charge, plain number in RWF | amount, retained as amountToPay |
data.currency |
string | RWF |
|
data.status |
string | PENDING_PAYMENT |
{
"responseCode": "00",
"responseMessage": "Application created",
"requestId": "c2d4e6f8-0a1b-4c3d-8e5f-7a9b1c3d5e7f",
"data": {
"dlsApplicationRef": "DLS-APP-2026-004512",
"amount": 150000,
"currency": "RWF",
"status": "PENDING_PAYMENT"
}
}
Errors
| HTTP | responseCode |
When |
|---|---|---|
| 200 | 00 |
Same Idempotency-Key again: the first result is returned |
| 409 | DLS-409-01 |
Idempotency-Key reused with a different body |
| 410 | DLS-410-01 |
Quote expired. The applicant must refresh the fee |
| 422 | DLS-422-03 |
An active licence already exists for this business and premise |
| 422 | DLS-422-04 |
A required document is missing |
Sample request
curl --request POST \
--url "$DLS_BASE_URL/applications" \
--header "Authorization: Bearer $DLS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--header "Idempotency-Key: APP-2026-000123-CREATE" \
--data @p1-create-application.json
p1-create-application.json holds the request body above. In the IremboHub request mapping, each document's content is getAttachmentContent(<field key>) (base64). If DLS prefers links, use generateAttachmentURL(<field key>) and send downloadUrl instead of content (wiki: Integration Management › Attachment submission).
POST /applications/{dlsApplicationRef}/payments · endpoint code DLS_PAYMENT_CONFIRMATION_PUSH · async, T5
| Property | Type | Required | Source |
|---|---|---|---|
applicationNumber |
string | Yes | Application |
billNumber |
string | Yes | billReferenceNumber |
amountPaid |
number | Yes | Payment transaction |
currency |
string | Yes | RWF |
paymentDate |
date-time | Yes | Payment transaction |
callbackUrl |
string | Yes | Integration engine |
{
"applicationNumber": "APP-2026-000123",
"billNumber": "880012345678",
"amountPaid": 150000,
"currency": "RWF",
"paymentDate": "2026-10-20T11:42:07+02:00",
"callbackUrl": "https://{integration-engine-host}/callbacks/dls"
}
Response: 202 acknowledgement (7.5). Callback event = PAYMENT_CONFIRMED:
{
"requestId": "1d2e3f40-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
"applicationNumber": "APP-2026-000123",
"dlsApplicationRef": "DLS-APP-2026-004512",
"event": "PAYMENT_CONFIRMED",
"status": "SUCCESS",
"responseMessage": "Payment recorded",
"data": { "paymentStatus": "CONFIRMED" },
"timestamp": "2026-10-20T11:42:30+02:00"
}
Errors: 404 DLS-404-02 application not found · 422 DLS-422-05 amount differs from the quoted amount.
Responses
| Status | responseCode |
Meaning |
|---|---|---|
| 202 Accepted | 00 |
Accepted. The result comes in the callback (7.5) |
| 200 OK | 00 |
Same Idempotency-Key again: the first acknowledgement is returned |
| 400 Bad Request | DLS-400-01 |
Missing or invalid field (see errors) |
| 401 / 403 | DLS-401-01 |
Token missing, expired or without the scope |
| 404 Not Found | DLS-404-02 |
Application not found |
| 422 Unprocessable | DLS-422-05 |
Amount paid differs from the quoted amount |
| 500 / 503 | DLS-500-01 |
DLS unavailable. IremboHub retries |
Sample request
curl --request POST \
--url "$DLS_BASE_URL/applications/DLS-APP-2026-004512/payments" \
--header "Authorization: Bearer $DLS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--header "Idempotency-Key: APP-2026-000123-PAYMENT" \
--data '{"applicationNumber":"APP-2026-000123","billNumber":"880012345678","amountPaid":150000,"currency":"RWF","paymentDate":"2026-10-20T11:42:07+02:00","callbackUrl":"https://{integration-engine-host}/callbacks/dls"}'
POST /applications/{dlsApplicationRef}/inspections · endpoint code DLS_INSPECTION_BOOKING_PUSH · async, T12
| Property | Type | Required | Source |
|---|---|---|---|
applicationNumber |
string | Yes | Application |
inspectionDateTime |
date-time | Yes | F1 inspectionDateTime |
addressConfirmed |
boolean | Yes | F1 inspectionAddressConfirmed |
requestedBy |
string | Yes | Name of the level 1 officer |
callbackUrl |
string | Yes | Integration engine |
{
"applicationNumber": "APP-2026-000123",
"inspectionDateTime": "2026-10-23T09:00:00+02:00",
"addressConfirmed": true,
"requestedBy": "Jean Bosco NIYONZIMA",
"callbackUrl": "https://{integration-engine-host}/callbacks/dls"
}
Callback event = INSPECTION_BOOKED:
{
"requestId": "2e3f4051-6b7c-4d8e-9f01-1b2c3d4e5f60",
"applicationNumber": "APP-2026-000123",
"dlsApplicationRef": "DLS-APP-2026-004512",
"event": "INSPECTION_BOOKED",
"status": "SUCCESS",
"responseMessage": "Inspection booked",
"data": {
"inspectionSlotId": "INSP-2026-00342",
"inspectorName": "Claudine MUKAMANA",
"inspectionDateTime": "2026-10-23T09:00:00+02:00"
},
"timestamp": "2026-10-20T14:05:11+02:00"
}
Errors: 422 DLS-422-06 no inspector available at that time (sent as a FAILED callback). How a failed callback is handled in the workflow is TBC (open question 9).
Responses
| Status | responseCode |
Meaning |
|---|---|---|
| 202 Accepted | 00 |
Accepted. The result comes in the callback (7.5) |
| 200 OK | 00 |
Same Idempotency-Key again: the first acknowledgement is returned |
| 400 Bad Request | DLS-400-01 |
Missing or invalid field (see errors) |
| 401 / 403 | DLS-401-01 |
Token missing, expired or without the scope |
| 404 Not Found | DLS-404-02 |
Application not found |
Callback FAILED |
DLS-422-06 |
No inspector available at that time |
| 500 / 503 | DLS-500-01 |
DLS unavailable. IremboHub retries |
Sample request
curl --request POST \
--url "$DLS_BASE_URL/applications/DLS-APP-2026-004512/inspections" \
--header "Authorization: Bearer $DLS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--header "Idempotency-Key: APP-2026-000123-INSPECTION" \
--data '{"applicationNumber":"APP-2026-000123","inspectionDateTime":"2026-10-23T09:00:00+02:00","addressConfirmed":true,"requestedBy":"Jean Bosco NIYONZIMA","callbackUrl":"https://{integration-engine-host}/callbacks/dls"}'
A second booking for the same application (after a send back, T16) uses a new key: APP-2026-000123-INSPECTION-2.
POST /applications/{dlsApplicationRef}/decisions · endpoint code DLS_DECISION_PUSH · async, T19
| Property | Type | Required | Source |
|---|---|---|---|
applicationNumber |
string | Yes | Application |
officerDecision |
string | Yes | Always APPROVED (P4 only runs after level 2 approves) |
decisionComments |
string | No | F3 decisionComments |
inspection.outcome |
string | Yes | F2 inspectionOutcome: COMPLIANT or COMPLIANT_WITH_CONDITIONS |
inspection.conditions |
string | When with conditions | F2 inspectionConditions |
inspection.report |
object | Yes | F2 inspectionReport: fileName, contentType, content (base64) |
inspection.inspectionSlotId |
string | Yes | inspectionSlotId from P3 |
approvedBy |
string | Yes | Name of the level 2 officer |
callbackUrl |
string | Yes | Integration engine |
{
"applicationNumber": "APP-2026-000123",
"officerDecision": "APPROVED",
"decisionComments": "All documents verified on site.",
"inspection": {
"inspectionSlotId": "INSP-2026-00342",
"outcome": "COMPLIANT_WITH_CONDITIONS",
"conditions": "Install a second fire extinguisher within 30 days.",
"report": { "fileName": "inspection-report.pdf", "contentType": "application/pdf", "content": "JVBERi0xLjcK..." }
},
"approvedBy": "Eric HABIMANA",
"callbackUrl": "https://{integration-engine-host}/callbacks/dls"
}
Callback event = DECISION_RECORDED. decisionStatus drives T20 / T21:
| Property | Type | Description | Saved as |
|---|---|---|---|
data.decisionStatus |
string | APPROVED or REJECTED (other values TBC) |
decisionStatus |
data.licenceNumber |
string | Only when APPROVED |
licenceNumber |
data.reason |
string | Only when not APPROVED |
partnerDecisionReason |
{
"requestId": "3f405162-7c8d-4e9f-a012-2c3d4e5f6071",
"applicationNumber": "APP-2026-000123",
"dlsApplicationRef": "DLS-APP-2026-004512",
"event": "DECISION_RECORDED",
"status": "SUCCESS",
"responseMessage": "Decision recorded",
"data": {
"decisionStatus": "APPROVED",
"licenceNumber": "TL-KIC-2026-01187"
},
"timestamp": "2026-10-24T10:20:45+02:00"
}
{
"requestId": "3f405162-7c8d-4e9f-a012-2c3d4e5f6071",
"applicationNumber": "APP-2026-000123",
"dlsApplicationRef": "DLS-APP-2026-004512",
"event": "DECISION_RECORDED",
"status": "SUCCESS",
"responseMessage": "Decision recorded",
"data": {
"decisionStatus": "REJECTED",
"reason": "Premise is in a zone where this activity is not allowed."
},
"timestamp": "2026-10-24T10:20:45+02:00"
}
A REJECTED decision is a successful callback: status is SUCCESS and the workflow routes it to level 2 (T21). status = FAILED is only for technical failures.
Responses
| Status | responseCode |
Meaning |
|---|---|---|
| 202 Accepted | 00 |
Accepted. The result comes in the callback (7.5) |
| 200 OK | 00 |
Same Idempotency-Key again: the first acknowledgement is returned |
| 400 Bad Request | DLS-400-01 |
Missing or invalid field (see errors) |
| 401 / 403 | DLS-401-01 |
Token missing, expired or without the scope |
| 404 Not Found | DLS-404-02 |
Application not found |
| 500 / 503 | DLS-500-01 |
DLS unavailable. IremboHub retries |
Sample request
curl --request POST \
--url "$DLS_BASE_URL/applications/DLS-APP-2026-004512/decisions" \
--header "Authorization: Bearer $DLS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--header "Idempotency-Key: APP-2026-000123-DECISION" \
--data @p4-decision.json
The inspection report is sent like the P1 documents: content = getAttachmentContent(inspectionReport). After a partner rejection (T21) and a new level 2 approval, the next P4 uses APP-2026-000123-DECISION-2.
POST /licences/{licenceNumber}/document-requests · endpoint code DLS_LICENCE_DOCUMENT_FETCH · async, T22
| Property | Type | Required | Source |
|---|---|---|---|
applicationNumber |
string | Yes | Application |
dlsApplicationRef |
string | Yes | dlsApplicationRef |
format |
string | Yes | Always PDF |
callbackUrl |
string | Yes | Integration engine |
{
"applicationNumber": "APP-2026-000123",
"dlsApplicationRef": "DLS-APP-2026-004512",
"format": "PDF",
"callbackUrl": "https://{integration-engine-host}/callbacks/dls"
}
Callback event = LICENCE_ISSUED:
| Property | Type | Description | Saved as |
|---|---|---|---|
data.licenceNumber |
string | Licence number | licenceNumber |
data.issueDate |
date | Issue date | licenceIssueDate |
data.expiryDate |
date | End of validity | licenceExpiryDate |
data.document.fileName |
string | File name | |
data.document.contentType |
string | application/pdf |
|
data.document.content |
string | Base64 PDF. Or data.document.downloadUrl, a link the integration engine downloads (open question: how the PDF is returned) |
LICENCE-Certificate-fileName |
{
"requestId": "40516273-8d9e-4fa0-b123-3d4e5f607182",
"applicationNumber": "APP-2026-000123",
"dlsApplicationRef": "DLS-APP-2026-004512",
"event": "LICENCE_ISSUED",
"status": "SUCCESS",
"responseMessage": "Licence issued",
"data": {
"licenceNumber": "TL-KIC-2026-01187",
"issueDate": "2026-10-24",
"expiryDate": "2027-10-23",
"document": {
"fileName": "TL-KIC-2026-01187.pdf",
"contentType": "application/pdf",
"content": "JVBERi0xLjcK..."
}
},
"timestamp": "2026-10-24T10:21:30+02:00"
}
Errors: 404 DLS-404-03 licence not found · 409 DLS-409-02 licence not yet approved.
Responses
| Status | responseCode |
Meaning |
|---|---|---|
| 202 Accepted | 00 |
Accepted. The result comes in the callback (7.5) |
| 200 OK | 00 |
Same Idempotency-Key again: the first acknowledgement is returned |
| 400 Bad Request | DLS-400-01 |
Missing or invalid field (see errors) |
| 401 / 403 | DLS-401-01 |
Token missing, expired or without the scope |
| 404 Not Found | DLS-404-02 |
Application not found |
| 404 Not Found | DLS-404-03 |
Licence not found |
| 409 Conflict | DLS-409-02 |
Licence not yet approved |
| 500 / 503 | DLS-500-01 |
DLS unavailable. IremboHub retries |
Sample request
curl --request POST \
--url "$DLS_BASE_URL/licences/TL-KIC-2026-01187/document-requests" \
--header "Authorization: Bearer $DLS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--header "Idempotency-Key: APP-2026-000123-LICENCE" \
--data '{"applicationNumber":"APP-2026-000123","dlsApplicationRef":"DLS-APP-2026-004512","format":"PDF","callbackUrl":"https://{integration-engine-host}/callbacks/dls"}'
POST /applications/{dlsApplicationRef}/cancellations
Endpoint code DLS_CANCEL_APPLICATION_PUSH · sync, non-breaking · T4 (payment expired) and the reject steps in 5.3.1
Tells DLS that the application will not continue, so DLS can release the quote and close its record. A failure is logged only; it never blocks the applicant or the officer.
Path parameters
| Parameter | Type | Required | Source |
|---|---|---|---|
dlsApplicationRef |
string | Yes | dlsApplicationRef (from P1) |
Request properties
| Property | Type | Required | Source | Description |
|---|---|---|---|---|
applicationNumber |
string | Yes | Application | IremboHub application number |
reason |
string | Yes | Transition | PAYMENT_EXPIRED (T4) or REJECTED_BY_OFFICER (T10, T15, T17) |
comments |
string | No | Officer's rejection comment | Empty for PAYMENT_EXPIRED |
{
"applicationNumber": "APP-2026-000123",
"reason": "REJECTED_BY_OFFICER",
"comments": "The occupancy permit is for a different building."
}
200 response
{
"responseCode": "00",
"responseMessage": "Application cancelled",
"requestId": "51627384-9eaf-4b01-c234-4e5f60718293",
"data": {
"dlsApplicationRef": "DLS-APP-2026-004512",
"status": "CANCELLED"
}
}
Responses
| Status | responseCode |
Meaning |
|---|---|---|
| 200 OK | 00 |
Cancelled. A cancel of an already cancelled application also returns 200 |
| 400 Bad Request | DLS-400-01 |
reason missing or not one of the two values |
| 401 / 403 | DLS-401-01 |
Token missing, expired or without the scope |
| 404 Not Found | DLS-404-02 |
Application not found |
| 500 / 503 | DLS-500-01 |
DLS unavailable. Logged only, not retried by the workflow |
Sample request
curl --request POST \
--url "$DLS_BASE_URL/applications/DLS-APP-2026-004512/cancellations" \
--header "Authorization: Bearer $DLS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--header "Idempotency-Key: APP-2026-000123-CANCEL" \
--data '{"applicationNumber":"APP-2026-000123","reason":"PAYMENT_EXPIRED","comments":""}'
PUT /applications/{dlsApplicationRef}
Endpoint code DLS_RESUBMIT_APPLICATION_PUSH · sync, non-breaking · resubmit step in 5.3.1
Sends the corrected application to DLS after a request for action. PUT replaces the whole application, so a repeat is safe and no Idempotency-Key is needed.
Path parameters: dlsApplicationRef, as in P6.
Request properties: every P1 property (7.7) except quoteReference. The fee does not change on resubmission (see open question 13). Documents are sent again in full, including the ones that did not change.
{
"applicationNumber": "APP-2026-000123",
"applicant": { "nationalId": "1199080012345678", "surname": "UWASE", "otherNames": "Aline", "gender": "F", "dateOfBirth": "1990-04-12", "phoneNumber": "0788100200", "email": "aline.uwase@example.com" },
"business": { "tin": "100123456", "businessId": "BR-104523", "name": "Inzozi Retail Ltd", "taxpayerCategory": "Small", "registeredAddress": "Kicukiro, Niboye", "activityCode": "4711" },
"premise": { "upi": "1/03/02/01/1234", "province": "Kigali City", "district": "Kicukiro", "districtCode": "0102", "sector": "Niboye", "cell": "Gatare", "village": "Amahoro", "landUse": "COMMERCIAL", "ownsPremise": false, "floorArea": 85, "openingHours": "Mon-Sat 08:00-20:00" },
"documents": [
{ "documentType": "OCCUPANCY_PERMIT", "fileName": "occupancy-corrected.pdf", "contentType": "application/pdf", "content": "JVBERi0xLjcK..." }
]
}
The example shows one document to keep it short. A real request carries every document in 3.6 that has a file.
200 response
{
"responseCode": "00",
"responseMessage": "Application updated",
"requestId": "62738495-afb0-4c12-d345-5f6071829304",
"data": {
"dlsApplicationRef": "DLS-APP-2026-004512",
"status": "UPDATED"
}
}
Responses
| Status | responseCode |
Meaning |
|---|---|---|
| 200 OK | 00 |
Updated |
| 400 Bad Request | DLS-400-01 |
Missing or invalid field |
| 401 / 403 | DLS-401-01 |
Token missing, expired or without the scope |
| 404 Not Found | DLS-404-02 |
Application not found |
| 422 Unprocessable | DLS-422-04 |
Required document missing |
| 500 / 503 | DLS-500-01 |
DLS unavailable. Logged only |
Sample request
curl --request PUT \
--url "$DLS_BASE_URL/applications/DLS-APP-2026-004512" \
--header "Authorization: Bearer $DLS_ACCESS_TOKEN" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--header "X-Request-Id: $(uuidgen)" \
--data @p7-update-application.json
responseCode |
HTTP | Operation | Meaning | Shown to |
|---|---|---|---|---|
00 |
200, 201, 202 | All | Success or accepted | |
DLS-400-01 |
400 | All | Missing or invalid field (see errors) |
Support |
DLS-401-01 |
401 | All | Token missing or expired | Support |
DLS-404-01 |
404 | F1 | Business not known to DLS | Applicant |
DLS-404-02 |
404 | P2 to P7 | Application not found | Support |
DLS-404-03 |
404 | P5 | Licence not found | Support |
DLS-409-01 |
409 | P1 | Idempotency key reused with a different body | Support |
DLS-409-02 |
409 | P5 | Licence not yet approved | Support |
DLS-410-01 |
410 | P1 | Fee quote expired | Applicant |
DLS-422-01 |
422 | F1 | District code unknown | Applicant |
DLS-422-02 |
422 | F1 | Activity not licensable at a commercial premise | Applicant |
DLS-422-03 |
422 | P1 | Active licence already exists for this business and premise | Applicant |
DLS-422-04 |
422 | P1 | Required document missing | Applicant |
DLS-422-05 |
422 | P2 | Amount paid differs from the quoted amount | Support |
DLS-422-06 |
422 | P3 | No inspector available at that time | Officer |
DLS-500-01 |
500, 503 | All | DLS unavailable. IremboHub retries | Support |
| Key | English | French | Kinyarwanda |
|---|---|---|---|
nationalId |
National ID | Numéro d'identité | Nomero y'indangamuntu |
surname |
Surname | Nom | Izina ry'umuryango |
otherNames |
Other names | Autres noms | Andi mazina |
gender |
Gender | Sexe | Igitsina |
dateOfBirth |
Date of birth | Date de naissance | Itariki y'amavuko |
phoneNumber |
Phone number | Numéro de téléphone | Nomero ya telefoni |
email |
Imeyili | ||
tin |
TIN | NIF | TIN |
businessName |
Business name | Nom de l'entreprise | Izina ry'ikigo |
taxpayerCategory |
Taxpayer category | Catégorie de contribuable | Icyiciro cy'usora |
registeredAddress |
Registered address | Adresse enregistrée | Aderesi yanditswe |
businessId |
Business registration ID | Numéro d'enregistrement de l'entreprise | Nomero iranga ikigo |
companyStatus |
Company status | Statut de l'entreprise | Uko ikigo gihagaze |
businessActivity |
Business activity at the premise · "Select an activity" | Activité exercée dans le local · "Sélectionnez une activité" | Igikorwa gikorerwa mu nyubako · "Hitamo igikorwa" |
landUpi |
Land UPI · "Enter the UPI of the premise" | UPI du terrain · "Saisissez l'UPI du local" | UPI y'ubutaka · "Andika UPI y'inyubako" |
landOwners |
Land owners | Propriétaires du terrain | Ba nyir'ubutaka |
province |
Province | Province | Intara |
district |
District | District | Akarere |
sector |
Sector | Secteur | Umurenge |
cell |
Cell | Cellule | Akagari |
village |
Village | Village | Umudugudu |
landUse |
Land use | Usage du terrain | Imikoreshereze y'ubutaka |
ownsPremise |
Do you own the premise? (Yes / No) | Êtes-vous propriétaire du local ? (Oui / Non) | Inyubako ni iyanyu? (Yego / Oya) |
floorArea |
Floor area (square metres) | Surface au sol (mètres carrés) | Ingano y'inyubako (metero kare) |
openingHours |
Opening hours | Heures d'ouverture | Amasaha yo gukora |
licenceFee |
Licence fee (RWF) | Frais de licence (RWF) | Amafaranga y'uruhushya (RWF) |
leaseAgreement |
Notarised lease agreement | Contrat de bail notarié | Amasezerano y'ubukode yemejwe na noteri |
landTitle |
Land title | Titre foncier | Icyangombwa cy'ubutaka |
occupancyPermit |
Occupancy permit | Permis d'occupation | Uruhushya rwo gukoresha inyubako |
fireSafetyCertificate |
Fire safety certificate | Certificat de sécurité incendie | Icyemezo cy'ubwirinzi bw'inkongi |
floorPlan |
Floor plan of the premise | Plan du local | Igishushanyo cy'inyubako |
otherDocuments |
Other documents | Autres documents | Izindi nyandiko |
| Sections | |||
applicantSection |
Applicant details | Informations sur le demandeur | Imyirondoro y'usaba |
businessSection |
Business details | Informations sur l'entreprise | Imyirondoro y'ikigo |
premiseSection |
Premise details | Informations sur le local | Imyirondoro y'inyubako |
feeSection |
Licence fee | Frais de licence | Amafaranga y'uruhushya |
documentsSection |
Supporting documents | Pièces justificatives | Inyandiko ziherekeza dosiye |
| Validation messages | |||
phoneNumber pattern |
Enter a 10-digit Rwandan phone number, e.g. 0788100200. | Saisissez un numéro de téléphone rwandais à 10 chiffres, par ex. 0788100200. | Andika nomero ya telefoni yo mu Rwanda igizwe n'imibare 10, urugero 0788100200. |
email pattern |
Enter a valid email address. | Saisissez une adresse e-mail valide. | Andika imeyili yemewe. |
businessActivity required |
Select the activity carried out at the premise. | Sélectionnez l'activité exercée dans le local. | Hitamo igikorwa gikorerwa mu nyubako. |
floorArea minimum |
Enter a floor area of at least 1 square metre. | Saisissez une surface d'au moins 1 mètre carré. | Andika ingano y'inyubako nibura metero kare 1. |
| File upload (fields 32 to 37) | Upload a PDF, JPG or PNG file of up to 10 MB. | Téléversez un fichier PDF, JPG ou PNG de 10 Mo maximum. | Ohereza dosiye ya PDF, JPG cyangwa PNG itarengeje MB 10. |
| Officer forms (F1 to F3) | |||
inspectionDateTime |
Inspection date and time | Date et heure de l'inspection | Itariki n'isaha by'igenzura |
inspectionAddressConfirmed |
I confirm the inspection address | Je confirme l'adresse de l'inspection | Nemeje aderesi y'aho igenzura rizabera |
inspectionReport |
Inspection report | Rapport d'inspection | Raporo y'igenzura |
inspectionOutcome |
Inspection outcome (Compliant / Compliant with conditions) | Résultat de l'inspection (Conforme / Conforme sous conditions) | Umwanzuro w'igenzura (Byujuje ibisabwa / Byujuje ibisabwa hari ibigomba kubahirizwa) |
inspectionConditions |
Conditions | Conditions | Ibigomba kubahirizwa |
decisionComments |
Decision comments | Commentaires sur la décision | Ibisobanuro ku cyemezo |
| Date in the past (F1) | The inspection date cannot be in the past. | La date d'inspection ne peut pas être dans le passé. | Itariki y'igenzura ntishobora kuba yararenze. |
| Required message | This field is required. | Ce champ est obligatoire. | Iki gice kirasabwa. |
French and Kinyarwanda texts must be reviewed by the institution before go-live.
Synthetic values for the DLS mock in UAT, until DLS shares its own test data. The mock follows the contract in section 7. Records for the lookup APIs (L1 to L7) come from the integration team's UAT test data (TBC); 9.1 says which kind of record each test needs.
| Name used in section 10 | API | Record needed |
|---|---|---|
| ID-OK | L1, L2 | A national ID with a phone number registered to it (PHONE-OK) |
| PHONE-OTHER | L2 | A valid phone number not registered to ID-OK |
| ID-UNKNOWN | L1 | A 16-digit ID that the identity API does not know (ID-404-01) |
| TIN-OK | L3, L4, L5, L6 | An active company, no tax arrears, at least one activity line (ACT-OK) |
| TIN-UNKNOWN | L3 | A 9-digit TIN that the tax API does not know (TAX-404-01) |
| TIN-INACTIVE | L5 | A company whose status is not ACTIVE |
| TIN-ARREARS | L4 | A company with tax arrears (TAX-422-01) |
| UPI-OK | L7 | A parcel with owners and land use COMMERCIAL or MIXED_USE |
| UPI-NO-OWNER | L7 | A parcel with no owners |
| UPI-RESIDENTIAL | L7 | A parcel with owners and land use RESIDENTIAL |
The applicant types floorArea freely, so the mock uses it to choose the answer. Values from 9001 are reserved for error cases.
| Input | Operation | Mock answer |
|---|---|---|
floorArea 1 to 50 |
F1 | 200, licenceFee 100,000 (base fee only) |
floorArea 51 to 200 |
F1 | 200, licenceFee 150,000 (base 100,000 + floor area 50,000) |
floorArea 201 to 9000 |
F1 | 200, licenceFee 250,000 (base 100,000 + floor area 150,000) |
floorArea 9001 |
F1 | 404 DLS-404-01 Business not known to DLS |
floorArea 9002 |
F1 | 422 DLS-422-01 District code unknown |
floorArea 9003 |
F1 | 422 DLS-422-02 Activity not licensable at a commercial premise |
floorArea 9004 |
F1, P1 | F1 returns 250,000 with an already expired quote; P1 returns 410 DLS-410-01 |
floorArea 9005 |
F1, P1 | F1 returns 250,000; P1 returns 422 DLS-422-03 Active licence already exists |
floorArea 9006 |
F1, P1 | F1 returns 250,000; P1 returns 503 DLS-500-01 |
| Any other valid request | P1 | 201, dlsApplicationRef DLS-APP-TEST-<n>, amount = the quoted licenceFee |
| Any valid request | P2 | 202, then PAYMENT_CONFIRMED callback after about 10 seconds |
inspectionDateTime at 13:00 (any day) |
P3 | 202, then a FAILED callback with DLS-422-06 No inspector available |
| Any other time | P3 | 202, then INSPECTION_BOOKED with inspectorName "Test Inspector" |
decisionComments contains MOCK_REJECT |
P4 | 202, then DECISION_RECORDED with decisionStatus REJECTED and reason "Premise is in a zone where this activity is not allowed." |
| Any other decision | P4 | 202, then DECISION_RECORDED with APPROVED and licenceNumber TL-TEST-<n> |
| Licence approved by P4 | P5 | 202, then LICENCE_ISSUED with a 1-page sample PDF, expiryDate = issue date + 1 year − 1 day |
| Licence not approved | P5 | 409 DLS-409-02 |
| Any reason | P6 | 200 CANCELLED (also when already cancelled) |
| Any valid body | P7 | 200 UPDATED |
Unless a case says otherwise, start from the base application: ID-OK, PHONE-OK, a valid email, TIN-OK, ACT-OK, UPI-OK, ownsPremise = NO, floorArea 85, opening hours filled, all required documents uploaded (PDF under 10 MB).
| Case | Steps | Expected |
|---|---|---|
| TC1 Base application | Fill the base application | Names, business details, businessId, location and landUse filled. Fee shows 150,000 RWF. Notarised lease agreement is shown and required; land title is hidden |
| TC2 Unknown ID | Enter ID-UNKNOWN | identity API message under the field; nothing filled; the applicant can't continue |
| TC3 Phone mismatch | ID-OK with PHONE-OTHER | Form blocks with the identity API message |
| TC4 Phone format | Enter 078123 | "Enter a 10-digit Rwandan phone number, e.g. 0788100200." No L2 call is made |
| TC5 Email format | Enter aline@ | "Enter a valid email address." |
| TC6 Unknown TIN | Enter TIN-UNKNOWN | tax API message; business fields empty; the applicant can't continue |
| TC7 Company not active | Enter TIN-INACTIVE | "This company is not active. Only active companies can apply." |
| TC8 Tax arrears | Enter TIN-ARREARS | Form blocks with the tax API message |
| TC9 Bad UPI | Enter UPI-NO-OWNER, then UPI-RESIDENTIAL | Form blocks with the UPI message both times; location fields empty |
| TC10 Owner switch | Base application, then switch ownsPremise to YES, then back to NO |
YES: land title shown and required, lease agreement hidden and its file cleared. NO: the reverse |
| TC11 Floor area minimum | Enter 0 | "Enter a floor area of at least 1 square metre." No fee shown |
| TC12 Missing quote input | Leave one of businessId, businessActivity, districtCode, floorArea empty |
No fee shown; the applicant can't continue (quoteReference is required) |
| TC13 Fee follows changes | Base application, change floorArea 85 → 250 → 30 |
Fee 150,000 → 250,000 → 100,000. Each change fires a new quote; quoteReference changes each time |
| TC14 Activity not licensable | floorArea 9003 |
The DLS message "This business activity does not need a trading licence for a commercial premise." under the fee; the applicant can't continue |
| TC15 Business unknown to DLS | floorArea 9001 |
DLS message under the fee; the applicant can't continue |
| TC16 File rules | Upload a 12 MB PDF, then a .docx file | Both refused with the file upload message |
| Case | Steps | Expected |
|---|---|---|
| TC17 Normal submit | Submit the base application | P1 returns dlsApplicationRef and amount 150,000. amountToPay = 150000 (a number). Bill of 150,000 RWF; invoice description "Trading licence DLS-APP-TEST-… - {business name}". N1 shows the bill number, amount and licensing reference |
| TC18 Quote expired | floorArea 9004, submit |
Submit shows the DLS message for DLS-410-01. No application in PAYMENT_PENDING, no bill |
| TC19 Licence already exists | floorArea 9005, submit |
Submit shows the DLS message for DLS-422-03. No application in PAYMENT_PENDING |
| TC20 DLS down on submit | floorArea 9006, submit |
Submit fails with an error; no application in PAYMENT_PENDING. Kibana shows the P1 error |
| TC21 Payment | TC17, pay the bill | PAID, N2 sent, T5 sends P2. After PAYMENT_CONFIRMED: PENDING_ASSIGN_OFFICE, then PENDING_OFFICER_LEVEL_1; O1 sent to the level 1 office |
| TC22 Payment expires | TC17, don't pay for 72 hours (or shorten the expiry on a test copy) | PAYMENT_EXPIRED, N3 sent. If P6 is on: P6 sent with reason PAYMENT_EXPIRED |
| Case | Steps | Expected |
|---|---|---|
| TC23 Request for action | After TC21, level 1 requests action; applicant replaces the occupancy permit and resubmits | N4, then N5. Application back in PENDING_OFFICER_LEVEL_1 with the new file. If P7 is on: P7 sent with all documents |
| TC24 Level 1 rejects at review | After TC21, level 1 rejects | REJECTED, N6. If P6 is on: P6 with REJECTED_BY_OFFICER |
| TC25 Inspection date in the past | Level 1 opens F1 and picks yesterday | F1 refuses the date; no state change |
| TC26 Appointment | Level 1 fills F1 for tomorrow 09:00 | APPOINTMENT_SET, N7 shows the date (no inspector name). T12 sends P3. After INSPECTION_BOOKED: PENDING_INSPECTION, inspectionSlotId and inspectorName saved |
| TC27 No inspector available | F1 with 13:00 | P3 sent; the FAILED callback leaves the application in PENDING_INSPECTION_BOOKING. Record what support sees (open question 9) |
| TC28 Inspection form rules | In F2, choose COMPLIANT_WITH_CONDITIONS with no conditions; then upload a JPG report |
Both refused: conditions are required, and the report must be a PDF |
| TC29 Level 1 rejects after inspection | After TC26, level 1 rejects | REJECTED, N6 |
| TC30 Level 2 sends back | After TC26, level 1 approves with F2; level 2 sends back | O2 on reaching level 2, then PENDING_OFFICER_LEVEL_1 and O1 |
| TC31 Level 2 rejects | Level 2 rejects | REJECTED, N6 |
| TC32 Approval and decision | Level 1 approves with F2 (COMPLIANT), level 2 approves with F3 |
LEVEL_2_APPROVED, T19 sends P4 with the report. DLS returns APPROVED and licenceNumber; PENDING_LICENCE_DOCUMENT, then T22 sends P5 |
| TC33 Partner rejects | As TC32 with decisionComments "MOCK_REJECT" |
Back in PENDING_OFFICER_LEVEL_2. O3 shows status REJECTED and the DLS reason |
| TC34 Licence issued | After TC32 | P5 returns the PDF. APPROVED, state name "Licence Issued". N8 arrives with the PDF attached and shows licenceNumber and licenceExpiryDate |
| Case | Steps | Expected |
|---|---|---|
| TC35 Languages | Run TC17 to TC34 with the applicant in French, then Kinyarwanda | Form, messages, states and every notification in the applicant's language; no English text or raw key left |
| TC36 Placeholders | Read every notification from TC35 | No raw ${…} and no empty value where a value exists at that step (5.5) |
| TC37 Next events | Run the full flow once | No application stuck after T3, T6, T11, T18 or T20 |
| TC38 Repeated callback | The integration team resends the last DECISION_RECORDED callback |
The second one changes nothing: no new state, no second N8 |
| Case | Call | Expected |
|---|---|---|
| TA1 Token | POST /oauth/token with a wrong secret, then the right one |
401 first; then 200 with access_token, token_type Bearer, expires_in |
| TA2 Expired token | Any operation with an expired token | 401 DLS-401-01 |
| TA3 Quote | F1 with the 7.6 example | 200; licenceFee is a JSON number; validUntil at least 72 hours later, with an offset |
| TA4 Missing field | F1 without floorArea |
400 DLS-400-01 with errors[].field = floorArea |
| TA5 Create and repeat | P1, then the same request with the same Idempotency-Key |
201, then 200 with the same dlsApplicationRef |
| TA6 Key conflict | Same Idempotency-Key with a different body |
409 DLS-409-01 |
| TA7 Missing document | P1 without the fire safety certificate | 422 DLS-422-04 |
| TA8 Async acknowledgement | P2, P3, P4, P5 | Each answers 202 with data.status ACCEPTED at once; the callback arrives later with the same requestId |
| TA9 Callback retry | Make the callback URL answer 500 for a few minutes | DLS keeps retrying with increasing delays; the callback is delivered once the URL answers 2xx |
| TA10 Rejected decision | P4 that DLS rejects | Callback status SUCCESS, decisionStatus REJECTED, reason filled |
| TA11 Licence too early | P5 before P4 approved | 409 DLS-409-02 |
| TA12 Licence PDF | P5 after approval | Callback with a PDF that opens, issueDate and expiryDate as YYYY-MM-DD |
| TA13 Cancel twice | P6 two times | 200 both times, status CANCELLED |
| TA14 Formats | Any response | Amounts are numbers, identifiers are strings, date-times have an offset, unknown properties are ignored by IremboHub |
| TL1 Identity | L1 with ID-OK, then ID-UNKNOWN | 200 with the four identity fields; then 404 ID-404-01 |
| TL2 Phone match | L2 with ID-OK + PHONE-OK, then PHONE-OTHER | 200 match true; then 422 ID-422-01 |
| TL3 Taxpayer | L3 with TIN-OK, then TIN-UNKNOWN | 200 with name, category, address; then 404 TAX-404-01 |
| TL4 Tax compliance | L4 with TIN-OK, then TIN-ARREARS | 200 hasArrears false; then 422 TAX-422-01 |
| TL5 Business record | L5 with TIN-OK, then TIN-INACTIVE | companyStatus ACTIVE; then a status other than ACTIVE (still 00) |
| TL6 Activities paging | L6 with TIN-OK, size 1, pages 0 and 1 |
One item per page, totalItems unchanged, no item repeated |
| TL7 Land parcel | L7 with UPI-OK, UPI-NO-OWNER, UPI-RESIDENTIAL | 00 each time; owners filled for UPI-OK only; landUse COMMERCIAL/MIXED_USE for UPI-OK, RESIDENTIAL for the third |
| TL8 No ID in URLs | Inspect the gateway logs for TL1 to TL7 | No national ID, TIN or UPI in any URL or query string |
| # | Question | Affects |
|---|---|---|
| 1 | Which QT/HUZA system receives these pushes (DLS is a placeholder name), and who owns the partner contract? Does it accept the contract in section 7? | Partner, section 7 |
| 2 | Is the fee always quoted by the partner, or are there fixed fees for some licence types? | Section 4 (a fixed fee needs a pricing condition) |
| 3 | Does the partner need the cancel (P6) and resubmit (P7) pushes? | Section 5.3.1 |
| 4 | Should the inspection appointment be booked by the officer, or should the partner assign the inspector? | F1, P3, N7 |
| 5 | How long is the licence valid, and does it need a renewal service later? | licenceExpiryDate, N8, follow-up service |
| 6 | Merchant, payment account and service payment code | Section 4 |
| 7 | Office assignment logic for levels 1 and 2. Providers of the lookup APIs L1 to L7 | Sections 5.7, 6 |
| 8 | Does the land parcel lookup (L7) return a district code for the fee quote? | Field 21, section 3.5 |
| 9 | How is a FAILED callback handled: stay in the PENDING_ state for support to re-trigger, or route back to the officer? |
Sections 5.3, 7.5 |
| 10 | How does DLS return the licence PDF: base64 content or a download link? How are uploaded documents sent to DLS? | P1, P4, P5 |
| 11 | How long does a fee quote stay valid? It must be at least the 72-hour payment expiry | F1 validUntil |
| 12 | Which decisionStatus values does DLS use besides APPROVED and REJECTED? |
T20, T21 |
| 13 | If the applicant changes businessActivity, landUpi or floorArea when resubmitting, the paid fee may no longer match. Should these fields be read-only on resubmission, or should a fee difference send the applicant back to pay? |
Section 3.2, P7, T9 |
| 14 | Is a paid application refunded when it is rejected (by an officer or by DLS), and through which process? | Section 4 |