Skip to content

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.

Add a restApi block inside contentServers.web in config.hjson:

contentServers: {
web: {
restApi: {
enabled: true
}
}
}

The API supports two authentication schemes. Both are accepted on the same Authorization header or dedicated header.

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/login
Content-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/refresh

Log out (revokes the refresh cookie):

POST /_enig/api/v1/auth/logout

API 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:

Terminal window
# 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 3

Pass the key on requests via:

X-Enigma-API-Key: <rawKey>

Valid scope values are read, write, and read,write.

MethodPathAuthDescription
POST/auth/loginNoneExchange credentials for JWT access token + refresh cookie
POST/auth/refreshRefresh cookieRotate access token
POST/auth/logoutRefresh cookieRevoke refresh token
MethodPathAuthDescription
GET/system/infoPublic¹Board name, version, node count
GET/system/nodesRequired¹Active node list
GET/system/last-callersPublic¹Recent login history
GET/system/statsPublic¹Total user count
MethodPathAuthDescription
GET/messages/conferencesACSList accessible conferences
GET/messages/conferences/:confTagACSConference detail + area list
GET/messages/areas/:areaTagACSArea detail
GET/messages/areas/:areaTag/messagesACSCursor-paginated message list
POST/messages/areas/:areaTag/messagesACS writePost a message
GET/messages/:uuidACSFull message body + FTN metadata
DELETE/messages/:uuidAuthDelete own message (or any, if sysop)

ActivityPub internal areas and private mail are always blocked regardless of auth or ACS.

MethodPathAuthDescription
GET/files/areasACSList accessible file areas
GET/files/areas/:areaTagACSArea detail
GET/files/areas/:areaTag/filesACSCursor-paginated file list
POST/files/areas/:areaTagACS writeUpload a file (multipart/form-data)
GET/files/:fileIdACSFile metadata
GET/files/:fileId/downloadACSStream file download
MethodPathAuthDescription
GET/users/meRequiredOwn profile
PUT/users/meRequiredUpdate own profile
GET/users/:usernameRequiredPublic profile (extended view for sysops)

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 userspublicAccess 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).

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=false
GET /_enig/api/v1/files/areas/local_flat/files?stripAnsi=false

Fields affected: body, subject, desc, descLong, and conference/area desc.

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).

Errors follow RFC 7807 Problem Details:

{
"type": "/_enig/api/v1/errors/404",
"title": "Not Found",
"status": 404,
"detail": "Area 'unknown_tag' not found"
}

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.

All keys live under contentServers.web.restApi in config.hjson:

KeyRequiredDescription
enabledYesSet to true to enable the REST API.
corsAllowedOriginsNoArray of allowed CORS origins. Default [] (no CORS headers). Use ["*"] for open access.
jwtSecretNoOverride the auto-generated JWT signing secret. Useful for multi-node setups sharing a secret.
system.public.infoNoMake GET /system/info public. Default true.
system.public.nodesNoMake GET /system/nodes public. Default false.
system.public.last-callersNoMake GET /system/last-callers public. Default true.
system.public.statsNoMake GET /system/stats public. Default true.
messages.publicAccessNoMap of conference tags to { include, exclude } glob arrays for anonymous read access.
files.publicAccessNoMap of area tags to { include, exclude } glob arrays for anonymous read access.

¹ Default visibility; override via system.public.* config keys.