03 — API Contracts
Status: ✅ Draft
Prerequisite: 02 — API Design Standards
Next: 04 — Event Catalog
Full endpoint-level specifications for every module-facing Core API, expanding C-010 to C-019 from TD3. All contracts follow the standard structure defined in TD3 Section 3 and the request/response envelope from 02 — API Design Standards.
Read/write split: Module teams only need the read-endpoint contracts here. Write endpoints (those that mutate Core state) are Platform Admin only and enforced at the Gateway — a module API key calling a write endpoint receives
AUTH_MODULE_NOT_PERMITTED (403)before it reaches the engine.Pending contracts: C-001 (ITS SSO Token) and C-005 (ITS ↔ Core Sync) are blocked on the ITS Technical session and are not included here. See TD3 Section 4.
How to Read This Document
Each contract section covers one Core engine. The structure per endpoint is:
Method Path
Auth: user-initiated | system-initiated | both
SLA: target response time
Request body / params
Success response
Business-answer response (e.g. denied, ineligible — still 200)
Error responses
Example
SLAs marked TBC are targets under analysis. Final SLAs are confirmed before each engine enters Phase 2 build.
C-010 — RBAC Engine
Contract ID: C-010
Engine: RBAC / Permission Engine
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
The RBAC Engine is the single permission authority for all 32 modules. It is the highest-frequency engine on the platform — which is exactly why modules must not call it on every user action. See the Scale Problem section below.
⚠️ Scale Problem — and How to Handle It
Calling POST /v1/rbac/check on every user action across 32 modules would generate thousands of synchronous calls per second during peak windows. That makes the RBAC Engine the single point of failure for the entire platform. This must be addressed at the module level — not by making the RBAC Engine bigger.
The right model: two levels of permission checking
| Level | Where | Purpose | Frequency | Freshness |
|---|---|---|---|---|
| UI gating | Frontend | Show/hide features, buttons, nav | Once at session load | Minutes OK |
| Operation enforcement | Backend | Actual security boundary before any write | Per mutation | Near real-time preferred |
Modules must not call POST /v1/rbac/check per user action. Use GET /v1/rbac/permissions (bulk fetch) once at session start, cache the result, and check locally. Call the single-check endpoint only when the cache is cold or has been invalidated.
Team Adaptation Challenge
This pattern requires code changes in every module. That is a real adoption cost and should not be underestimated. The work differs significantly by the module's frontend and backend stack.
The migration tier classification (Tier A–D) referred to here describes how a module integrates with Core — greenfield, modern REST, legacy .NET, or outbox-based. It is not about stored procedures or database internals. See
TD4_ModuleBuildGuidance.mdfor the full tier definitions.
Modern stack — React frontend + stateless backend (Python, Node.js)
These modules can implement the full pattern:
- React fetches
GET /v1/rbac/permissionsonce at login and stores in component state or a state manager (Redux, Zustand) - All UI gating (show/hide buttons, forms, nav) reads from local state — zero RBAC API calls during the session
- The backend (Python/Node.js) caches permission sets in Redis with a 60-second TTL for write-path enforcement
- The backend subscribes to
role.revokedandrole.assignedevents to clear the Redis key immediately
The code changes are moderate — a cache layer and an event subscriber. Modules being built fresh can design this in from the start. For existing modules, the main effort is retrofitting the cache into the existing session/auth flow.
Legacy stack — .NET 4.5, server-rendered templates
These modules cannot easily adopt Redis or an event bus consumer in the short term. The realistic path:
- The server calls
GET /v1/rbac/permissionsonce per user session at login - Stores the result in .NET server-side session (
HttpRuntime.CacheorSession) with a TTL - Every subsequent server request reads from the session cache before rendering the page — no RBAC API call
- Event-driven invalidation is not required for this stack — TTL expiry (maximum 5 minutes) is accepted as the trade-off
The code change is small — one fetch at login and one cache read per request. But it requires the module team to understand where in their existing request lifecycle to hook this in. A team unfamiliar with Core's model may implement it incorrectly on the first attempt. Core must provide a worked example for this stack.
Scanning and Kiosk modules — special case
These modules operate at very high frequency — one scan per second is not unusual during operational windows. Frontend state caching works for the operator's own permissions, but scanning involves checking a different itsId on every scan (the person being scanned, not the operator).
GET /v1/rbac/permissions is designed for one person's session. It does not solve the scanning case where many different itsIds are checked in rapid succession.
For the online case, the API Gateway's TTL response cache on POST /v1/rbac/check absorbs repeated identical checks without hitting the RBAC engine. Scanning modules do not need to implement their own cache for this.
However, Scanning and Kiosk modules also operate offline — and that changes the problem entirely.
These devices run in venues where network connectivity is intermittent or unavailable. RBAC cannot depend on a live call to Core when the device is offline. At the same time, any solution must account for how permission changes and app updates are distributed to potentially hundreds of devices deployed across a Miqaat venue.
This introduces two concerns that go beyond RBAC caching:
- RBAC offline — how does a device verify operator permissions without reaching Core? What is the acceptable staleness window if a role is revoked while the device is offline?
- Device release management — when Core's permission model changes, or the scanning app is updated, how are those changes pushed to all deployed devices? This is a module-level deployment concern that needs its own answer.
⏳ Pending: Dedicated technical session required before implementation.
The offline RBAC approach and the device release strategy for Scanning and Kiosk modules must be decided in a dedicated session. The right approach depends on venue network conditions, the acceptable security trade-off for offline operation, and how the module team manages device deployments. This cannot be prescribed here without that discussion.
What Core Will Provide to Help
The burden cannot be left entirely to module teams. Core's responsibility:
| Deliverable | What it does |
|---|---|
GET /v1/rbac/permissions endpoint (see below) | Bulk fetch all permissions for one itsId in one miqaatId — one call replaces hundreds |
| Reference implementation per stack | Worked example: React + stateless backend, .NET 4.5 server session |
| Module onboarding checklist item | "RBAC caching implemented correctly" is a sign-off criterion before a module goes live |
| Gateway-level TTL cache | Core configures this on POST /v1/rbac/check — modules get it for free, no code change needed |
Without reference implementations, 32 module teams will independently solve the same problem in 32 different ways — producing 32 different bugs.
POST /v1/rbac/check
Auth: User-initiated (X-Its-Id required)
SLA: < 50 ms p95
When to use: Cache miss, post-invalidation, or when a one-off check is needed outside of a full session (e.g. internal Core engine cross-checks). Not for per-action calls from module UIs — use GET /v1/rbac/permissions for that.
Asks: Can this itsId perform this action on this resource within this Miqaat scope?
Returns a structured answer — not a hard error. A denied check is a valid business answer (200 OK), not a 403.
Permission string format
Permissions follow resource:action notation — the same convention as AWS IAM. Examples: volunteer:approve, allocation:view, report:export.
Wildcard permissions (volunteer:*) are supported at the action level only. A role holding volunteer:* passes any check where resource == "volunteer", regardless of action. Resource-level wildcards (*:approve) are not supported.
Implication: if a module adds a new action to a resource (e.g.,
volunteer:bulk_export), any role holdingvolunteer:*automatically grants it — no role update required. Platform Admin must be aware of this when assigning wildcard-level roles. See 07 — Authorization Model for the full evaluation logic.
Request
{
"action": "approve",
"resource": "volunteer",
"miqaatId": "MQ-1447-KHI"
}
itsIdis not in the request body — it is read from theX-Its-Idheader set by the Identity Bridge. Never pass itsId in the request body.
| Field | Type | Required | Description |
|---|---|---|---|
action | string | ✅ | The action being checked (e.g. approve, view, edit, export) |
resource | string | ✅ | The resource type the action applies to (e.g. volunteer, allocation, report) |
miqaatId | string | ✅ | The Miqaat scope for this check. Permissions are always scoped to a Miqaat. |
Response — allowed
{
"status": "success",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"allowed": true,
"roleMatched": "VMS_Coordinator",
"reasonCode": "ROLE_GRANTS_ACTION"
}
}
Response — denied
{
"status": "success",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"allowed": false,
"roleMatched": null,
"reasonCode": "NO_MATCHING_ROLE"
}
}
reasonCode | Meaning |
|---|---|
ROLE_GRANTS_ACTION | A role held by this itsId grants this action on this resource |
NO_MATCHING_ROLE | No role held by this itsId grants this action |
ROLE_SCOPE_MISMATCH | A matching role exists but not scoped to this miqaatId |
MIQAAT_CLOSED | The Miqaat is archived — no permission checks are valid |
Error responses
| Code | HTTP | Cause |
|---|---|---|
AUTH_ITS_ID_REQUIRED | 403 | X-Its-Id header missing — system-initiated call attempted on a user-only endpoint |
RBAC_INVALID_SCOPE | 400 | miqaatId is missing or does not exist |
AUTH_INVALID_API_KEY | 401 | Module API key is missing or invalid |
GET /v1/rbac/permissions
Auth: User-initiated (X-Its-Id required)
SLA: < 100 ms p95
Use: Call once at session start. Cache the result. Do not call per action.
Returns all permissions held by the authenticated itsId in the given Miqaat scope. This is the bulk-fetch endpoint that replaces per-action POST /v1/rbac/check calls. Module teams cache this response and evaluate permissions locally.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat scope to resolve permissions for |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"roles": [
{ "roleId": "VMS_Coordinator", "level": "miqaat" }
],
"permissions": [
"volunteer:approve",
"volunteer:view",
"volunteer:edit",
"allocation:view",
"report:export"
],
"resolvedAt": "2026-07-15T10:00:00Z"
}
}
How to use the cached response
// .NET — store in server session at login
Session["core_permissions"] = response.data.permissions;
Session["core_permissions_expiry"] = DateTime.UtcNow.AddMinutes(5);
// Python — store in Redis with TTL
redis.setex(f"rbac:{itsId}:{miqaatId}", 60, json.dumps(permissions))
// React — store in state at module load
const { permissions } = await fetchRbacPermissions(miqaatId);
setPermissions(permissions);
Cache invalidation
Subscribe to role.revoked and role.assigned events from the event bus. When either event fires for the current itsId + miqaatId, clear the local cache and re-fetch.
- Tier A/B modules: event-driven invalidation required
- Tier C/D modules: TTL expiry only is acceptable (5 minutes maximum TTL)
Error responses
| Code | HTTP | Cause |
|---|---|---|
AUTH_ITS_ID_REQUIRED | 403 | X-Its-Id header missing |
RBAC_INVALID_SCOPE | 400 | miqaatId does not exist |
GET /v1/rbac/assignments/{itsId}
Auth: User-initiated or system-initiated
SLA: < 100 ms p95
Access: Platform Admin only (module API keys rejected)
Note — role definitions are not a module-facing contract. Listing all role definitions on the platform (
GET /v1/rbac/roles) is a Platform Admin internal tool, not a module API. Modules do not assign roles and do not need to enumerate the full role catalog at runtime. The role name held by the current user is already present in theGET /v1/rbac/permissionsresponse (roles[].roleId). Module teams can reference role definitions in the architecture documents.
Returns all role assignments for a given itsId across all Miqaat scopes. Used by Platform Admin to audit what roles a person holds.
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"itsId": "ITS-1234567",
"assignments": [
{
"roleId": "VMS_Coordinator",
"miqaatId": "MQ-1447-KHI",
"assignedAt": "2026-06-01T08:00:00Z",
"assignedBy": "ITS-9999999"
}
]
}
}
C-011 — Eligibility Engine
Contract ID: C-011
Engine: Eligibility Evaluation Engine
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
Evaluates composite eligibility for a Mumin against a Miqaat. Pulls from 4 data sources at evaluation time. Returns a structured result — not a simple pass/fail.
POST /v1/eligibility/check
Auth: User-initiated or system-initiated (both accepted)
SLA: < 300 ms p95 single check · < 2 s p95 batch (TBC — pending load testing)
Request
{
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"ruleSetId": "ISTEFADAH_2026_GENTS",
"context": {
"category": "residential",
"zone": "KHI-NORTH"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin to evaluate |
miqaatId | string | ✅ | Scopes the eligibility check to this Miqaat |
ruleSetId | string | ✅ | The rule set to evaluate against (registered in Rule Engine for this Miqaat) |
context | object | ❌ | Additional context fields used by certain rule families (category, zone, role, etc.) |
Response — eligible
{
"status": "success",
"requestId": "uuid",
"data": {
"eligible": true,
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"ruleSetId": "ISTEFADAH_2026_GENTS",
"rulesEvaluated": [
{ "ruleId": "AGE_MIN_15", "passed": true },
{ "ruleId": "PRIOR_ATTENDANCE", "passed": true },
{ "ruleId": "LEARNING_COMPLETE", "passed": true }
],
"evaluatedAt": "2026-06-25T14:30:00Z"
}
}
Response — ineligible
{
"status": "success",
"requestId": "uuid",
"data": {
"eligible": false,
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"ruleSetId": "ISTEFADAH_2026_GENTS",
"rulesEvaluated": [
{ "ruleId": "AGE_MIN_15", "passed": true },
{ "ruleId": "PRIOR_ATTENDANCE", "passed": false, "reason": "No confirmed attendance in last 3 years" },
{ "ruleId": "LEARNING_COMPLETE", "passed": true }
],
"evaluatedAt": "2026-06-25T14:30:00Z"
}
}
Error responses
| Code | HTTP | Cause |
|---|---|---|
ELIGIBILITY_RULE_NOT_FOUND | 404 | ruleSetId not configured for this miqaatId |
ELIGIBILITY_DATA_UNAVAILABLE | 503 | ITS sync store or HR Bank temporarily unavailable |
RBAC_INVALID_SCOPE | 400 | miqaatId does not exist |
GET /v1/eligibility/rules
Auth: User-initiated or system-initiated
SLA: < 150 ms p95
Lists all rule sets registered for a given Miqaat. Used by module UIs and ops teams to understand which rule sets exist and which rules they contain.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | Return rule sets registered for this Miqaat |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"miqaatId": "MQ-1447-KHI",
"ruleSets": [
{
"ruleSetId": "ISTEFADAH_2026_GENTS",
"displayName": "Istefadah 2026 — Gents",
"rules": [
{ "ruleId": "AGE_MIN_15", "family": "demographic", "description": "Minimum age 15" },
{ "ruleId": "PRIOR_ATTENDANCE", "family": "history", "description": "At least one confirmed attendance in last 3 years" },
{ "ruleId": "LEARNING_COMPLETE","family": "learning", "description": "Required learning module completed" }
]
}
]
}
}
C-012 — Config Cascade Engine
Contract ID: C-012
Engine: Config Cascade Engine
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
Returns the resolved effective value of a config key at the correct level of the cascade (Global → Miqaat → type-specific context levels). A module never needs to know which level set a value — it calls resolve and gets the answer.
GET /v1/config/resolve
Auth: User-initiated or system-initiated (both accepted)
SLA: < 20 ms p95 (highly cacheable — TTL cache layer in front of the store)
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
key | string | ✅ | Config key to resolve (e.g. registration.open, allocation.mode, notify.channels) |
miqaatId | string | ✅ | Resolve the key in the context of this Miqaat |
context | string | ❌ | Additional context level key (e.g. zone=KHI-NORTH) for type-specific cascade levels |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"key": "registration.open",
"value": true,
"resolvedAt": "miqaat",
"miqaatId": "MQ-1447-KHI"
}
}
resolvedAt | Meaning |
|---|---|
global | No Miqaat-level or context-level override — global default applies |
miqaat | Miqaat-level override set — overrides global |
context | Type-specific context level override set — overrides Miqaat |
Error responses
| Code | HTTP | Cause |
|---|---|---|
CONFIG_NOT_FOUND | 404 | Key not set at any cascade level for this context |
CONFIG_INVALID_SCOPE | 400 | miqaatId does not exist or is not recognised |
C-013 — Miqaat Lifecycle Engine (Read)
Contract ID: C-013
Engine: Miqaat Lifecycle Engine
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
Note: Write endpoints (POST /v1/miqaat, POST /v1/miqaat/{id}/transition) are Platform Admin only and not documented here.
GET /v1/miqaat/{miqaatId}
Auth: User-initiated or system-initiated
SLA: < 100 ms p95
Returns the current state of a Miqaat — phase, dates, structural config, and capacity overview.
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"miqaatId": "MQ-1447-KHI",
"name": "Ashara Mubaraka 1447 — Karachi",
"type": "ashara_mubaraka",
"phase": "registration_open",
"phases": {
"setup": "completed",
"registration_open": "active",
"registration_closed": "pending",
"operational": "pending",
"closed": "pending",
"archived": "pending"
},
"dates": {
"registrationOpen": "2026-07-01T00:00:00Z",
"registrationClose": "2026-08-01T00:00:00Z",
"miqaatStart": "2026-10-01T00:00:00Z",
"miqaatEnd": "2026-10-10T00:00:00Z"
},
"structure": {
"type": "ashara_mubaraka",
"levels": ["city", "zone", "bethak"]
},
"createdAt": "2026-05-01T08:00:00Z"
}
}
Error responses
| Code | HTTP | Cause |
|---|---|---|
MIQAAT_NOT_FOUND | 404 | miqaatId does not exist |
GET /v1/miqaat
Auth: User-initiated or system-initiated
SLA: < 150 ms p95
Lists all active (non-archived) Miqaats. Used by modules that need to present a Miqaat picker to users.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
status | string | ❌ | Filter by phase (e.g. registration_open, operational). Default: all non-archived. |
limit | integer | ❌ | Default 20, max 100 |
cursor | string | ❌ | Pagination cursor |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"items": [
{
"miqaatId": "MQ-1447-KHI",
"name": "Ashara Mubaraka 1447 — Karachi",
"type": "ashara_mubaraka",
"phase": "registration_open"
}
],
"pagination": { "cursor": null, "hasMore": false, "total": null }
}
}
C-014 — Allocation Engine
Contract ID: C-014
Engine: Allocation Engine
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
Modules submit allocation requests — Core resolves them, prevents double-booking across all 32 modules, and returns the outcome. The Allocation Engine is the single code path for all seat and slot decisions.
POST /v1/allocation/request
Auth: User-initiated or system-initiated (both accepted)
SLA: < 500 ms p95 single request · TBC under concurrent load
Idempotency: X-Idempotency-Key supported — safe to retry on network failure
Request
{
"miqaatId": "MQ-1447-KHI",
"itsId": "ITS-1234567",
"category": "residential",
"zone": "KHI-NORTH",
"slots": 1,
"requestedBy": "vms"
}
| Field | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat to allocate within |
itsId | string | ✅ | The Mumin receiving the allocation |
category | string | ✅ | Allocation category (e.g. residential, day_visitor, khidmatguzar) |
zone | string | ❌ | Zone preference — used in zone-quota allocation modes |
slots | integer | ✅ | Number of slots requested. Usually 1. |
requestedBy | string | ✅ | Module identifier — must match X-Module-Id header |
Response — confirmed
{
"status": "success",
"requestId": "uuid",
"data": {
"allocationId": "ALO-20261001-00042",
"outcome": "confirmed",
"miqaatId": "MQ-1447-KHI",
"itsId": "ITS-1234567",
"category": "residential",
"zone": "KHI-NORTH",
"confirmedAt": "2026-07-15T10:30:00Z"
}
}
Response — waitlisted
{
"status": "success",
"requestId": "uuid",
"data": {
"allocationId": "ALO-20261001-00043",
"outcome": "waitlisted",
"miqaatId": "MQ-1447-KHI",
"itsId": "ITS-1234567",
"category": "residential",
"waitlistPosition": 14,
"waitlistedAt": "2026-07-15T10:30:01Z"
}
}
Error responses
| Code | HTTP | Cause |
|---|---|---|
ALLOCATION_FULL | 409 | No seats/slots remaining and no waitlist configured |
ALLOCATION_DUPLICATE | 409 | This itsId already has an allocation for this miqaatId + category |
ALLOCATION_RULE_REJECTED | 422 | Rule Engine rejected the request (e.g. eligibility gate not passed) |
ALLOCATION_MIQAAT_CLOSED | 409 | Allocation phase for this Miqaat is not currently open |
MIQAAT_NOT_FOUND | 404 | miqaatId does not exist |
GET /v1/allocation/status
Auth: User-initiated or system-initiated
SLA: < 100 ms p95
Returns the current allocation status for a given itsId in a given Miqaat.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin to look up |
miqaatId | string | ✅ | The Miqaat scope |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"allocations": [
{
"allocationId": "ALO-20261001-00042",
"category": "residential",
"zone": "KHI-NORTH",
"outcome": "confirmed",
"confirmedAt": "2026-07-15T10:30:00Z"
}
]
}
}
C-015 — Zone Mapping Engine
Contract ID: C-015
Engine: Zone ↔ Resident Mapping Engine
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
Source of truth for zone-to-resident counts. Modules never compute zone populations themselves — they query Core.
GET /v1/zones/resident-count
Auth: User-initiated or system-initiated
SLA: < 100 ms p95
Returns the resident count for one or more zones.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat scope (zone definitions may vary by Miqaat type) |
zone | string | ❌ | A specific zone ID. Omit to return all zones for this Miqaat. |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"miqaatId": "MQ-1447-KHI",
"syncedAt": "2026-07-14T23:00:00Z",
"zones": [
{ "zoneId": "KHI-NORTH", "residentCount": 4820 },
{ "zoneId": "KHI-SOUTH", "residentCount": 3210 },
{ "zoneId": "KHI-EAST", "residentCount": 2950 }
]
}
}
syncedAtindicates when the ITS sync last updated this data. Modules should surface this to ops teams if staleness matters for their decisions.
GET /v1/zones/mapping
Auth: User-initiated or system-initiated
SLA: < 100 ms p95
Returns the full zone structure for a Miqaat — zone IDs, display names, and hierarchy (if zones have sub-zones).
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat scope |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"miqaatId": "MQ-1447-KHI",
"zones": [
{
"zoneId": "KHI-NORTH",
"displayName": "Karachi North",
"subZones": ["KHI-NORTH-A", "KHI-NORTH-B"]
}
]
}
}
C-016 — Vendor Registry
Contract ID: C-016
Engine: Vendor Registry
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
Provides shared vendor catalog lookups and budget envelope checks. Modules run their own indent/approval workflows — Core only owns the shared vendor data and the budget ceiling.
GET /v1/vendors
Auth: User-initiated or system-initiated
SLA: < 150 ms p95
Lists vendors from the shared catalog, optionally filtered by city and category.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
city | string | ❌ | Filter by city (e.g. KHI, MUM, HYD) |
category | string | ❌ | Filter by procurement category (e.g. seating, catering, transport, printing) |
miqaatId | string | ❌ | Filter to vendors approved for this Miqaat |
limit | integer | ❌ | Default 20, max 100 |
cursor | string | ❌ | Pagination cursor |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"items": [
{
"vendorId": "VND-KHI-00142",
"name": "Al-Kareem Catering",
"city": "KHI",
"categories": ["catering"],
"approvedFor": ["MQ-1447-KHI"],
"contactName": "Ahmed Hussain",
"contactPhone": "+92-300-1234567"
}
],
"pagination": { "cursor": null, "hasMore": false, "total": null }
}
}
POST /v1/procurement/budget-check
Auth: User-initiated or system-initiated
SLA: < 100 ms p95
Idempotency: Not required (read-only check — does not create state)
Checks whether a proposed procurement amount is within the Miqaat's overall budget envelope. Modules call this before raising an indent.
Request
{
"miqaatId": "MQ-1447-KHI",
"module": "ams",
"category": "catering",
"amount": 50000,
"currency": "INR"
}
Response — within budget
{
"status": "success",
"requestId": "uuid",
"data": {
"withinBudget": true,
"miqaatId": "MQ-1447-KHI",
"category": "catering",
"requested": 50000,
"remaining": 220000,
"currency": "INR"
}
}
Response — over budget
{
"status": "success",
"requestId": "uuid",
"data": {
"withinBudget": false,
"miqaatId": "MQ-1447-KHI",
"category": "catering",
"requested": 50000,
"remaining": 30000,
"shortfall": 20000,
"currency": "INR"
}
}
Error responses
| Code | HTTP | Cause |
|---|---|---|
CONFIG_NOT_FOUND | 404 | No budget envelope configured for this Miqaat + category |
MIQAAT_NOT_FOUND | 404 | miqaatId does not exist |
C-017 — HR Bank
Contract ID: C-017
Engine: HR Bank
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
Cross-Miqaat khidmat history per itsId. Modules query this for eligibility gating and volunteer matching. Modules never write to HR Bank directly — khidmat records are written via events.
GET /v1/hr/history/{itsId}
Auth: User-initiated or system-initiated
SLA: < 200 ms p95
Returns the full khidmat history for a Mumin across all Miqaats.
Path parameters
| Param | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin's ITS ID |
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ❌ | Filter history to a specific Miqaat |
limit | integer | ❌ | Default 20, max 100 |
cursor | string | ❌ | Pagination cursor |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"itsId": "ITS-1234567",
"items": [
{
"recordId": "HR-2024-00892",
"miqaatId": "MQ-1445-MUM",
"miqaatName": "Ashara Mubaraka 1445 — Mumbai",
"role": "VMS_Coordinator",
"isPrimary": true,
"status": "completed",
"recordedAt": "2024-10-10T00:00:00Z"
}
],
"pagination": { "cursor": null, "hasMore": false, "total": null }
}
}
GET /v1/hr/quota
Auth: User-initiated or system-initiated
SLA: < 150 ms p95
Returns whether a Mumin's one-primary-khidmat quota is available for a given Miqaat. Used by VMS and similar modules before assigning a primary khidmat role.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
itsId | string | ✅ | The Mumin to check |
miqaatId | string | ✅ | The Miqaat scope for the quota check |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"primaryQuotaFree": true,
"currentPrimaryRole": null
}
}
primaryQuotaFree | Meaning |
|---|---|
true | No primary khidmat assigned for this Miqaat — quota is available |
false | A primary khidmat is already assigned — currentPrimaryRole will be set |
C-018 — Capacity Balance Engine
Contract ID: C-018
Engine: Capacity Balance Engine
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
The Capacity Balance Engine is event-driven — it reacts to capacity.configured events published by modules, not to direct API calls. The single read endpoint is for Platform Admin status queries.
GET /v1/capacity/status
Auth: User-initiated or system-initiated
SLA: < 150 ms p95
Returns the current capacity balance state for a Miqaat — the aggregated capacity figures received from each module, and whether any imbalance has been detected.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat to check |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"miqaatId": "MQ-1447-KHI",
"balanceStatus": "imbalance_detected",
"capacities": [
{ "venue": "vaaz", "capacity": 8000, "reportedBy": "venue-module", "reportedAt": "2026-06-10T09:00:00Z" },
{ "venue": "mawaid", "capacity": 3200, "reportedBy": "mawaid-module", "reportedAt": "2026-06-11T11:00:00Z" },
{ "venue": "kitchen", "capacity": 4000, "reportedBy": "kitchen-module","reportedAt": "2026-06-12T08:00:00Z" }
],
"imbalances": [
{
"description": "Vaaz capacity (8,000) is more than double Mawaid capacity (3,200). Ratio exceeds threshold.",
"detectedAt": "2026-06-12T08:00:01Z"
}
]
}
}
balanceStatus | Meaning |
|---|---|
balanced | All reported capacities are within acceptable ratios |
imbalance_detected | One or more capacity ratios exceed threshold — see imbalances |
incomplete | Not all expected modules have reported capacity yet |
C-019 — Task Engine
Contract ID: C-019
Engine: Task Engine (Platform Layer)
Version: 1.0.0 (draft)
Status: Draft
Owner: Core team
Platform-level tasks that span modules or Miqaat phases. Module-specific checklists are not here — they live inside each module.
GET /v1/tasks
Auth: User-initiated or system-initiated
SLA: < 150 ms p95
Returns platform-level tasks for a given Miqaat, optionally filtered by phase or completion status.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ✅ | The Miqaat scope |
phase | string | ❌ | Filter to tasks due in a specific Miqaat phase |
status | string | ❌ | pending, completed, or all. Default: all |
limit | integer | ❌ | Default 20, max 100 |
cursor | string | ❌ | Pagination cursor |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"miqaatId": "MQ-1447-KHI",
"items": [
{
"taskId": "TSK-CORE-001",
"title": "Core contracts signed before Phase 0 closes",
"phase": "setup",
"dueBy": "2026-07-01T00:00:00Z",
"status": "pending",
"assignedTo": "platform_admin",
"completedAt": null
}
],
"pagination": { "cursor": null, "hasMore": false, "total": null }
}
}
POST /v1/tasks/{taskId}/complete
Auth: User-initiated (X-Its-Id required)
SLA: < 200 ms p95
Marks a platform-level task as complete. The itsId from the header is recorded as the completing user.
Path parameters
| Param | Type | Required | Description |
|---|---|---|---|
taskId | string | ✅ | The task to complete |
Request
{
"miqaatId": "MQ-1447-KHI",
"notes": "All module teams have countersigned the Core integration agreement."
}
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"taskId": "TSK-CORE-001",
"status": "completed",
"completedBy": "ITS-9999999",
"completedAt": "2026-06-25T14:00:00Z"
}
}
Error responses
| Code | HTTP | Cause |
|---|---|---|
TASK_NOT_FOUND | 404 | taskId does not exist for this Miqaat |
TASK_ALREADY_COMPLETE | 409 | Task was already marked complete |
AUTH_ITS_ID_REQUIRED | 403 | Completion requires a verified user — system-initiated call rejected |
Audit Log — Read Contract (Platform Admin Only)
The Audit Log Backbone does not have a module-facing read contract. Only Platform Admin can query audit logs.
GET /v1/audit/query
Auth: Platform Admin only (module API keys rejected)
SLA: < 2 s p95 (query performance depends on time range and filter depth)
Queries the immutable audit log. Read-only. Cannot modify, delete, or update any record.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
miqaatId | string | ❌ | Filter to a specific Miqaat |
engine | string | ❌ | Filter to events from a specific engine (e.g. rbac, allocation) |
itsId | string | ❌ | Filter to actions involving this itsId |
from | ISO 8601 | ❌ | Start of time range |
to | ISO 8601 | ❌ | End of time range |
limit | integer | ❌ | Default 50, max 500 |
cursor | string | ❌ | Pagination cursor |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"items": [
{
"auditId": "AUD-20260625-000001",
"engine": "allocation",
"action": "allocation.confirmed",
"miqaatId": "MQ-1447-KHI",
"itsId": "ITS-1234567",
"moduleId": "vms",
"details": { "allocationId": "ALO-20261001-00042", "category": "residential" },
"recordedAt": "2026-07-15T10:30:00Z"
}
],
"pagination": { "cursor": "eyJpZCI6IjEwMCJ9", "hasMore": true, "total": null }
}
}
Notification Dispatch
POST /v1/notify
Auth: User-initiated or system-initiated (both accepted)
SLA: < 200 ms p95 to accept and queue · delivery latency depends on channel (WhatsApp: TBC)
Idempotency: X-Idempotency-Key supported
Request
{
"miqaatId": "MQ-1447-KHI",
"audience": {
"type": "itsIds",
"itsIds": ["ITS-1234567", "ITS-7654321"]
},
"channels": ["whatsapp", "email"],
"templateId": "ALLOCATION_CONFIRMED",
"variables": {
"allocationId": "ALO-20261001-00042",
"category": "residential"
}
}
audience.type | Meaning |
|---|---|
itsIds | Send to a specific list of itsIds |
role | Send to all itsIds holding a given role in this Miqaat (requires roleId field) |
zone | Send to all itsIds in a given zone (requires zoneId field) |
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"dispatchId": "NTF-20260715-00089",
"queued": true,
"recipientCount": 2,
"channels": ["whatsapp", "email"],
"queuedAt": "2026-07-15T10:31:00Z"
}
}
Delivery is async. The response confirms queuing, not delivery. Delivery receipts are tracked internally and visible in Platform Admin.
ITS Sync Engine — Internal Endpoints
These endpoints are not available to module API keys. They are Platform Admin and Core infrastructure only.
GET /v1/its/mumin/{itsId}
Auth: Platform Admin / Internal only
SLA: < 100 ms p95
Returns Core's locally-held copy of the ITS record for a given itsId. Used by Platform Admin to verify sync data and by Core engines internally.
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"itsId": "ITS-1234567",
"syncedAt": "2026-07-14T23:00:00Z",
"profile": {
"fullName": "Pending ITS Technical session — field names TBC",
"jamaat": "TBC",
"zone": "TBC"
}
}
}
Field names in
profileare placeholders — exact ITS schema confirmed in the ITS Technical session (C-005).
GET /v1/its/sync/status
Auth: Platform Admin only
SLA: < 50 ms p95
Returns the status of the last ITS sync run.
Response
{
"status": "success",
"requestId": "uuid",
"data": {
"lastFullSync": "2026-07-14T00:00:00Z",
"lastDeltaSync": "2026-07-15T06:00:00Z",
"syncStatus": "healthy",
"recordsUpdated": 142,
"errors": 0
}
}
Pending Contracts
These contracts are not documented here because they depend on pending session outcomes.
| Contract | Blocked On | Impact |
|---|---|---|
| C-001 — ITS SSO Token | ITS Technical session | Identity Bridge cannot be fully specified |
| C-005 — ITS ↔ Core Sync | ITS Technical session | ITS Sync Engine schema (field names in profile above are placeholders) |
| Rule Engine write contract | Core architecture session | Ops-facing rule configuration UI |
| Event publish contracts (C-020 to C-026) | Event Catalog design (Doc 04) | Module event subscription specs |