Skip to main content

API Conventions

All public REST endpoints served at https://api.septemcore.com/v1/ follow a single, consistent contract. This page is the normative reference. Deviating from it in a module or platform service is a build-blocking lint error.


Base URL

https://api.septemcore.com/v1/{resource}

All requests require a bearer token unless the endpoint is documented as unauthenticated:

Authorization: Bearer <access_token>
Content-Type: application/json

1. CRUD Verb Dictionary

The platform uses exactly five CRUD verbs and their fixed HTTP method mappings. Synonyms (get, fetch, find, remove, put, add) are prohibited at every layer — URL, service method, SDK export.

OperationService methodHTTP methodURL pattern
CreatecreateEntity()POST/v1/{entities}
Retrieve oneretrieveEntity()GET/v1/{entities}/:id
ListlistEntities()GET/v1/{entities}
Update (partial)updateEntity()PATCH/v1/{entities}/:id
DeletedeleteEntity()DELETE/v1/{entities}/:id
caution

PUT is prohibited. All updates are partial (PATCH). PUT implies full resource replacement, which is unsafe in a multi-tenant environment where clients may not have read access to all fields.


2. Action Endpoints

Not every operation maps to a CRUD verb. Operations with side effects that are not resource creation use the action endpoint pattern:

POST /v1/{entities}/:id/{action}
TypePatternExamples
Action on resourcePOST /v1/{entities}/:id/{action}/wallets/:id/credit, /files/:id/restore, /notifications/:id/retry
Standalone actionPOST /v1/{domain}/{action}/auth/login, /auth/logout, /auth/refresh

Rules:

  • Always POST — actions have side effects.
  • Return 200 OK for synchronous results, 202 Accepted for async operations (the caller should poll or listen on Event Bus).
  • Never use GET for actions — GET must be free of side effects.
  • Never mix CRUD and action on the same URL.

OIDC/OAuth 2.1 exceptions (names defined by the protocol standard):

GET /.well-known/openid-configuration
GET /.well-known/jwks.json
POST /v1/oauth/token
GET /v1/oauth/authorize
POST /v1/oauth/revoke
POST /v1/oauth/introspect
GET /v1/oauth/userinfo

3. Response Envelopes

Single resource

A single-resource response is a bare object — no wrapper:

// GET https://api.septemcore.com/v1/users/usr_01j9p3kx2e00000000000000
// 200 OK

{
"id": "01j9p3kx2e00000000000000",
"email": "[email protected]",
"name": "Alice",
"tenant_id": "01j9p3kz5f00000000000000",
"created_at": "2026-04-15T09:12:00Z",
"deleted_at": null
}

List resource

A list response uses a data array and a meta pagination object:

// GET https://api.septemcore.com/v1/users?limit=20
// 200 OK

{
"data": [
{ "id": "01j9p3kx2e00000000000000", "email": "[email protected]" },
{ "id": "01j9p3m1aa00000000000000", "email": "[email protected]" }
],
"meta": {
"cursor": "eyJpZCI6IjAxajlwM20xYWEwMDAwMDAwMDAwMDAwMCJ9",
"has_more": true
}
}

Rules:

  • data and errors never appear in the same response body.
  • null fields are included in responses, not omitted.
  • All field names use snake_case.

4. Field Naming

ConventionFormatExample
All JSON fieldssnake_casetenant_id, created_at, is_active
All dates / timestampsISO 8601 UTC"2026-04-15T09:12:00Z"
All resource IDsUUID v7 string"01j9p3kx2e00000000000000"
Soft-delete markerdeleted_at (nullable)null or "2026-05-01T00:00:00Z"
TTL valuesSeconds (integer)3600
important

UUID v7 is time-sortable (k-sorted). IDs are lexicographically ordered by creation time, enabling index-friendly cursor pagination without a separate created_at sort column.

Dates without a timezone designator (2026-04-15T09:12:00) are rejected at the OpenAPI validation layer with 400 Bad Request.


5. Cursor Pagination

All list endpoints use cursor-based pagination. Offset / page-based pagination is prohibited (it is unstable under concurrent writes).

Request

GET /v1/users?limit=20&cursor=eyJpZCI6Ii4uLiJ9
ParameterTypeRequiredDefaultMax
limitintegerNo20100
cursorstringNo

Response meta

{
"meta": {
"cursor": "eyJpZCI6IjAxajlwM20xYWEwMDAwMDAwMDAwMDAwMCJ9",
"has_more": true
}
}

The cursor value is an opaque base64 string. Clients must treat it as a black box — parsing its content or constructing it manually is unsupported and will break across versions.

totalCount, totalPages, and offset are prohibited in list APIs. The only exception is GET /v1/{entities}/count — a dedicated admin endpoint that returns { "count": N } for dashboard widgets.


6. Idempotency

Mutating POST requests on financial and critical paths require an Idempotency-Key header:

POST https://api.septemcore.com/v1/wallets/01j9.../credit
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{ "amount": 5000, "currency": "USD", "description": "Top-up" }

Endpoints requiring Idempotency-Key

ServiceEndpoints
Moneycredit, debit, transfer, hold, confirm, cancel, reversal
Authregister, invite
Notifysend, batch
Filesupload
Module RegistryPOST /modules

Behaviour

SituationResponse
First call — succeedsExecute, cache response 24 h, return 200/201.
Repeat call — same keyReturn cached original response + Idempotency-Replayed: true header.
First call — fails (4xx/5xx)Not cached. Retry with same key re-executes the operation.
Missing key (mutating POST)400 Bad RequestIdempotency-Key header is required.
Key is not UUID v4400 Bad RequestIdempotency-Key must be UUID v4.

Financial idempotency keys are persisted in PostgreSQL (source of truth) plus a Valkey fast-path cache. Non-financial keys are Valkey-only. Both have a 24-hour TTL.


7. API Versioning

All endpoints are versioned by URL path:

/v1/{resource} — current stable
/v2/{resource} — next major (when breaking changes are necessary)

Deprecation lifecycle

StageAction
Deprecation noticeResponses include Deprecation: true and Sunset: 2027-04-15T00:00:00Z headers. Endpoint documentation is marked deprecated.
Grace periodMinimum 6 months. Both versions are live simultaneously.
SunsetAfter the sunset date, the old version returns 410 Gone (RFC 9457 type: ".../problems/api-version-sunset").

Modules that are still calling a deprecated version appear in the Admin → Usage → Deprecated API report. The Event Bus emits platform.api.deprecated with targetVersion and sunsetDate.


8. Rate Limiting

The Gateway enforces a dual-layer strategy:

Request TypeDefault QuotaTarget
Authenticated (JWT)1 000 RPS / tenanttenantId
Unauthenticated20 RPS / IPSource IP
Sensitive Action5 / minIP + email

Quota Overrides

To customize limits in the Gateway environment, override the default config:

# 1. Authenticated Requests (JWT present)
GATEWAY_RATE_LIMIT_PER_TENANT=1000

# 2. Unauthenticated Requests (No JWT)
GATEWAY_RATE_LIMIT_PER_IP=20

# 3. Sensitive Action Endpoints (/auth/login, /auth/register)
GATEWAY_RATE_LIMIT_AUTH_PER_MIN=5

When the quota is exceeded, the Gateway returns 429 Too Many Requests with an RFC 9457 error body and the standard IETF rate-limit headers:

HTTP/1.1 429 Too Many Requests
RateLimit-Policy: "default";q=1000;w=1
RateLimit: "default";r=0;t=12
Retry-After: 12
Content-Type: application/problem+json

{
"type": "https://api.septemcore.com/problems/rate-limit-exceeded",
"title": "Too many requests.",
"status": 429,
"detail": "Tenant quota of 1000 RPS exceeded.",
"instance": "/v1/wallets/01j9.../credit",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
caution

X-RateLimit-* headers are prohibited — they are a non-standard convention. The platform uses only the IETF-standardised RateLimit-Policy and RateLimit headers.


9. Health Endpoints

Each service exposes three health endpoints outside the versioned namespace. These are system endpoints, not API endpoints:

EndpointK8s probePurpose
GET /health/livelivenessProbeProcess is up. No dependency checks.
GET /health/readyreadinessProbeAll critical dependencies (PostgreSQL, Valkey, Kafka) are reachable. Returns 503 if any fail.
GET /healthMonitoringFull dependency status with latency.
// GET https://api.septemcore.com/health
{
"status": "ok",
"version": "1.0.0",
"uptime": 86400,
"dependencies": [
{ "name": "postgres", "status": "ok", "latency_ms": 2 },
{ "name": "valkey", "status": "ok", "latency_ms": 1 },
{ "name": "kafka", "status": "ok", "latency_ms": 5 }
]
}

/healthcheck, /ping, /status, and /api/v1/health are prohibited path patterns for health endpoints.


10. OpenAPI Validation

Every route is validated at two levels before reaching any business logic:

  1. Envoy Gateway — validates requests against the OpenAPI 3.1.0 schema (path, query params, request body schema). Invalid requests are rejected at the edge with 400 Bad Request.
  2. Go Gateway Service — applies business-level cross-field validation and permission checks.

Response bodies are also validated by Envoy. Fields not declared in the response schema never appear in the API response — no accidental data leakage.

All nine OpenAPI specs live under services/{name}/api/openapi.yaml and use RFC 9457 for error schemas. The traceId field is present on every error response for correlation with distributed traces.


Quick Reference

ConventionValue
Base URLhttps://api.septemcore.com/v1/
Auth headerAuthorization: Bearer <token>
Field namingsnake_case
DatesISO 8601 UTC (2026-04-15T09:12:00Z)
IDsUUID v7 string
List paginationCursor-based (cursor + has_more)
IdempotencyIdempotency-Key: <UUID v4> header
Update methodPATCH only (PUT prohibited)
Rate limit headersRateLimit-Policy, RateLimit, Retry-After
Error formatRFC 9457 application/problem+json
API versioningURL path (/v1/, /v2/)