Skip to main content

REST API Overview

The SeptemCore Platform exposes all services as REST HTTP/1.1 APIs through a single API Gateway (Envoy Gateway + Go Gateway Service). All requests flow through the Gateway — there are no direct service endpoints exposed to clients.


Base URL

https://api.septemcore.com/v1

All endpoints are versioned under /v1. A version bump to /v2 will be communicated at least 6 months in advance per the Versioning & Deprecation Policy.


Authentication

Every request (except public auth endpoints) requires a Bearer JWT in the Authorization header:

Authorization: Bearer <access_token>

Tokens are obtained via:

  • POST /api/v1/auth/login — email + password
  • POST /api/v1/auth/login/:providerId — OAuth / wallet provider
  • POST /api/v1/auth/refresh — rotate access token using refresh token

Token Lifetimes

TokenLifetimeNotes
Access token (JWT)15 minutesShort-lived, stateless
Refresh token7 daysRotation on each use
Refresh grace window10 secondsTwo tabs refreshing simultaneously: both get the same new pair within the window
Session token (tenant selection)5 minutesIssued during multi-tenant login, before select-tenant

JWT Payload Fields

ClaimTypeDescription
subUUIDUser ID
emailstringUser email
tenantIdUUIDActive tenant ID
rolesstring[]List of role names
iatnumberIssued-at (Unix timestamp)
expnumberExpires-at (Unix timestamp)
issstringOIDC issuer URL
audstringAudience (platform-kernel or OIDC client ID)

Request Headers

HeaderRequiredDescription
Authorization: Bearer <token>✅ All protected routesJWT access token
Content-Type: application/json✅ POST / PATCH bodyBody format
Idempotency-Key: <uuid>Required for financial operationsUUID v4/v7 — prevents double-execution on retry
Accept: application/jsonOptional (default)Response format

Error Format — RFC 9457

All errors return Content-Type: application/problem+json following RFC 9457 Problem Details:

{
"type": "https://api.septemcore.com/problems/insufficient-funds",
"title": "Insufficient Funds",
"status": 400,
"detail": "Available balance (12500 cents) is less than requested amount (50000 cents).",
"instance": "/api/v1/wallets/01j9pwal0000000000000001/debit",
"traceId": "4bf92f3477d54adb"
}

For validation errors, the response includes an errors array:

{
"type": "https://api.septemcore.com/problems/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "The request body contains invalid fields.",
"instance": "/api/v1/users",
"traceId": "1a2b3c4d5e6f",
"errors": [
{ "field": "email", "message": "Must be a valid email address" },
{ "field": "roles", "message": "At least one role is required" }
]
}

Standard HTTP Status Codes

StatusMeaningCommon cause
200 OKSuccessGET, PATCH
201 CreatedResource createdPOST
202 AcceptedAsync operation queuedBatch notify, large export
204 No ContentSuccess, no bodyDELETE
207 Multi-StatusPartial batch successBulk operations (RFC 9457 per item)
400 Bad RequestValidation or business rule errorInvalid amount, missing field
401 UnauthorizedMissing or expired tokenToken not sent or expired
403 ForbiddenInsufficient permissionsRole does not have required permission
404 Not FoundResource not found or module not installed for tenantUnknown ID
409 ConflictOptimistic lock / duplicateversion mismatch, duplicate idempotencyKey
413 Payload Too LargeBody or file exceeds limitFile > 10 MB for images
429 Too Many RequestsRate limit exceededSee rate limiting below
503 Service UnavailableCircuit breaker open or staging limitTry again after Retry-After
504 Gateway TimeoutUpstream service did not respond in 5 sTransient; retry with backoff

Rate Limiting

The Gateway applies a hybrid two-layer token-bucket algorithm:

LayerMechanismPurpose
Envoy (per-instance)Local token bucket — zero latencyAbsorb burst before hitting Valkey
Valkey (global)Distributed counter per-tenantFair enforcement across all Gateway instances

When the rate limit is exceeded:

HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/problem+json

{
"type": "https://api.septemcore.com/problems/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "Request rate exceeds limit of 1000 req/min for tenant 01j9pten...",
"instance": "/api/v1/contacts",
"retryAfter": 42
}
ParameterDefaultOverride
Time window1 minutePer Billing plan
Default limit1,000 requests / min per tenantAdmin role: 5,000 / min
Group keyIP / user / tenant / API keyConfigurable per route

Cursor-Based Pagination

All list endpoints use cursor-based pagination (not offset/page). Cursors are opaque base64-encoded strings — do not parse them.

Request

GET https://api.septemcore.com/v1/users?limit=20&cursor=eyJpZCI6IjAx...
Query parameterDefaultMaxDescription
limit20100Records per page
cursorOpaque cursor from previous response meta.cursor

Response

{
"data": [
{ "id": "01j9pusr0000000000000001", "email": "[email protected]" },
{ "id": "01j9pusr0000000000000002", "email": "[email protected]" }
],
"meta": {
"cursor": "eyJpZCI6IjAx...",
"hasMore": true,
"total": 847
}
}

When hasMore: false, cursor is null — no more pages exist.


OpenAPI Specifications

Nine OpenAPI 3.x specifications are available — one per service. The documentation URLs dynamically follow the service's API namespace:

  • Swagger UI: /api-docs/<namespace>
  • Redoc: /api-docs/<namespace>/redoc
  • OpenAPI JSON: /api/v1/<namespace>/openapi.json
ServiceNamespace
IAMiam
Data Layerdata
Moneymoney
Filesfiles
Notifynotify
Auditaudit
Eventsevents
Billingbilling
Gatewaygateway

Deprecated API versions: When a version is deprecated, the Gateway saves an immutable OpenAPI snapshot to S3 (api-specs/v{N}/openapi.yaml). Snapshots are served at GET /api-docs/v{N} and are retained indefinitely — clients still migrating can always access the old spec.


Gateway Behaviour

FeatureDetail
Circuit breaker5 consecutive failures in 30 s → circuit opens for 30 s → 503 Service Unavailable
gRPC deadline5 s per upstream call (GRPC_CALL_TIMEOUT_MS). Exceeded → 504 Gateway Timeout
Retry1 retry + 100 ms backoff for idempotent GET requests. POST/PATCH/DELETE — no retry
OpenAPI validationRequest body validated against schema on every call. Invalid → 400 Bad Request
Response validationResponse body validated in dev/staging environments. Violations → 500 Internal Server Error + alert
Module routingModule not installed for tenant → 404 Not Found (before downstream call, using Valkey module cache)
Tenant injectiontenantId from JWT is injected into every downstream request — modules cannot override it