Authentication & Conventions

Everything shared by every endpoint: the base URL, versioning, how you authenticate, the error model, and the request conventions the whole API follows.

Base URL & versioning

Every route is served under a global api prefix and a URI version segmentv1. The one exception is the /health readiness probe, which is version-neutral and sits outside the prefix.

EnvironmentBase URL
Production (current)https://gulfpay-production.up.railway.app/api/v1
Production (after DNS cutover)https://api.grivaone.com/api/v1

So a controller route such as sales-invoices → GET / resolves to https://gulfpay-production.up.railway.app/api/v1/sales-invoices.

Authentication

The API is protected by a global JWT guard: unless a route is explicitly marked public, it requires a valid bearer access token. Obtain one from POST /auth/login, then send it on every request:

HeaderValue
AuthorizationBearer <accessToken>

Access tokens are short-lived. When one expires, call POST /auth/refresh (the refresh token travels in an httpOnly cookie or the request body) to obtain a fresh access token — no need to re-enter credentials. Live sessions can be listed and revoked under /auth/sessions, and accounts may be further protected with TOTP two-factor (/auth/2fa/*) or an SMS login code (/auth/phone-otp/*).

Public routes

Only a small surface is open without a token — login and account recovery, the payment gateway config & provider webhook, the public coverage catalog, streamed logo/image assets, the migrator’s token-scoped import endpoints, and the health probe. Everything else returns 401 without a valid bearer token. Public routes are labelled Public throughout this catalog.

Errors

Errors use standard HTTP status codes with a JSON body. The common ones:

StatusMeaningTypical cause
400Bad RequestValidation failed (a field is missing, malformed or out of range).
401UnauthorizedMissing, expired or invalid bearer token.
403ForbiddenAuthenticated, but the role or company scope does not permit the action.
404Not FoundThe resource does not exist, or is outside your company scope.
409ConflictA uniqueness clash, or an Idempotency-Key replay whose payload differs from the original request.
429Too Many RequestsPer-IP / per-account rate limit tripped (e.g. repeated login attempts).

Conventions

Pagination

List endpoints accept page and limitquery parameters and return the newest records first. Additional per-module filters (date ranges, status, search) are shown in each endpoint’s notes.

Company scope — X-Company-Id

An organization can hold several companies (legal entities). Business endpoints are company-scoped: send the active company on the X-Company-Idheader so reads and writes are isolated to that tenant and company. Omitting it falls back to the caller’s default company where one applies.

Idempotency-Key

Write endpoints accept an Idempotency-Key header. Replaying a request with the same key returns the original result instead of creating a duplicate; replaying the key with a different payload is rejected with 409. This is what makes the offline write-sync queue safe to replay on reconnect.

Document numbering

Document and master codes are sequential per company, so numbering never leaks volumes across tenants.

Reading this catalog

Each portal page groups its modules into collapsible sections. Use the filter box on a portal page to narrow within it, or the Endpoint Explorer to search across every portal at once.