STYGION Documentation Search the docs

The local API and keys

Everything the mod can do, for programs, behind keys that only carry what you gave them.

Keystone 0.2.8 Updated 5 min read

This page is older than Keystone 0.2.8, released 18 October 2026. Something may have changed since.

On this page 6 parts
  1. Keys
  2. Scopes
  3. What is there
  4. The live stream
  5. Addons on the JVM
  6. What is written down

The local API and keys

Keystone answers HTTP on 127.0.0.1:25586. The panel is one caller; your own tooling can be another.

Code
[api]
enabled = true
address = "127.0.0.1"
port = 25586
reachable-at = ""     # 0.1.1 and newer

Loopback is the default and it is the safe one. Anything else is reachable by whoever can reach that machine, and the API can do everything the mod can do. If you need it from another machine, put it behind an SSH tunnel rather than binding it to the world.

reachable-at does not decide where it listens — it decides which address the mod writes into the editor link and into openapi.json. In detail on the panel page.

Keys

#
Code
/keystone key new <name> <scope> [scope ...]
/keystone key list
/keystone key revoke <id>
/keystone key own <id>

A key is printed once, when it is made, and never again — the same way a person sees a password. Every key has a name, so the audit trail says who did what, and a rate limit per minute. /keystone key new hands out nothing its maker could not be handed.

Authorization: Bearer <key> on every call. No key answers 404 rather than 403: a route that says "forbidden" has told an unauthenticated caller that it exists. A real key that lacks the scope the route needs gets 403 and a sentence saying which scope is missing — that caller already knows the surface is there.

A key remembers whether it was issued to the owner. /keystone key list marks the owner's keys and says when each was made. A key from before owners were recorded (say, the one a shop uses to write ranks) is not the owner's until the owner says so with /keystone key own <id>; the log names each one at boot. A key somebody named like one of the mod's own is never widened into it. The API reads nobody before it knows them and at most a megabyte from anybody; MCP tool calls go through the same rate limit, owner check and audit record as every other request.

Scopes

#

A key carries the scopes it is given and nothing else. Asking for a scope this server has nothing behind is refused, and the refusal lists what does exist — so a typo is caught when the key is made rather than when it is used.

Scope Reaches
status.read Whether the server is up, who is on, how it is doing.
players.read The people — including which game accounts one Discord user has linked.
permissions.read · permissions.write Groups and what they may do.
operations.read · operations.run The timetable, and running one by hand.
settings.read · settings.write Every setting.
console.read The server's log.
chat.read What is being said.
events.read The live stream (below).
audit.read Who changed what.
keys.read Which keys exist — never their secrets.
backup.read · backup.restore Backups, and putting one back.
perks.read · perks.write What people have earned.
assistant.read · assistant.write The in-game assistant.
languages.write The translations players read.
server.read The machine underneath.

Some scopes are the owner's: settings.write, permissions.write, perks.write, backup.restore, operations.run, assistant.write and languages.write. The router checks them on every request — whatever wildcard a key holds, and over MCP too — and they pass only with a key issued to the console or a level-4 operator. That is why a panel link reached through a group is read and moderate only.

Give a key the narrowest set that does the job. A monitoring script wants status.read and nothing else.

What is there

#

GET /v1/openapi.json lists every path this server actually has — the mod writes that document out of its own routes, so it cannot be wrong about what exists. When you want the list, ask the server rather than this page.

Roughly: /v1/status, /v1/players (plus /v1/players/group, /v1/players/ungroup, /v1/players/perk, /v1/players/check, /v1/players/grants), /v1/groups, /v1/operations (/run, /cancel, /history), /v1/backups (/restore), /v1/settings (/history, /rollback), /v1/console, /v1/chat, /v1/events, /v1/audit, /v1/keys, /v1/bans, /v1/tickets, /v1/votes, /v1/skins, /v1/progress, /v1/metrics (/history), /v1/bench, /v1/catalog, /v1/entities, /v1/gate (/maintenance), /v1/accounts, /v1/discord (/links), /v1/assistant/* and /v1/reload.

  • POST /v1/settings takes check=true to ask whether a value would be accepted without writing it. A list is answered as a, b rather than [a, b], which could not be written back.
  • POST /v1/players/ungroup (permissions.write, fields player and group) removes only the group grants the calling key made — what an admin typed, what Discord granted and what another key gave stay — and answers {player, group, removed}; removed: 0 is not an error. /v1/players/group and /ungroup find a player who is not online but has joined this server before, because ranks are bought and run out while people are away. A name this server has never seen, or a group that does not exist, is a 400.
  • The tick time in /v1/metrics is what a tick costs now, from the server's own tick times — not 1000 ÷ TPS.

The live stream

#

GET /v1/events with events.read holds the connection open and writes server events down it as they happen — which is what makes the panel a panel rather than a page you refresh. It is authenticated like everything else, and answers 404 without a key.

Addons on the JVM

#

If you are building an addon in Java you do not talk over HTTP — you compile against keystone-api, which travels inside that same jar (META-INF/jarjar/) and has no dependencies of its own, not even a logging framework. It is how you register your own module, command, permission or source of ranks.

What is written down

#

Every change through the API is an audit line: who, what, when. Sensitive commands are masked out of it, the console and the log — Keystone's own automatically, and anything you name in keystone.mask-commands.

Did this page help?

Opens the feedback panel with this page attached. It lands in the same queue as everything else.