Authentication & Headers
Minimal does not have its own authentication system. It operates on a trusted subsystem model — your existing infrastructure authenticates the user and injects identity headers before the request reaches Minimal.
Minimal reads those headers and acts on them. It never sees the original token, session, or credential.
The Model
Client → [Your Gateway / App Server] → Minimal
│
└── validates token
resolves user identity
injects headers
forwards to Minimal
Your gateway or app server is the only trust boundary. Once a request passes through it, Minimal assumes it is legitimate.
All requests must go through your gateway or app server. A client that calls Minimal directly — bypassing your gateway — can forge any identity by setting arbitrary headers.
Header Reference
There are two sets of headers — System API headers used only during setup, and Auto API headers used on every data request.
Auto API Headers
Required on every GET, POST, PUT, and DELETE request:
| Header | Type | Required | Description |
|---|---|---|---|
X-Org-Id | String | ✅ Yes | Organization the request belongs to |
X-Project-Id | String | ✅ Yes | Project (database config) to use |
X-Space-Id | String | ✅ Yes | Workspace within the project |
X-User-Id | String | ✅ Yes | The authenticated user making the request |
X-User-Roles | String | ✅ Yes | Comma-separated roles — e.g. admin, users |
Content-Type | String | POST only | Must be application/json |
Missing any of the first five returns 400 Bad Request.
System API Headers
Required only for org and project creation — never sent from a client:
| Header | Type | Required | Description |
|---|---|---|---|
X-User-Id | String | ✅ Yes | The user performing the setup |
X-Server-Key | String | ✅ Yes | Server trust credential — from config.yml |
Content-Type | String | ✅ Yes | Must be application/json |
X-Server-Key must be injected by your backend service only — not your gateway, not your client.
It is a service-to-service credential. Treat it the same as a database password.
Who Sets Each Header
| Header | Set by | When |
|---|---|---|
X-User-Id | Gateway | Every request — resolved from token |
X-User-Roles | Gateway | Every request — resolved from token claims or roles DB |
X-Org-Id | Gateway or App server | Every request — may be in token or resolved per request |
X-Project-Id | Gateway or App server | Every request — may vary per request |
X-Space-Id | Gateway or App server | Every request — may vary per request |
X-Server-Key | Backend service only | System API only — org and project creation |
Integration Patterns
Pattern 1 — Gateway sets everything
Your API gateway validates the token and injects all five Auto API headers. The client only sends a Bearer token — it never sets Minimal headers directly.
Client (Bearer token)
→ Gateway (validates, resolves all 5 headers, injects)
→ Minimal
Best for centralised auth where org/project/space are fixed in the token claims.
// Kong / custom gateway plugin example
const claims = await verifyJWT(token);
request.headers['X-User-Id'] = claims.sub;
request.headers['X-User-Roles'] = claims.roles.join(', ');
request.headers['X-Org-Id'] = claims.org_id;
request.headers['X-Project-Id'] = claims.project_id;
request.headers['X-Space-Id'] = claims.space_id;
Pattern 2 — App server sets headers
Your gateway validates the token and blocks unauthenticated requests. Your backend app resolves org/project/space and injects the headers before calling Minimal.
Client (Bearer token)
→ Gateway (validates token, passes through)
→ App Server (resolves context, injects headers)
→ Minimal
Best when org/project/space are dynamic — chosen per request by the app, not fixed in the token.
// Node/Express backend example
app.use('/minimal', async (req, res, next) => {
const session = req.user; // already validated by gateway
req.headers['X-User-Id'] = session.userId;
req.headers['X-User-Roles'] = session.roles.join(', ');
req.headers['X-Org-Id'] = session.orgId;
req.headers['X-Project-Id'] = req.query.projectId; // dynamic per request
req.headers['X-Space-Id'] = req.query.spaceId;
next();
});
Pattern 3 — nginx auth_request
nginx forwards the token to an internal auth service via auth_request.
The auth service validates it and returns the identity headers.
nginx picks them up and injects them into the Minimal request.
Client (Bearer token)
→ nginx
→ auth_request → your auth service (returns headers)
→ nginx injects headers
→ Minimal
Best when you want zero application-layer code for header injection.
location /minimal/api/ {
auth_request /auth/validate;
auth_request_set $user_id $upstream_http_x_user_id;
auth_request_set $user_roles $upstream_http_x_user_roles;
auth_request_set $org_id $upstream_http_x_org_id;
auth_request_set $project_id $upstream_http_x_project_id;
auth_request_set $space_id $upstream_http_x_space_id;
proxy_set_header X-User-Id $user_id;
proxy_set_header X-User-Roles $user_roles;
proxy_set_header X-Org-Id $org_id;
proxy_set_header X-Project-Id $project_id;
proxy_set_header X-Space-Id $space_id;
proxy_pass http://127.0.0.1:3045/;
}
location = /auth/validate {
internal;
proxy_pass http://127.0.0.1:4000/validate;
proxy_pass_request_body off;
proxy_set_header Content-Length '';
proxy_set_header X-Original-URI $request_uri;
}
X-Server-Key — Backend service only
X-Server-Key is used only for System API calls (org and project creation).
It must always come from a backend service — never from a gateway or client:
Client
→ Gateway ← does NOT handle X-Server-Key
→ Backend App ← reads from process.env, injects X-Server-Key
→ Minimal System API
// Backend service — org creation example
const response = await fetch('https://your-domain.com/minimal/system/api/v1/org', {
method: 'POST',
headers: {
'X-User-Id': req.user.id,
'X-Server-Key': process.env.MINIMAL_SERVER_KEY, // never hardcoded
'Content-Type': 'application/json',
},
body: JSON.stringify(req.body),
});
ID Format
All IDs in Minimal use ULID format — Universally Unique Lexicographically Sortable Identifiers.
01KBY3K9NDC5XW523M2V1Z0373
ULIDs are:
- 26 characters, uppercase alphanumeric
- Time-sortable — the first 10 characters encode the timestamp
- URL-safe — no special characters
- Globally unique — safe to generate client-side or server-side
| Language | Library |
|---|---|
| JavaScript / Node | ulid — npm install ulid |
| Python | python-ulid — pip install python-ulid |
| Go | github.com/oklog/ulid |
| Java | com.github.f4b6a3:ulid-creator |
| Online | ulidgenerator.com |
Common Errors
| Code | Cause | Fix |
|---|---|---|
400 Bad Request | One or more Auto API headers missing | Ensure all five headers are injected on every request |
401 Unauthorized | X-User-Id is missing | Always pass X-User-Id |
403 Forbidden | X-Server-Key wrong or missing on System API | Check server_key value in config.yml |
403 Forbidden | Role not permitted for this operation | Check X-User-Roles value and auto_api.roles in config.yml |