Internal handoff documentation for the teams building against this deployment: the Flutter mobile app, the Sysmedac One portal, and the fintech ports. Contract only — no credentials, no tenant data.
You consume the product app API under /api/v1.
You call GroundIT inbound and hand users over by SSO.
/api/v1.POST /api/sso/exchange — also outside the version
prefix, because you sign and link that exact path.dhanavega / PayDay+ (EWA) and interpay (KSA money movement).
Every /api/v1/* operation is authenticated by an opaque session cookie.
No token, refresh token, or Keycloak material is ever returned to a client: the cookie
is the credential, and it is HttpOnly.
POST /api/v1/auth/login with
{ email, password, market, workspace? }. GroundIT owns no password; it presents the
credential to the market's Keycloak realm and, on success, mints a product session and sets the
cookie. A market with no configured client secret is sealed and always answers 401.POST /api/sso/exchange verifies the
platform's Ed25519 bridge token and mints the same product session (see §5).GET /api/v1/auth/session returns the principal and its
resolved grants. It requires a session but no permission token, so any signed-in principal can
ask.POST /api/v1/auth/logout revokes the session and clears
the cookie (always, even for an unknown handle).Authorization is re-resolved server-side on every request. The session carries
identity only; permissions come from the tenant's RBAC state, so a client must never cache or infer
an allow decision. Each operation's operationId is its permission token
(<module>.<resource>.<action>, ADR 0015) — that string is the single
join key between this API, the RBAC catalogue, and the traceability corpus.
Tenancy is never a parameter. tenant_id never appears in a path, query, or body on the
product surface: it comes from the session and is enforced in Postgres by row-level security.
Every error response is application/problem+json and carries a stable machine-readable
code alongside the HTTP status. Branch on code, never on
detail (human text, may change) or on the status alone. Every response — success or
error — also carries X-Correlation-Id; log it, and quote it when raising an issue.
{
"type": "about:blank",
"title": "Forbidden",
"status": 403,
"code": "TOKEN_DENIED",
"detail": "human-readable, non-contractual",
"instance": "/api/v1/employees/…",
"correlation_id": "…"
}
The codes below are read from the running build's catalogue, so this list cannot drift from what this deployment actually emits:
| Code |
|---|
TOKEN_DENIED |
SCOPE_DENIED |
OWNERSHIP_DENIED |
MAKER_EQUALS_CHECKER |
STEP_UP_REQUIRED |
CONSENT_REQUIRED |
PLAN_LIMIT_EXCEEDED |
FEATURE_NOT_IN_PLAN |
TENANT_SUSPENDED |
NOT_FOUND |
IDEMPOTENCY_KEY_REUSE |
STATE_TRANSITION_INVALID |
FACE_MISMATCH |
FACE_VERIFICATION_REQUIRED |
TENANT_CANCELLED |
VERSION_CONFLICT |
VALIDATION_FAILED |
WITHHOLDING_REVIEW_REQUIRED |
TENANT_PAST_DUE |
PRECONDITION_REQUIRED |
RATE_LIMITED |
The entitlement family is ordered subscription-status → feature-flag → numeric-limit and
surfaces as 423 TENANT_PAST_DUE (writes blocked, reads still fine) ·
402 FEATURE_NOT_IN_PLAN · 402 PLAN_LIMIT_EXCEEDED ·
403 TENANT_SUSPENDED · 410 TENANT_CANCELLED. A row hidden by row-level
security answers 404 NOT_FOUND, deliberately indistinguishable from an absent row.
page[size] with
page[after] / page[before]; read the next cursor out of the response
envelope. Offsets are not supported — do not synthesise them.Idempotency-Key (a UUID you
generate per logical attempt) on POST/PATCH/DELETE and reuse it verbatim on retry. A replay
returns the original result with Idempotency-Replayed: true; the same key with a
different body is rejected 409 IDEMPOTENCY_KEY_REUSE. This is what makes
the mobile offline queue safe.null (which is not the same as omitting
the field), and value types (1 is not "1").ETag; send it back as If-Match. A missing header is
428 PRECONDITION_REQUIRED, a stale one 412 VERSION_CONFLICT.Accept-Language (en ·
ar). Arabic responses are RTL and may carry Hijri (Umm al-Qura) date companions;
money is always a decimal string plus an ISO-4217 currency, never a float./platform/*) and the SSO exchangeThese two channels are the Sysmedac One seam. Both sit outside the
/api/v1 prefix, because the caller signs (or links to) the exact wire path.
/platform/* callEvery call except GET /platform/health carries three signals; any failure is a single
opaque 401 that tells you nothing about which one failed.
Authorization: Bearer <service-token plaintext>
X-Platform-Signature: <hex lowercase HMAC-SHA256> (X-SaasPortal-Signature also accepted)
X-Platform-Timestamp: <epoch MILLISECONDS> (X-SaasPortal-Timestamp also accepted)
Idempotency-Key: <uuid> (required on every mutation; X-Idempotency-Key also accepted)
signature = HMAC-SHA256(
key = <the same service-token plaintext>,
message = timestamp + "\n" + METHOD + "\n" + path + "\n" + sha256hex(body)
)
path is the full request target including the query string — sign
what you put on the wire.sha256(""), not as the empty string.timestamp is epoch milliseconds and must fall inside the replay
window (default ±300 s).TenantCache and is therefore not entitlement
gated — it never answers 402/403/410/423.POST /api/sso/exchange takes the platform's Ed25519-signed bridge token and mints a
GroundIT product session (the same cookie as §2). GroundIT is a verifier only — it
holds the platform's public key and never a portal credential. aud is always enforced,
iss when configured, and the channel is sealed (every exchange 401) until the
public key is provisioned. Full request/response shapes are in
Platform inbound + SSO.
/api/v1One document per module grouping. Audience: Flutter mobile and Sysmedac One portal / web back-office.
| Document | Ops | Version | Download |
|---|---|---|---|
| Org & Admin Config plane (legal entities, org tree, locations, shifts, leave/holiday config, pay structures, statutory hub, templates) and governance plane (RBAC catalogue, audit lens, reports, tenant config). | 142 | 0.1.0 | JSON · YAML |
| People Employee directory, profile 360, personal info, contacts, dependents, bank accounts, and the PII-reveal path. | 91 | 0.1.0 | JSON · YAML |
| Recruit Requisitions, candidates, interviews, offers, and pre-boarding hand-off. | 98 | 0.1.0 | JSON · YAML |
| Attendance & Leave Punches (offline sync), attendance records, regularizations, overtime, geofence events, leave applications/balances/encashment, comp-offs. | 143 | 0.1.0 | JSON · YAML |
| Work Projects, task boards, tasks, assignments, work entries, and timesheets. | 162 | 0.1.0 | JSON · YAML |
| Pay & Tax Payroll runs and their lifecycle, payslips, ESOP grants/exercises/valuations, full & final settlement, tax declarations, proofs, and regime choice. | 137 | 0.1.0 | JSON · YAML |
| Payroll Simple monthly payroll: tenant settings, named pay heads, employee pay, calendar months, payslips, bank advice, payments and advances. | 28 | 0.2.0 | JSON · YAML |
| Expense & Benefits Travel and expense claims, advances, and the benefits catalogue/enrolment. | 54 | 0.1.0 | JSON · YAML |
| Perform & Learn Goals, reviews, feedback, and the learning catalogue/enrolments. | 92 | 0.1.0 | JSON · YAML |
| Documents, Helpdesk & Assets Document vault and policies, helpdesk tickets, and asset assignment/return. | 89 | 0.1.0 | JSON · YAML |
| Engage & Exit Engagement moments, surveys, exit workflow, alumni, rehire, and referrals. | 86 | 0.1.0 | JSON · YAML |
| Comply Statutory filings and registers for India (EPFO/ESIC/TRACES/PT) and KSA (GOSI, WPS-Mudad, Nitaqat). | 35 | 0.1.0 | JSON · YAML |
| Fintech Earned-wage access, salary advances, employee loans, wallet/ledger, and the PayrollPort event mirror (ADR 0014/0018). | 53 | 0.1.0 | JSON · YAML |
| Client Billing Tenant client register, effective-dated billing rates, invoices, payments, and receivables aging for the Finance console. | 27 | 0.1.0 | JSON · YAML |
| Sales Pre-delivery revenue: accounts, enquiries, proposals, contracts, and licensing (ADR 0042; module 15). | 27 | 0.1.0 | JSON · YAML |
| Cross-cutting (xc) Search, notifications, device tokens, files, home widgets, approval inbox, delegations, sessions, virtual IDs, privacy requests, and the audit read lens. | 60 | 0.1.0 | JSON · YAML |
| Document | Ops | Version | Download |
|---|---|---|---|
| Platform inbound + SSO Everything Sysmedac One calls on GroundIT: tenant lifecycle, workspace members, the RBAC mirror, impersonation, usage — plus POST /api/sso/exchange. Service-bearer + HMAC signed, outside the /api/v1 prefix. | 31 | 0.2.0 | JSON · YAML |
| Document | Ops | Version | Download |
|---|---|---|---|
| PayrollPort (dhanavega EWA) The employer-side EWA adapter contract GroundIT exposes to dhanavega / PayDay+ (ADR 0014) — five capabilities plus the inbound webhook. | 5 | 0.1.0 | JSON · YAML |
| interpay (KSA money movement) Placeholder contract for SAR payouts and SAMA compliance (ADR 0013) — no sandbox access yet, specced ahead of integration. | 5 | 0.1.0 | JSON · YAML |
| Document | Ops | Version | Download |
|---|---|---|---|
| Public legal content Unauthenticated mobile retrieval of the published privacy policy and the existing bundled Terms and About copy; routes are outside /api/v1. | 3 | 0.1.0 | JSON · YAML |
These documents are the spec of record from
planning/api-docs/openapi/, served straight off this deployment's build — not a
snapshot pasted into a wiki. The repository's working contract makes them release-blocking: every
change to an endpoint, DTO, error code, header, or auth requirement updates the owning OpenAPI
document in the same change, and an automated check fails the build when a route the API actually
serves is absent from a served document.
The spec deliberately runs ahead of the implementation — it is the full designed surface, and endpoints keep landing sprint by sprint. If an operation you need answers 404, it is specced but not yet built; ask before designing around it.