Skip to main content

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

LevelWherePurposeFrequencyFreshness
UI gatingFrontendShow/hide features, buttons, navOnce at session loadMinutes OK
Operation enforcementBackendActual security boundary before any writePer mutationNear 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.md for 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/permissions once 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.revoked and role.assigned events 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/permissions once per user session at login
  • Stores the result in .NET server-side session (HttpRuntime.Cache or Session) 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:

  1. 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?
  2. 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:

DeliverableWhat 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 stackWorked 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 cacheCore 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 holding volunteer:* 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"
}

itsId is not in the request body — it is read from the X-Its-Id header set by the Identity Bridge. Never pass itsId in the request body.

FieldTypeRequiredDescription
actionstringThe action being checked (e.g. approve, view, edit, export)
resourcestringThe resource type the action applies to (e.g. volunteer, allocation, report)
miqaatIdstringThe 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"
}
}
reasonCodeMeaning
ROLE_GRANTS_ACTIONA role held by this itsId grants this action on this resource
NO_MATCHING_ROLENo role held by this itsId grants this action
ROLE_SCOPE_MISMATCHA matching role exists but not scoped to this miqaatId
MIQAAT_CLOSEDThe Miqaat is archived — no permission checks are valid

Error responses

CodeHTTPCause
AUTH_ITS_ID_REQUIRED403X-Its-Id header missing — system-initiated call attempted on a user-only endpoint
RBAC_INVALID_SCOPE400miqaatId is missing or does not exist
AUTH_INVALID_API_KEY401Module 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

ParamTypeRequiredDescription
miqaatIdstringThe 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

CodeHTTPCause
AUTH_ITS_ID_REQUIRED403X-Its-Id header missing
RBAC_INVALID_SCOPE400miqaatId 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 the GET /v1/rbac/permissions response (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"
}
}
FieldTypeRequiredDescription
itsIdstringThe Mumin to evaluate
miqaatIdstringScopes the eligibility check to this Miqaat
ruleSetIdstringThe rule set to evaluate against (registered in Rule Engine for this Miqaat)
contextobjectAdditional 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

CodeHTTPCause
ELIGIBILITY_RULE_NOT_FOUND404ruleSetId not configured for this miqaatId
ELIGIBILITY_DATA_UNAVAILABLE503ITS sync store or HR Bank temporarily unavailable
RBAC_INVALID_SCOPE400miqaatId 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

ParamTypeRequiredDescription
miqaatIdstringReturn 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

ParamTypeRequiredDescription
keystringConfig key to resolve (e.g. registration.open, allocation.mode, notify.channels)
miqaatIdstringResolve the key in the context of this Miqaat
contextstringAdditional 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"
}
}
resolvedAtMeaning
globalNo Miqaat-level or context-level override — global default applies
miqaatMiqaat-level override set — overrides global
contextType-specific context level override set — overrides Miqaat

Error responses

CodeHTTPCause
CONFIG_NOT_FOUND404Key not set at any cascade level for this context
CONFIG_INVALID_SCOPE400miqaatId 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

CodeHTTPCause
MIQAAT_NOT_FOUND404miqaatId 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

ParamTypeRequiredDescription
statusstringFilter by phase (e.g. registration_open, operational). Default: all non-archived.
limitintegerDefault 20, max 100
cursorstringPagination 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"
}
FieldTypeRequiredDescription
miqaatIdstringThe Miqaat to allocate within
itsIdstringThe Mumin receiving the allocation
categorystringAllocation category (e.g. residential, day_visitor, khidmatguzar)
zonestringZone preference — used in zone-quota allocation modes
slotsintegerNumber of slots requested. Usually 1.
requestedBystringModule 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

CodeHTTPCause
ALLOCATION_FULL409No seats/slots remaining and no waitlist configured
ALLOCATION_DUPLICATE409This itsId already has an allocation for this miqaatId + category
ALLOCATION_RULE_REJECTED422Rule Engine rejected the request (e.g. eligibility gate not passed)
ALLOCATION_MIQAAT_CLOSED409Allocation phase for this Miqaat is not currently open
MIQAAT_NOT_FOUND404miqaatId 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

ParamTypeRequiredDescription
itsIdstringThe Mumin to look up
miqaatIdstringThe 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

ParamTypeRequiredDescription
miqaatIdstringThe Miqaat scope (zone definitions may vary by Miqaat type)
zonestringA 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 }
]
}
}

syncedAt indicates 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

ParamTypeRequiredDescription
miqaatIdstringThe 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

ParamTypeRequiredDescription
citystringFilter by city (e.g. KHI, MUM, HYD)
categorystringFilter by procurement category (e.g. seating, catering, transport, printing)
miqaatIdstringFilter to vendors approved for this Miqaat
limitintegerDefault 20, max 100
cursorstringPagination 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

CodeHTTPCause
CONFIG_NOT_FOUND404No budget envelope configured for this Miqaat + category
MIQAAT_NOT_FOUND404miqaatId 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

ParamTypeRequiredDescription
itsIdstringThe Mumin's ITS ID

Query parameters

ParamTypeRequiredDescription
miqaatIdstringFilter history to a specific Miqaat
limitintegerDefault 20, max 100
cursorstringPagination 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

ParamTypeRequiredDescription
itsIdstringThe Mumin to check
miqaatIdstringThe Miqaat scope for the quota check

Response

{
"status": "success",
"requestId": "uuid",
"data": {
"itsId": "ITS-1234567",
"miqaatId": "MQ-1447-KHI",
"primaryQuotaFree": true,
"currentPrimaryRole": null
}
}
primaryQuotaFreeMeaning
trueNo primary khidmat assigned for this Miqaat — quota is available
falseA 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

ParamTypeRequiredDescription
miqaatIdstringThe 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"
}
]
}
}
balanceStatusMeaning
balancedAll reported capacities are within acceptable ratios
imbalance_detectedOne or more capacity ratios exceed threshold — see imbalances
incompleteNot 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

ParamTypeRequiredDescription
miqaatIdstringThe Miqaat scope
phasestringFilter to tasks due in a specific Miqaat phase
statusstringpending, completed, or all. Default: all
limitintegerDefault 20, max 100
cursorstringPagination 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

ParamTypeRequiredDescription
taskIdstringThe 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

CodeHTTPCause
TASK_NOT_FOUND404taskId does not exist for this Miqaat
TASK_ALREADY_COMPLETE409Task was already marked complete
AUTH_ITS_ID_REQUIRED403Completion 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

ParamTypeRequiredDescription
miqaatIdstringFilter to a specific Miqaat
enginestringFilter to events from a specific engine (e.g. rbac, allocation)
itsIdstringFilter to actions involving this itsId
fromISO 8601Start of time range
toISO 8601End of time range
limitintegerDefault 50, max 500
cursorstringPagination 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.typeMeaning
itsIdsSend to a specific list of itsIds
roleSend to all itsIds holding a given role in this Miqaat (requires roleId field)
zoneSend 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 profile are 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.

ContractBlocked OnImpact
C-001 — ITS SSO TokenITS Technical sessionIdentity Bridge cannot be fully specified
C-005 — ITS ↔ Core SyncITS Technical sessionITS Sync Engine schema (field names in profile above are placeholders)
Rule Engine write contractCore architecture sessionOps-facing rule configuration UI
Event publish contracts (C-020 to C-026)Event Catalog design (Doc 04)Module event subscription specs