Skip to main content

02 — API Design Standards

Status: ✅ Draft
Prerequisite: TD3, 01 — Service Decomposition
Next: 03 — API Contracts

Conventions that apply to every Core API endpoint, across all 15 services. Module teams read this once and understand how every Core API behaves — before reading individual contracts.

Everything here is confirmed from TD2 and TD3. Individual contracts in 03 — API Contracts inherit these standards — they do not restate them.


1. Base URL and Versioning

All Core services are accessed through a single platform URL. Modules never call individual service addresses.

https://core.miqaat.platform/v1/{service-path}

The v1 segment is the API version. It is part of the path, not a header.

Versioning rules (confirmed in TD3):

Change typeVersion bumpModule impact
New optional field added to responseMINORSafe — existing modules ignore new fields
New optional request parameterMINORSafe — existing modules omit it
Removing a response fieldMAJORBreaking — requires coordinated migration
Renaming a fieldMAJORBreaking
Changing a field's data typeMAJORBreaking
Changing an endpoint pathMAJORBreaking
Removing an endpointMAJORBreaking
Changing error codes modules handleMAJORBreaking

A MAJOR version bump means the old version stays live alongside the new one until all modules have migrated. No engine breaks a module by bumping versions unilaterally.


2. Request Headers

Every call to Core requires these headers. The API Gateway validates them before routing.

Required on every request

HeaderValueSet by
AuthorizationBearer <ITS-token>Module (user-initiated calls)
X-Module-IdModule identifier assigned by Platform Admin (e.g. vms, misaaq, prefix)Module
X-Request-IdUUID generated per request — for tracingModule

Set by the Identity Bridge (not by the module)

The Identity Bridge at the Gateway validates credentials, strips them, and passes clean headers downstream to Core engines. Engines never see the raw token or API key.

HeaderValueNotes
X-Its-IdVerified itsId extracted from the ITS tokenOnly present on user-initiated calls
X-Correlation-IdThe module's X-Request-Id value, copied and renamed by the BridgePropagated through the full request chain — engine → internal engine calls → logs. Echoed back as requestId in the response envelope.

Tracing chain: the module generates a UUID as X-Request-Id → the Bridge renames it to X-Correlation-Id and forwards it downstream → every engine includes it in its logs → if that engine calls another engine internally, it passes the same X-Correlation-Id forward → the engine echoes it back as requestId in the response. The module can surface this value to users so support can trace the full request path across all engines from a single ID. The value has no lifecycle beyond the request — it is not stored, only logged.

The two call types

User-initiated call — a human action triggers the Core call. The module includes the user's ITS bearer token.

POST /v1/rbac/check
Authorization: Bearer <ITS-token>
X-Module-Id: vms
X-Request-Id: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

After Identity Bridge processing, the engine receives:

X-Module-Id: vms
X-Its-Id: ITS-1234567
X-Correlation-Id: 550e8400-e29b-41d4-a716-446655440000

System-initiated call — an automated process calls Core with no human user (batch jobs, scheduled syncs, module-to-Core background processes). No ITS token is included.

POST /v1/allocation/request
Authorization: Bearer <API-key>
X-Module-Id: vms
X-Request-Id: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
Content-Type: application/json

After Identity Bridge processing, the engine receives:

X-Module-Id: vms
(no X-Its-Id)
X-Correlation-Id: 6ba7b810-9dad-11d1-80b4-00c04fd430c8

Endpoints that require a human context (permission checks, eligibility checks) reject system-initiated calls with AUTH_ITS_ID_REQUIRED. Endpoints that allow both must handle the absence of X-Its-Id gracefully.


3. Standard Response Envelope

Every Core API response — from every engine, whether FastAPI or Node.js — uses the same envelope. No engine returns a Python traceback, a Node.js stack dump, or a framework error page.

Success response

{
"status": "success",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"data": { }
}
  • status — always "success" on a 2xx response
  • requestId — the X-Correlation-Id value echoed back by the engine. This is the same UUID the module sent as X-Request-Id. Modules can log or surface this so users can quote it to support for end-to-end tracing.
  • data — the engine-specific payload; structure defined per contract in 03 — API Contracts

Error response

{
"status": "error",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"error": {
"code": "RBAC_DENIED",
"message": "The specified itsId does not hold a role that grants this action.",
"details": { }
}
}
  • status — always "error" on a 4xx or 5xx response
  • error.code — machine-readable constant; modules switch on this, not on the HTTP status code
  • error.message — human-readable; for logging and support, not for display in module UI
  • error.details — optional; engine-specific additional context (e.g., which rule failed)

HTTP status codes

Core uses HTTP status codes as a coarse signal. The error.code is the precise signal.

HTTP StatusMeaning
200 OKRequest succeeded
201 CreatedResource created (write endpoints only)
400 Bad RequestRequest payload is malformed or fails validation
401 UnauthorizedMissing or invalid Authorization header or API key
403 ForbiddenCredentials valid but this module/user is not permitted to call this endpoint
404 Not FoundThe requested resource does not exist
409 ConflictThe request conflicts with existing state (e.g., duplicate allocation request)
422 Unprocessable EntityPayload is syntactically valid but semantically rejected (e.g., missing required field in business logic)
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorCore engine error — the module should retry with backoff
503 Service UnavailableEngine is temporarily unavailable

4. Standard Error Codes

Error codes are namespaced by engine. Modules switch on these constants.

Auth and gateway errors (all engines)

CodeHTTPMeaning
AUTH_INVALID_API_KEY401API key missing or not recognised
AUTH_API_KEY_REVOKED401API key has been revoked in Platform Admin
AUTH_ITS_TOKEN_INVALID401ITS bearer token failed validation
AUTH_ITS_TOKEN_EXPIRED401ITS bearer token has expired
AUTH_ITS_ID_REQUIRED403Endpoint requires user context; system-initiated call rejected
AUTH_MODULE_NOT_PERMITTED403This module's API key is not allowed to call this endpoint
RATE_LIMIT_EXCEEDED429Module has exceeded its rate limit window

RBAC Engine

CodeHTTPMeaning
RBAC_DENIED200*Permission check completed — result is denied
RBAC_ROLE_NOT_FOUND404The specified role does not exist
RBAC_INVALID_SCOPE400miqaatId scope is missing or unrecognised

* RBAC_DENIED is returned as a 200 OK with data.allowed: false — it is a valid business answer, not an error.

Eligibility Engine

CodeHTTPMeaning
ELIGIBILITY_INELIGIBLE200*Eligibility check completed — result is ineligible
ELIGIBILITY_RULE_NOT_FOUND404The specified rule set does not exist for this miqaat
ELIGIBILITY_DATA_UNAVAILABLE503A required data source (ITS sync, HR Bank) is temporarily unavailable

* Same pattern — ineligible is a valid answer.

Config Cascade Engine

CodeHTTPMeaning
CONFIG_NOT_FOUND404Key not set at any level of the cascade for this context
CONFIG_INVALID_SCOPE400miqaatId or context level is unrecognised

Allocation Engine

CodeHTTPMeaning
ALLOCATION_FULL409No seats/slots remaining
ALLOCATION_DUPLICATE409This itsId already has an allocation for this miqaat + category
ALLOCATION_RULE_REJECTED422Rule Engine rejected the allocation request
ALLOCATION_MIQAAT_CLOSED409Allocation phase for this miqaat is not open

Miqaat Lifecycle Engine

CodeHTTPMeaning
MIQAAT_NOT_FOUND404miqaatId does not exist
MIQAAT_INVALID_TRANSITION422The requested phase transition is not valid from the current phase

Platform errors (all engines)

CodeHTTPMeaning
INTERNAL_ERROR500Unexpected engine error; retry with exponential backoff
SERVICE_UNAVAILABLE503Engine temporarily unavailable; retry after Retry-After header value

5. Pagination

List endpoints that return multiple items use cursor-based pagination. Offset pagination is not used — it produces inconsistent results under concurrent writes.

Request parameters

ParameterTypeDefaultDescription
limitinteger20Number of items to return. Maximum 100.
cursorstringOpaque cursor from the previous response. Omit on the first page.

Response structure (list endpoints)

{
"status": "success",
"requestId": "uuid",
"data": {
"items": [ ],
"pagination": {
"cursor": "eyJpZCI6IjEyMyJ9",
"hasMore": true,
"total": null
}
}
}
  • cursor — pass this as the cursor query parameter to fetch the next page
  • hasMoretrue if there are more items beyond this page
  • totalnull by default; some endpoints provide a count where it is cheap to compute

6. Rate Limiting

The API Gateway enforces per-module rate limits. Limits are configured per module in Platform Admin — modules with predictable low-volume traffic have lower ceilings than modules with high-frequency use (e.g., Jamaat Scan calling RBAC on every scan event).

Rate limit headers (returned on every response)

HeaderValue
X-RateLimit-LimitRequests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets

When the limit is exceeded the gateway returns 429 Too Many Requests with error code RATE_LIMIT_EXCEEDED and a Retry-After header (seconds until the window resets).

Modules must implement exponential backoff on 429 responses. Retrying immediately is a protocol violation.


7. Idempotency

Mutation endpoints (allocations, role assignments, notifications) support idempotency keys to prevent duplicate operations caused by network retries.

How to use

Pass X-Idempotency-Key: <uuid> on any POST or PUT request to a mutation endpoint.

If Core has already processed a request with this key, it returns the original response — it does not re-execute the operation. Keys expire after 24 hours.

POST /v1/allocation/request
Authorization: Bearer <ITS-token>
X-Module-Id: vms
X-Request-Id: 550e8400-e29b-41d4-a716-446655440000
X-Idempotency-Key: 7b1f3c2a-4d5e-4f6a-8b9c-0d1e2f3a4b5c
Content-Type: application/json

If the response is 200 OK with the same requestId, the operation was a replay — not a new execution.

Endpoints that support idempotency are marked in 03 — API Contracts.


8. Health Endpoints

Every Core service exposes two health endpoints in the same format. Platform Admin, deployment tooling, and load balancers depend on these.

GET /health

Returns whether the service process is running. Used by load balancers and uptime monitors.

{
"status": "ok",
"service": "rbac-engine",
"version": "1.2.3"
}

Returns 200 OK if the process is alive. Returns 503 Service Unavailable if the process is degraded or shutting down.

GET /ready

Returns whether the service is ready to handle requests — data store connected, dependencies reachable. Used by deployment tooling before routing traffic to a new instance.

{
"status": "ready",
"service": "rbac-engine",
"checks": {
"database": "ok",
"config-cascade": "ok"
}
}

Returns 200 OK only when all checks pass. Returns 503 Service Unavailable if any dependency is unavailable. Deployment tools must wait for ready before routing traffic.


9. Read vs Write Endpoint Split

Confirmed in TD2 — module API keys are read-only. Write endpoints (those that mutate Core state) are restricted to Platform Admin and designated Core processes.

What this means in practice:

  • Module teams only need to read the read-endpoint contracts in 03 — API Contracts.
  • Write contracts exist but are not published for module teams — they are internal Platform Admin interfaces.
  • The Gateway enforces this: a module API key calling a write endpoint receives AUTH_MODULE_NOT_PERMITTED (403) before the request reaches the engine.

Write endpoints (examples — full list in 03 API Contracts):

  • POST /v1/miqaat — create a miqaat
  • POST /v1/miqaat/{id}/transition — advance miqaat phase
  • POST /v1/rbac/assign — assign a role
  • PUT /v1/config/set — set a config override
  • PUT /v1/rules/configure — update a rule definition

10. Content Type

All requests and responses use application/json. No other content type is supported on the Core API.

Content-Type: application/json
Accept: application/json

11. Null vs Absent Fields

Core follows a consistent rule:

  • Absent field — the engine has no data for this field and the concept does not apply in this context
  • null field — the engine knows the field exists but the value is not set / not available yet

Module code must handle both. Do not treat an absent field and a null field as equivalent — they carry different semantic meaning.


12. Summary — What Every Engine Implements

This is the minimum contract every Core engine fulfils. Individual engines may add to this — they never subtract from it.

RequirementDetail
Base URLhttps://core.miqaat.platform/v1/{path}
VersioningSemantic versioning; MAJOR on breaking changes; old version stays live during migration
Required request headersAuthorization, X-Module-Id, X-Request-Id
Clean headers (set by Gateway)X-Module-Id, X-Its-Id (user calls), X-Correlation-Id
Success envelope{ status, requestId, data }
Error envelope{ status, requestId, error: { code, message, details } }
No raw tracebacks in errorsPlatform-level requirement; enforced at engine level
Health endpointsGET /health · GET /ready on every service
Rate limit headersX-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
Retry-After on 429Seconds until rate limit window resets
Idempotency (mutation endpoints)X-Idempotency-Key header; 24-hour key expiry
Pagination (list endpoints)Cursor-based; limit + cursor params; pagination in response
Read/write splitModule keys rejected on write endpoints at the Gateway
Content typeapplication/json only