Skip to content

HTTP API ​

Scripts and websites (dashboards, Discord bots, shops) can read and change the server through a JSON API served by FXServer on the game port:

text
http://<server ip>:<port>/<resource>/v1/<route>

Turn it on ​

The API is off by default. Turn it on in /nay, on the Nayretis page: HTTP API (ApiEnabled). While it is off, every route answers 503 api_disabled. Maximum request size (KB) (ApiMaxBodyKb) bounds request bodies (1024 KB by default).

Tokens ​

Create tokens in the API tab of /nay (the token is shown once: copy it then) or from the server console:

text
nay_api_token create <label> <scope...>
nay_api_token list
nay_api_token revoke <id>

A token looks like nay_<id>_<secret>. Only its SHA-256 hash is stored. Each token has its scopes, optional allowed addresses (IPv4, IPv6 or CIDR ranges), a request limit per minute and an optional expiry.

Scopes are <resource>.<scope>, with wildcards: nay_car_wash.* gives every route of a script, * gives everything. Each script lists its routes and scopes on its own page, for example Car Wash.

Requests ​

  • Send Authorization: Bearer <token>.
  • Bodies are JSON (Content-Type: application/json) with a Content-Length. Chunked bodies are refused.
  • After 10 failed authentications within a minute, requests without a valid token from that address are refused for 5 minutes. Valid tokens keep working.
  • Behind a reverse proxy, set nay:apiTrustedProxies (see HTTPS). Otherwise every caller shares the proxy address.

Every answer is JSON with an X-Request-Id header:

json
{ "ok": true, "data": { } }
{ "ok": false, "error": { "code": "forbidden", "message": "This token does not allow this route", "details": { } } }
StatusCodeMeaning
400unexpected_body, invalid_json, invalid_requestBody sent to a route without body, body not valid JSON, or malformed Content-Length.
401unauthorizedMissing, unknown, revoked or expired token.
403forbidden, ip_not_allowedThe token lacks the scope, or the address is not allowed for it.
404 / 405not_found, method_not_allowedUnknown route, or wrong method on a known route.
411length_requiredAny Transfer-Encoding (chunked body).
413payload_too_largeBody over ApiMaxBodyKb. An announced Content-Length over 10 MB is refused before the body is read.
415unsupported_media_typeBody that is not application/json.
422invalid_request, unknown_parameter, invalid_valueParameter, query or body failing validation (details.path and details.reason).
429rate_limited, too_many_failuresToken over its limit per minute, or address blocked. See Retry-After.
500internal_errorThe route failed. See the server console.
503api_disabled, not_readyAPI off, or resource still starting (Retry-After).
504timeoutThe route did not answer within 10 seconds. It keeps running: a change may still be applied after the 504, so read the state back before retrying.

nay_lib routes ​

MethodRouteScopeData
GET/nay_lib/v1/statusnay.statusServer name, players, max players, framework, inventory, target, Nayretis resources and versions.
GET/nay_lib/v1/playersnay.playersConnected players: id, name, character, identifier, job.
GET/nay_lib/v1/settings/:resourcenay.settings plus <resource>.config or nay.configSettings of a script with their values.
PUT/nay_lib/v1/settings/:resource/:keysameChanges a setting. Body { "value": ... }.
DELETE/nay_lib/v1/settings/:resource/:keysameResets a setting to its default.
GET/nay_lib/v1/auditnay.auditAudit log, 50 rows per page. Query resource, action, actor, from and to (Unix seconds), page.

Every call that changes something is written to the audit log (api.call, actor api:<token label>).

HTTPS ​

FXServer only speaks plain HTTP: anyone on the way can read a token sent over it. Put a reverse proxy with a certificate in front of the API and only expose the routes you use. Then tell nay_lib which proxies to trust, so allowed addresses and rate limits apply to the real client address taken from X-Forwarded-For:

cfg
set nay:apiTrustedProxies "127.0.0.1"

The value is a comma separated list of addresses or CIDR ranges.

nginx:

nginx
server {
	listen 443 ssl;
	server_name api.example.com;
	ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

	location ~ ^/(nay_lib|nay_car_wash)/v1/ {
		proxy_pass http://127.0.0.1:30120;
		proxy_set_header X-Forwarded-For $remote_addr;
		client_max_body_size 1m;
	}
}

Caddy (certificate obtained automatically):

caddy
api.example.com {
	@nayretis path_regexp ^/(nay_lib|nay_car_wash)/v1/
	reverse_proxy @nayretis 127.0.0.1:30120
}