REST API
ENiGMA½ includes a built-in REST API served under /_enig/api/v1/. It exposes message bases, file areas, user profiles, and system information over JSON, enabling third-party clients, bots, and integrations to interact with your BBS programmatically.
The API requires the Web Server to be enabled.
Enabling the API
Section titled “Enabling the API”Add a restApi block inside contentServers.web in config.hjson:
contentServers: { web: { restApi: { enabled: true } }}Authentication
Section titled “Authentication”The API supports two authentication schemes. Both are accepted on the same Authorization header or dedicated header.
JWT Bearer Tokens (interactive sessions)
Section titled “JWT Bearer Tokens (interactive sessions)”Obtain a short-lived access token by posting credentials to /auth/login. The response includes a Bearer token valid for 15 minutes and an HttpOnly refresh cookie valid for 30 days.
POST /_enig/api/v1/auth/loginContent-Type: application/json
{ "username": "sysop", "password": "hunter2" }Pass the access token on subsequent requests:
Authorization: Bearer <accessToken>Refresh silently before the access token expires:
POST /_enig/api/v1/auth/refreshLog out (revokes the refresh cookie):
POST /_enig/api/v1/auth/logoutAPI Keys (automated / programmatic access)
Section titled “API Keys (automated / programmatic access)”API keys are long-lived tokens suitable for bots, scripts, and integrations. Generate them with oputil:
# Generate a read-only key for user "sysop"./oputil.js rest api-key generate sysop --label "Discord bot" --scope read
# Generate a read+write key./oputil.js rest api-key generate sysop --label "Upload bot" --scope read,write
# List all keys (optionally filter by user)./oputil.js rest api-key list./oputil.js rest api-key list sysop
# Revoke a key by its numeric ID./oputil.js rest api-key revoke 3Pass the key on requests via:
X-Enigma-API-Key: <rawKey>Valid scope values are read, write, and read,write.
Endpoints
Section titled “Endpoints”| Method | Path | Auth | Description |
|---|---|---|---|
POST | /auth/login | None | Exchange credentials for JWT access token + refresh cookie |
POST | /auth/refresh | Refresh cookie | Rotate access token |
POST | /auth/logout | Refresh cookie | Revoke refresh token |
System
Section titled “System”| Method | Path | Auth | Description |
|---|---|---|---|
GET | /system/info | Public¹ | Board name, version, node count |
GET | /system/nodes | Required¹ | Active node list |
GET | /system/last-callers | Public¹ | Recent login history |
GET | /system/stats | Public¹ | Total user count |
Messages
Section titled “Messages”| Method | Path | Auth | Description |
|---|---|---|---|
GET | /messages/conferences | ACS | List accessible conferences |
GET | /messages/conferences/:confTag | ACS | Conference detail + area list |
GET | /messages/areas/:areaTag | ACS | Area detail |
GET | /messages/areas/:areaTag/messages | ACS | Cursor-paginated message list |
POST | /messages/areas/:areaTag/messages | ACS write | Post a message |
GET | /messages/:uuid | ACS | Full message body + FTN metadata |
DELETE | /messages/:uuid | Auth | Delete own message (or any, if sysop) |
ActivityPub internal areas and private mail are always blocked regardless of auth or ACS.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /files/areas | ACS | List accessible file areas |
GET | /files/areas/:areaTag | ACS | Area detail |
GET | /files/areas/:areaTag/files | ACS | Cursor-paginated file list |
POST | /files/areas/:areaTag | ACS write | Upload a file (multipart/form-data) |
GET | /files/:fileId | ACS | File metadata |
GET | /files/:fileId/download | ACS | Stream file download |
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /users/me | Required | Own profile |
PUT | /users/me | Required | Update own profile |
GET | /users/:username | Required | Public profile (extended view for sysops) |
Public Access
Section titled “Public Access”By default all endpoints (except the explicitly public system endpoints) require authentication. You can expose message conferences and file areas to unauthenticated callers via the publicAccess configuration. ACS still applies for authenticated users — publicAccess only grants anonymous read access to specific areas.
restApi: { enabled: true
messages: { publicAccess: { // Expose all areas in the "local" conference except "private*" tags local: { include: ["*"] exclude: ["private*"] } } }
files: { publicAccess: { // Expose the "local_flat" file area publicly local_flat: { include: ["*"] } } }}Cross-Origin Resource Sharing headers are off by default. To allow browser-based clients:
restApi: { enabled: true corsAllowedOrigins: ["https://my-bbs-frontend.example.com"]}Set to ["*"] to allow any origin (suitable only for fully public read endpoints).
stripAnsi Query Parameter
Section titled “stripAnsi Query Parameter”Text fields such as message bodies, file descriptions, and area descriptions may contain ANSI escape sequences. By default the API strips these before returning them. Pass ?stripAnsi=false to receive raw ANSI-bearing content:
GET /_enig/api/v1/messages/areas/local_general/messages?stripAnsi=falseGET /_enig/api/v1/files/areas/local_flat/files?stripAnsi=falseFields affected: body, subject, desc, descLong, and conference/area desc.
Pagination
Section titled “Pagination”List endpoints return a standard pagination envelope:
{ "data": [ ... ], "pagination": { "next": "<opaque cursor string or null>" }}Pass the cursor as ?cursor=<value> on the next request. Control page size with ?limit=N (max 100, default 25).
Error Responses
Section titled “Error Responses”Errors follow RFC 7807 Problem Details:
{ "type": "/_enig/api/v1/errors/404", "title": "Not Found", "status": 404, "detail": "Area 'unknown_tag' not found"}OpenAPI Specification
Section titled “OpenAPI Specification”A full API reference is generated from the OpenAPI 3.1 specification, covering every endpoint, its parameters, schemas and example responses.
The specification itself lives at website/src/api/openapi.yaml if you would rather generate a client from it. It is checked against the routes the server actually registers on every build, so it cannot silently fall behind the implementation.
Configuration Reference
Section titled “Configuration Reference”All keys live under contentServers.web.restApi in config.hjson:
| Key | Required | Description |
|---|---|---|
enabled | Yes | Set to true to enable the REST API. |
corsAllowedOrigins | No | Array of allowed CORS origins. Default [] (no CORS headers). Use ["*"] for open access. |
jwtSecret | No | Override the auto-generated JWT signing secret. Useful for multi-node setups sharing a secret. |
system.public.info | No | Make GET /system/info public. Default true. |
system.public.nodes | No | Make GET /system/nodes public. Default false. |
system.public.last-callers | No | Make GET /system/last-callers public. Default true. |
system.public.stats | No | Make GET /system/stats public. Default true. |
messages.publicAccess | No | Map of conference tags to { include, exclude } glob arrays for anonymous read access. |
files.publicAccess | No | Map of area tags to { include, exclude } glob arrays for anonymous read access. |
¹ Default visibility; override via system.public.* config keys.