addresses
Address lookup and registration
POST/v1/addresses
Register an address (allocate a digital address code)
Partner/agent scoped. Allocates the next sequential premise number in
the resolved parish and mints the canonical digital address. In a
`pending_allocation` parish the capture is stored and `201` returns
the record with `status: pending_allocation` and `digital_address`,
`postcode`, and `premise` all `null` (the schema enforces this
coherence); issuance happens when MoICT allocates the postcode — the
partner is notified and can re-fetch via its `unique_reference`.
**Requires `Idempotency-Key`, scoped per principal.** The replay
ledger key is (authenticated principal, Idempotency-Key): replays by
the same principal return the original result; a different principal
using the same key string hits its own independent ledger — it can
neither read another partner's result nor cause a cross-tenant `409`.
Parameters
| Name | In | Type | Description |
|---|
| Idempotency-Key * | header | string | Client-generated key (UUID recommended). The server's replay ledger
is keyed by **(authenticated principal, Idempotency-Key)** — keys are
tenant-isolated and can never match, replay, or conflict across
principals. Within one principal: replays with the same key return
the original result; the same key with a different payload is
rejected with 409. Keys are retained for at least 24 hours. |
Request body — RegisterAddressRequest
| Field | Type | Description |
|---|
| point * | Point | WGS84 coordinate |
| gps_accuracy_m | number | null | |
| street | string | null | |
| house_number | string | null | |
| unit_designator | string | null | |
| unique_reference | string | null | The partner's own id — becomes the crosswalk join key |
| source | string | null | Capture context (defaults to the caller's channel) |
Responses
| Status | Schema | Description |
|---|
| 201 | RegisterAddressResponse | Address registered (digital code minted, or explicitly pending) |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 409 | ErrorResponse | Conflict — either the Idempotency-Key was replayed with a different payload (`idempotency_conflict`), or the supplied `unique_reference` is already bound to a different address for this partner (`reference_conflict`). |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/addresses' \
-H 'X-API-Key: <your sandbox key>' \
-H 'Content-Type: application/json' \
-d '{ "point": { "lat": 0.3355, "lon": 32.5555 }, "gps_accuracy_m": 6.5, "street": "Kafu Road", "house_number": "24", "unique_reference": "partner-abc-000123" }'
GET/v1/addresses/{digital_address}
Look up an address by digital address code
Accepts the canonical frozen format **and any superseded or legacy
alias shape** (grandfathered System-A codes included — issued codes
resolve forever). The registry, not the URL pattern, decides validity:
unknown codes are `404`; only over-length input is structurally
rejected (`422`). The response's `digital_address` is **always the
canonical current code**; when the query was an alias, `queried_code`
echoes what was asked.
Anonymous lookups draw from the strict per-IP anti-enumeration budget
(`x-ndas-rate-limit-tiers`); bulk lookup requires keyed access.
Audience shaping: unauthenticated and public keys receive
`PublicAddress`; partner scopes receive `PartnerAddress`.
Parameters
| Name | In | Type | Description |
|---|
| digital_address * | path | string | A digital address code: canonical (`UG-XXXX-XXXXX`), superseded,
or legacy alias shape. Free-form by design — legacy System-A
codes do not match the frozen pattern but must resolve forever. |
Responses
| Status | Schema | Description |
|---|
| 200 | AddressResponse | The address record, shaped for the caller's audience |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/addresses/{digital_address}' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/addresses/reference/{unique_reference}
Look up an address by partner reference (crosswalk)
The sync join key: a partner passes its own `unique_reference`
(supplied at registration) and receives the canonical record. Scoped
to the owning partner — references are tenant-isolated.
Parameters
| Name | In | Type | Description |
|---|
| unique_reference * | path | string | The partner-supplied external reference |
Responses
| Status | Schema | Description |
|---|
| 200 | PartnerAddressResponse | The partner-tier address record |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/addresses/reference/{unique_reference}' \
-H 'X-API-Key: <your sandbox key>'
reference
Admin hierarchy and postcode reference data
GET/v1/reference/admin-units
List administrative units (drill-down)
The Region → District → County → Sub-county → Parish → Village
drill-down. Filter by `level` and/or `parent_id`; omit both to list
regions. Paginated.
Parameters
| Name | In | Type | Description |
|---|
| level | query | AdminLevel | |
| parent_id | query | string | Return children of this unit |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | AdminUnitListResponse | Matching admin units |
| 400 | ErrorResponse | Malformed request |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/reference/admin-units' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/reference/admin-units/{admin_unit_id}/children
List the direct children of an administrative unit
Parameters
| Name | In | Type | Description |
|---|
| admin_unit_id * | path | string | |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | AdminUnitListResponse | The unit's direct children |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/reference/admin-units/{admin_unit_id}/children' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/reference/postcodes/{code}
Look up a postcode (current, superseded, or legacy alias)
Returns the official record for a postcode query. Accepts canonical
5-digit codes, superseded codes, and **legacy alias shapes** (e.g.
grandfathered System-A placeholders) — the registry decides validity;
unknown queries are `404`, only over-length input is `422`.
Superseded 5-digit codes resolve forever: the record carries
`status: superseded` plus the canonical successor in `canonical_code`.
For legacy-shaped aliases the returned record **is** the canonical
target (its `code` keeps the frozen 5-digit format) and
`queried_code` echoes the original query.
Parameters
| Name | In | Type | Description |
|---|
| code * | path | string | A postcode query: canonical 5-digit, superseded, or a legacy
alias shape. Free-form by design (see endpoint description). |
Responses
| Status | Schema | Description |
|---|
| 200 | PostcodeResponse | The postcode record |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/reference/postcodes/{code}' \
-H 'X-API-Key: <your sandbox key>'
kyc
Consented address-person verification (partner product)
POST/v1/kyc/address-match
Consented person–address match (KYC product)
The monetized verification product for banks/immigration/MDAs.
Requires the `kyc.match` scope (OAuth2 bearer — API keys alone do not
carry it) **and** a valid consent reference (Uganda DPPA 2019).
Consent is bound to the specific check: **(subject, purpose,
address-bounded)**. The server MUST reject with `403`
(`consent_scope_invalid`) any consent whose scope does not cover the
queried `digital_address` — a single consent can never be replayed
across arbitrary addresses to discover where a person lives.
The response is a match boolean with consent/audit references —
**raw PII is never echoed back**. Every lookup is written to the
immutable audit trail (who queried whose address, under which
consent).
Request body — KycMatchRequest
| Field | Type | Description |
|---|
| digital_address * | DigitalAddress | The frozen national format: UG- + 4-digit premise + - + 5-digit
postcode. All numeric, nothing appended, no check digit. |
| subject * | object | The data subject to match (PII is processed, never echoed)| full_name | string | | | nin_token | string | Tokenized NIN reference issued by the identity provider.
The token shape is structural: anything not matching
`^tok_[A-Za-z0-9_]+$` — a raw NIN in particular — is rejected
with 422 before any processing. | | phone | string | E.164-style digits only |
|
| consent_reference * | string | The consent record authorizing this check (Uganda DPPA 2019).
Consent is **(subject, purpose, address-bounded)**: it names the
data subject, the address-verification purpose, and the specific
digital address(es) it covers. The server MUST reject (403,
`consent_scope_invalid`) a consent whose scope does not cover the
queried `digital_address` — one subject-scoped consent can never
be swept across many addresses to discover residence. |
Responses
| Status | Schema | Description |
|---|
| 200 | KycMatchResponse | Match outcome (fail-safe — a wrong code yields no-match) |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Missing scope or invalid/revoked consent reference |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/kyc/address-match' \
-H 'X-API-Key: <your sandbox key>' \
-H 'Content-Type: application/json' \
-d '{ "digital_address": "UG-0007-10201", "subject": { "full_name": "Synthetic Party 001", "nin_token": "tok_sandbox_5f2a9c" }, "consent_reference": "consent-2026-000042" }'
admin
NDAS Admin Console — staff tier (Keycloak ndas-staff realm roles)
GET/v1/admin/addresses
List addresses for moderation (NDAS Staff)
Moderation-oriented listing. Carries NO owner/party data.
Parameters
| Name | In | Type | Description |
|---|
| status | query | AddressStatus | |
| district | query | string | |
| parish | query | string | |
| q | query | string | Substring match on code or street |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | StaffAddressListResponse | Matching addresses (staff summary projection) |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/addresses' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/addresses/{digital_address}
Staff address detail (owner block included — the read is audited)
The only endpoint family that shapes owner data into a response
(blueprint §9 — staff see more, but everything is audited): each
call emits an `admin.address_viewed` audit event naming the staff
principal. The NIN token hash never leaves the registry.
Parameters
| Name | In | Type | Description |
|---|
| digital_address * | path | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | StaffAddressResponse | The staff-tier address record with verification history |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/addresses/{digital_address}' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/admin/addresses/{digital_address}/moderate
Moderate an address (verify / reject / retire)
Lifecycle transitions: `verify` (unverified → verified), `reject`
(stays unverified; the rejection is recorded as a verification
event), `retire` (unverified|verified → retired — the code stays
resolvable forever and is never reused). Disallowed transitions are
`409` `moderation_action_invalid`. Audited.
Parameters
| Name | In | Type | Description |
|---|
| digital_address * | path | string | |
Request body — ModerationRequest
| Field | Type | Description |
|---|
| action * | string | verify · reject · retire |
| note | string | null | |
Responses
| Status | Schema | Description |
|---|
| 200 | StaffAddressResponse | The updated staff-tier record |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 409 | ErrorResponse | Transition not allowed from the current status |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/admin/addresses/{digital_address}/moderate' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/verification/queue
The verification queue (unverified addresses + evidence)
Parameters
| Name | In | Type | Description |
|---|
| district | query | string | |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | VerificationQueueResponse | Unverified addresses, oldest first, with their events |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/verification/queue' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/disputes
The dispute / claim-conflict queue
Summaries only — claimant contact (PII) appears in detail.
Parameters
| Name | In | Type | Description |
|---|
| status | query | DisputeStatus | |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | DisputeListResponse | Dispute summaries, oldest first |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/disputes' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/admin/disputes
Open a dispute against a registered address
Request body — DisputeCreateRequest
| Field | Type | Description |
|---|
| digital_address * | string | |
| kind | string | claim_conflict · ownership · data_error |
| claimant_name * | string | |
| claimant_contact | string | null | |
| grounds * | string | |
| evidence_ref | string | null | |
Responses
| Status | Schema | Description |
|---|
| 201 | DisputeDetailResponse | The opened dispute (detail projection) |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/admin/disputes' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/disputes/{dispute_id}
Dispute detail (claimant contact included — the read is audited)
Parameters
| Name | In | Type | Description |
|---|
| dispute_id * | path | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | DisputeDetailResponse | The dispute with grounds, evidence, and decision trail |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/disputes/{dispute_id}' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/admin/disputes/{dispute_id}/adjudicate
Decide a dispute (evidence-based; audited)
Allowed from `open`, `under_review`, or `appealed`; the case becomes
`resolved`. Decisions move the *claim*, never the code (permanence).
Parameters
| Name | In | Type | Description |
|---|
| dispute_id * | path | string | |
Request body — DisputeAdjudicateRequest
| Field | Type | Description |
|---|
| decision * | string | uphold_claimant · uphold_holder · dismissed |
| note * | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | DisputeDetailResponse | The resolved dispute |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 409 | ErrorResponse | The dispute is not in an adjudicable state |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/admin/disputes/{dispute_id}/adjudicate' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/admin/disputes/{dispute_id}/appeal
Appeal a resolved dispute (reopens the case; audited)
Parameters
| Name | In | Type | Description |
|---|
| dispute_id * | path | string | |
Request body — DisputeAppealRequest
| Field | Type | Description |
|---|
| note * | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | DisputeDetailResponse | The appealed (reopened) dispute |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 409 | ErrorResponse | Only resolved disputes can be appealed |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/admin/disputes/{dispute_id}/appeal' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/agents
The field network — per-agent activity and trust scores
Responses
| Status | Schema | Description |
|---|
| 200 | AgentListResponse | Agents aggregated from verification events + tasks |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/agents' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/field-tasks
List field-agent assignments
Parameters
| Name | In | Type | Description |
|---|
| agent_id | query | string | |
| status | query | FieldTaskStatus | |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | FieldTaskListResponse | Field tasks, newest first |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/field-tasks' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/admin/field-tasks
Assign a field agent to an area (audited)
Request body — FieldTaskCreateRequest
| Field | Type | Description |
|---|
| agent_id * | string | |
| area_admin_unit_id * | string | The area's public admin-unit id (e.g. adm-parish-bwaise-i) |
Responses
| Status | Schema | Description |
|---|
| 201 | FieldTaskResponse | The created assignment |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/admin/field-tasks' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/admin/field-tasks/{task_id}/status
Move a field task along its lifecycle (audited)
Parameters
| Name | In | Type | Description |
|---|
| task_id * | path | string | |
Request body — FieldTaskStatusRequest
| Field | Type | Description |
|---|
| status * | FieldTaskStatus | open · assigned · done |
Responses
| Status | Schema | Description |
|---|
| 200 | FieldTaskResponse | The updated task |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/admin/field-tasks/{task_id}/status' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/reports/coverage
Verification coverage per district (or per parish of one district)
Parameters
| Name | In | Type | Description |
|---|
| district | query | string | Drill into one district (groups become parishes) |
Responses
| Status | Schema | Description |
|---|
| 200 | AdminCoverageResponse | Coverage/verification statistics |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/reports/coverage' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/reports/usage
Usage/audit event volumes (ClickHouse-backed, outbox fallback)
Parameters
| Name | In | Type | Description |
|---|
| days | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | UsageReportResponse | Per-day, per-event-type event volumes; `source` names the store |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/reports/usage' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/audit-events
Query the immutable audit trail (who did what)
Queries the ClickHouse audit store; when it is unreachable the
response falls back to the transactional outbox and says so in
`source` — a partial view is never silently presented as the trail.
Parameters
| Name | In | Type | Description |
|---|
| event_type | query | string | |
| aggregate | query | string | |
| aggregate_id | query | string | |
| principal | query | string | |
| date_from | query | string | |
| date_to | query | string | |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | AuditEventListResponse | Matching audit events, newest first |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/audit-events' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/admin/users
NDAS Staff users & roles (Keycloak-backed view; NDAS Admins only)
A read-only projection of the Keycloak `ndas-staff` realm (users +
realm roles). Provisioning stays in Keycloak. Requires the
`ndas-admin` role; the read is audited. `503` when the identity
provider view is unavailable or unconfigured.
Responses
| Status | Schema | Description |
|---|
| 200 | StaffUserListResponse | Staff accounts with their realm roles |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
| 503 | ErrorResponse | Identity provider view unavailable |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/admin/users' \
-H 'X-API-Key: <your sandbox key>'
boundary
Boundary & Postcode Dashboard — staff tier GIS editing
GET/v1/boundary/changes
List boundary changes (draft → review → publish pipeline)
Parameters
| Name | In | Type | Description |
|---|
| status | query | BoundaryChangeStatus | |
| admin_unit_id | query | string | |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | BoundaryChangeListResponse | Boundary changes, newest first (no geometry payloads) |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/boundary/changes' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/boundary/changes
Draft a boundary change (hand-drawn or dissolve-from-children)
`geometry_edit` takes a GeoJSON Polygon/MultiPolygon drawn in the
editor. `dissolve_from_children` computes the geometry server-side
as the union of the unit's current children — the standard way to
fill missing upper-level (region/county/subcounty) polygons — and
rejects a supplied geometry. Both create a reviewable, audited
draft; nothing touches the live map until publish.
Request body — BoundaryChangeCreateRequest
| Field | Type | Description |
|---|
| admin_unit_id * | string | |
| kind | BoundaryChangeKind | geometry_edit · dissolve_from_children |
| geometry | null | GeoJsonPolygonInput | Required for geometry_edit; forbidden for dissolve |
| note | string | null | |
Responses
| Status | Schema | Description |
|---|
| 201 | BoundaryChangeResponse | The created draft (with geometry) |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/boundary/changes' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/boundary/changes/{change_id}
Boundary change detail (with geometry + validation report)
Parameters
| Name | In | Type | Description |
|---|
| change_id * | path | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | BoundaryChangeResponse | The change, its geometry, and the last validation report |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/boundary/changes/{change_id}' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/boundary/changes/{change_id}/submit
Submit a draft for review (topology validation gates it)
Re-runs topology validation and stores the report on the change. A
failing report blocks review (`422 geometry_invalid`, status stays
`draft`); a passing one moves the change to `in_review`. Audited.
Parameters
| Name | In | Type | Description |
|---|
| change_id * | path | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | BoundaryChangeResponse | The change, now in review |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 409 | ErrorResponse | Not a draft |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/boundary/changes/{change_id}/submit' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/boundary/changes/{change_id}/publish
Publish a reviewed change (NDAS Admins; four-eyes; versioned)
Requires the `ndas-admin` role AND a reviewer different from the
author. Re-runs topology validation inside the publish transaction
(a neighbouring boundary may have changed since review — TOCTOU
guard), then applies the geometry, snapshots the prior geometry on
the change, advances the unit's version, and bumps the live boundary
data version (cache invalidation). `409` when: not in review, the
draft is stale (the unit's version moved since drafting), or topology
no longer validates against the current map.
Parameters
| Name | In | Type | Description |
|---|
| change_id * | path | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | BoundaryChangeResponse | The published change (prior geometry retained) |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 409 | ErrorResponse | Not in review, the draft is stale, or topology regressed |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/boundary/changes/{change_id}/publish' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/boundary/changes/{change_id}/reject
Reject a change in review (audited)
Parameters
| Name | In | Type | Description |
|---|
| change_id * | path | string | |
Request body — BoundaryRejectRequest
| Field | Type | Description |
|---|
| note | string | null | |
Responses
| Status | Schema | Description |
|---|
| 200 | BoundaryChangeResponse | The rejected change |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 409 | ErrorResponse | Not in review |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/boundary/changes/{change_id}/reject' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/boundary/validate
Ad-hoc topology validation for the editor
Validates a proposed geometry for a unit against its level's
invariants — OGC validity (with an auto-repaired geometry offered
back), no sibling overlap (borders may touch, interiors may not),
containment within the parent. PostGIS/GEOS-backed.
Request body — BoundaryValidateRequest
| Field | Type | Description |
|---|
| admin_unit_id * | string | |
| geometry * | GeoJsonPolygonInput | Editor input — Polygon or MultiPolygon (normalized server-side)| type * | string | Polygon · MultiPolygon | | coordinates * | array<array<object>> | |
|
Responses
| Status | Schema | Description |
|---|
| 200 | TopologyReportResponse | The topology report |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/boundary/validate' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/boundary/coverage
Coverage intelligence — the heatmap + the fix-next list
Where the map is missing or wrong: per-level geometry coverage, the
capped list of units without polygons, registered addresses falling
OUTSIDE every parish polygon (per district), parishes awaiting an
official postcode, and duplicate admin codes.
Responses
| Status | Schema | Description |
|---|
| 200 | BoundaryCoverageResponse | The coverage report |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/boundary/coverage' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/boundary/admin-units/{admin_unit_id}/geometry
One unit's editable geometry (GeoJSON) for the editor
Parameters
| Name | In | Type | Description |
|---|
| admin_unit_id * | path | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | UnitGeometryResponse | The unit with its current MultiPolygon (null when missing) |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/boundary/admin-units/{admin_unit_id}/geometry' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/boundary/postcodes
Postcode registry oversight (search + paginate)
Parameters
| Name | In | Type | Description |
|---|
| status | query | PostcodeRecordStatus | |
| q | query | string | Prefix match on code, substring on parish/district |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | PostcodeOversightListResponse | Registry rows (official MoICT allocation) |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/boundary/postcodes' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/boundary/pending-parishes
The pending_allocation queue (parishes without official codes)
Parameters
| Name | In | Type | Description |
|---|
| q | query | string | |
| page | query | integer | |
| limit | query | integer | |
Responses
| Status | Schema | Description |
|---|
| 200 | PendingParishListResponse | Parishes awaiting a MoICT allocation |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/boundary/pending-parishes' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/boundary/pending-parishes/{pending_id}/allocate
Record an official MoICT allocation for a pending parish
NDAS never mints postcodes — this RECORDS what MoICT allocated
(blueprint §3.3), and is fail-closed on authenticity and permanence:
- **Four-eyes:** `ndas-admin` only (not a lone boundary-editor).
- **Authenticity:** the code MUST be in the official MoICT allocation
(the complete April-2019 schedule plus any loaded refresh master).
A well-formed but unofficial code is rejected `422
code_not_official` — a fabricated code can never become a permanent
digital address.
- **Permanence / one-code-per-parish:** `409 postcode_conflict` if the
code is already registered OR the parish already holds a code.
On success the code enters the registry (bound to the parish
admin-unit by id), the parish leaves the queue, the registry data
version bumps (engine + caches reload), and the allocation is
audited with the MoICT source reference.
Parameters
| Name | In | Type | Description |
|---|
| pending_id * | path | string | |
Request body — PostcodeAllocateRequest
| Field | Type | Description |
|---|
| code * | string | The code MoICT allocated (never minted by NDAS). Validated against
the official allocation set — an unofficial code is rejected. |
| effective_from | string | null | |
| source_reference | string | null | The MoICT source-document reference for this allocation (schedule
or refresh master). Recorded in the audit event. |
Responses
| Status | Schema | Description |
|---|
| 201 | PostcodeAllocateResponse | The recorded allocation |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 409 | ErrorResponse | Code already registered, or the parish already has a code |
| 422 | ErrorResponse | Malformed code, bad date, or code not in the official allocation |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/boundary/pending-parishes/{pending_id}/allocate' \
-H 'X-API-Key: <your sandbox key>'
partner
Partner self-service (additive, W5) — the Developer Portal and
Partner/MDA Dashboard backend: tenancy, API-key lifecycle, team
management, webhooks, usage, invoices, and clerk-recorded consent.
All endpoints require a signed-in partner user (OAuth2, ndas-partners
realm) whose Keycloak subject is bound to a partner tenant; key /
user / webhook management additionally requires the partner-admin
role. Plaintext secrets appear exactly once, in the response of the
request that created them.
GET/v1/partner/me
The signed-in partner user's tenant, role, and environment
Responses
| Status | Schema | Description |
|---|
| 200 | PartnerProfileResponse | Tenant profile. In sandbox deployments a first-login user is auto-provisioned a tenant (self-service sandbox); production tenancy is provisioned by NDAS Staff. |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/partner/me' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/partner/keys
List the tenant's API keys and production key requests
Responses
| Status | Schema | Description |
|---|
| 200 | PartnerKeyListResponse | Keys (hashed at rest; prefix only) + open requests |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/partner/keys' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/partner/keys
Issue a sandbox key instantly, or file a production key request
partner-admin only. `sandbox: true` (default) mints immediately and
returns the plaintext ONCE. `sandbox: false` files a request that
NDAS Staff approve; the partner then claims the approved request
(the plaintext is minted in the partner's own session — staff never
see it). Sandbox deployments always mint sandbox keys regardless of
the flag. `kyc.match` can never be carried by an API key.
Request body — CreatePartnerKeyRequest
| Field | Type | Description |
|---|
| name * | string | |
| scopes * | array<string> | kyc.match is OAuth-only and can never be carried by a key |
| sandbox | boolean | |
| justification | string | Required context for production key requests |
Responses
| Status | Schema | Description |
|---|
| 201 | PartnerKeyIssuedResponse | Sandbox key minted — plaintext shown once |
| 202 | KeyRequestFiledResponse | Production key request filed (await staff decision) |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/partner/keys' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/partner/keys/{key_id}/rotate
Rotate a key (revoke + mint a successor with the same scopes)
Parameters
| Name | In | Type | Description |
|---|
| key_id * | path | string | The API key id (tenant-scoped) |
Responses
| Status | Schema | Description |
|---|
| 201 | PartnerKeyIssuedResponse | Successor key — plaintext shown once |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/partner/keys/{key_id}/rotate' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/partner/keys/{key_id}/revoke
Revoke a key immediately
Parameters
| Name | In | Type | Description |
|---|
| key_id * | path | string | The API key id (tenant-scoped) |
Responses
| Status | Schema | Description |
|---|
| 200 | PartnerKeyResponse | The revoked key record |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/partner/keys/{key_id}/revoke' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/partner/key-requests/{request_id}/claim
Claim an approved production key request (mints the key once)
Parameters
| Name | In | Type | Description |
|---|
| request_id * | path | string | |
Responses
| Status | Schema | Description |
|---|
| 201 | PartnerKeyIssuedResponse | Production key minted — plaintext shown once |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 409 | ErrorResponse | Request not in a claimable state (request_state_invalid) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/partner/key-requests/{request_id}/claim' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/partner/users
List the tenant's team members
Responses
| Status | Schema | Description |
|---|
| 200 | PartnerUserListResponse | Team members (partner-admin / partner-clerk) |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/partner/users' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/partner/users
Add a team member (partner-admin invites partner-clerk)
Request body — AddPartnerUserRequest
| Field | Type | Description |
|---|
| subject * | string | |
| role | string | admin · clerk |
| label | string | |
Responses
| Status | Schema | Description |
|---|
| 201 | PartnerUserResponse | Member added |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/partner/users' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/partner/users/{user_id}/remove
Remove a team member (cannot remove yourself)
Parameters
| Name | In | Type | Description |
|---|
| user_id * | path | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | RemovedResponse | Member removed |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/partner/users/{user_id}/remove' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/partner/webhooks
List webhook endpoints (signing secrets are never re-shown)
Responses
| Status | Schema | Description |
|---|
| 200 | WebhookListResponse | Configured webhook endpoints |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/partner/webhooks' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/partner/webhooks
Register a webhook endpoint (HMAC secret shown once)
Deliveries are signed with `X-NDAS-Signature` (HMAC-SHA256 over the
raw body using the returned secret). HTTPS destinations only.
Request body — CreateWebhookRequest
| Field | Type | Description |
|---|
| url * | string | |
| events * | array<string> | |
Responses
| Status | Schema | Description |
|---|
| 201 | WebhookCreatedResponse | Webhook registered — secret shown once |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/partner/webhooks' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/partner/webhooks/{webhook_id}/delete
Delete a webhook endpoint
Parameters
| Name | In | Type | Description |
|---|
| webhook_id * | path | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | DeletedResponse | Webhook deleted |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 404 | ErrorResponse | Unknown resource (e.g. code not in the registry) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/partner/webhooks/{webhook_id}/delete' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/partner/usage
Usage aggregates (per day / per key / per endpoint)
Served from the ClickHouse metering store; when it is unreachable
the endpoint fails SOFT to daily Postgres rollups
(`source: rollup`) with calls only — error/latency fields are null.
Parameters
| Name | In | Type | Description |
|---|
| from | query | string | |
| to | query | string | |
Responses
| Status | Schema | Description |
|---|
| 200 | UsageSummaryResponse | Usage aggregates for the requested range (default 30d) |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/partner/usage' \
-H 'X-API-Key: <your sandbox key>'
GET/v1/partner/invoices
The tenant's invoices, newest first
Responses
| Status | Schema | Description |
|---|
| 200 | InvoiceListResponse | Invoices |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X GET \
'https://sandbox.n.das.ug/v1/partner/invoices' \
-H 'X-API-Key: <your sandbox key>'
POST/v1/partner/consents
Record an explicitly captured consent (clerk verification tool)
The clerk attests the data subject granted consent for an address
verification of the named address (Uganda DPPA 2019). The record
stores hashed/normalized subject identifiers only — a raw NIN is
rejected before any processing. The returned `consent_reference`
is quoted by the subsequent `/v1/kyc/address-match` call, which
enforces BOTH the address-bounded and subject-bounded scope of the
consent. Recording is audited.
Request body — RecordConsentRequest
| Field | Type | Description |
|---|
| digital_address * | DigitalAddress | The frozen national format: UG- + 4-digit premise + - + 5-digit
postcode. All numeric, nothing appended, no check digit. |
| subject * | object | The data subject granting consent. Identifiers are stored
hashed/normalized only; a raw NIN is rejected with 422.| full_name | string | | | nin_token | string | | | phone | string | |
|
| expires_in_days | integer | |
Responses
| Status | Schema | Description |
|---|
| 201 | ConsentReceiptResponse | Consent recorded |
| 400 | ErrorResponse | Malformed request |
| 401 | ErrorResponse | Missing or invalid credentials |
| 403 | ErrorResponse | Authenticated but not permitted (scope/tenant) |
| 422 | ErrorResponse | Input failed validation (structural code rules, domains) |
| 429 | ErrorResponse | Request budget exhausted |
Try it (sandbox)
curl -X POST \
'https://sandbox.n.das.ug/v1/partner/consents' \
-H 'X-API-Key: <your sandbox key>'