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.
| Operation | Service method | HTTP method | URL pattern |
|---|---|---|---|
| Create | createEntity() | POST | /v1/{entities} |
| Retrieve one | retrieveEntity() | GET | /v1/{entities}/:id |
| List | listEntities() | GET | /v1/{entities} |
| Update (partial) | updateEntity() | PATCH | /v1/{entities}/:id |
| Delete | deleteEntity() | DELETE | /v1/{entities}/:id |
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}
| Type | Pattern | Examples |
|---|---|---|
| Action on resource | POST /v1/{entities}/:id/{action} | /wallets/:id/credit, /files/:id/restore, /notifications/:id/retry |
| Standalone action | POST /v1/{domain}/{action} | /auth/login, /auth/logout, /auth/refresh |
Rules:
- Always
POST— actions have side effects. - Return
200 OKfor synchronous results,202 Acceptedfor async operations (the caller should poll or listen on Event Bus). - Never use
GETfor actions —GETmust 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",
"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": [
],
"meta": {
"cursor": "eyJpZCI6IjAxajlwM20xYWEwMDAwMDAwMDAwMDAwMCJ9",
"has_more": true
}
}
Rules:
dataanderrorsnever appear in the same response body.nullfields are included in responses, not omitted.- All field names use
snake_case.
4. Field Naming
| Convention | Format | Example |
|---|---|---|
| All JSON fields | snake_case | tenant_id, created_at, is_active |
| All dates / timestamps | ISO 8601 UTC | "2026-04-15T09:12:00Z" |
| All resource IDs | UUID v7 string | "01j9p3kx2e00000000000000" |
| Soft-delete marker | deleted_at (nullable) | null or "2026-05-01T00:00:00Z" |
| TTL values | Seconds (integer) | 3600 |
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
| Parameter | Type | Required | Default | Max |
|---|---|---|---|---|
limit | integer | No | 20 | 100 |
cursor | string | No | — | — |
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
| Service | Endpoints |
|---|---|
| Money | credit, debit, transfer, hold, confirm, cancel, reversal |
| Auth | register, invite |
| Notify | send, batch |
| Files | upload |
| Module Registry | POST /modules |
Behaviour
| Situation | Response |
|---|---|
| First call — succeeds | Execute, cache response 24 h, return 200/201. |
| Repeat call — same key | Return 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 Request — Idempotency-Key header is required. |
| Key is not UUID v4 | 400 Bad Request — Idempotency-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
| Stage | Action |
|---|---|
| Deprecation notice | Responses include Deprecation: true and Sunset: 2027-04-15T00:00:00Z headers. Endpoint documentation is marked deprecated. |
| Grace period | Minimum 6 months. Both versions are live simultaneously. |
| Sunset | After 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 Type | Default Quota | Target |
|---|---|---|
| Authenticated (JWT) | 1 000 RPS / tenant | tenantId |
| Unauthenticated | 20 RPS / IP | Source IP |
| Sensitive Action | 5 / min | IP + 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"
}
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:
| Endpoint | K8s probe | Purpose |
|---|---|---|
GET /health/live | livenessProbe | Process is up. No dependency checks. |
GET /health/ready | readinessProbe | All critical dependencies (PostgreSQL, Valkey, Kafka) are reachable. Returns 503 if any fail. |
GET /health | Monitoring | Full 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:
- 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. - 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
| Convention | Value |
|---|---|
| Base URL | https://api.septemcore.com/v1/ |
| Auth header | Authorization: Bearer <token> |
| Field naming | snake_case |
| Dates | ISO 8601 UTC (2026-04-15T09:12:00Z) |
| IDs | UUID v7 string |
| List pagination | Cursor-based (cursor + has_more) |
| Idempotency | Idempotency-Key: <UUID v4> header |
| Update method | PATCH only (PUT prohibited) |
| Rate limit headers | RateLimit-Policy, RateLimit, Retry-After |
| Error format | RFC 9457 application/problem+json |
| API versioning | URL path (/v1/, /v2/) |