API reference
A REST surface under /api/v1 and a GraphQL one beside it, both calling the same services under the same permissions, plus the sockets and streams.
The two surfaces, and which is which#
Keydra answers on two surfaces, and they are not two halves of one thing.
- GraphQL at
/graphql -
135 operations. This is what the Keydra interface uses. Nearly every page — the connection catalog, the key browser’s tree and its mutations, monitoring, migrations, schedules, alerts, backups, tunnels, access control, the audit log, sessions — asks its questions here.
- REST under
/api/v1 -
161 endpoints. The complete surface, and the one to write a script or another tool against. Versioned in the path, so
/api/v1is a promise in a way an internal query is not.
Both call the same services and return the same shapes: a GraphQL type is the DTO the REST endpoint already returns, which is what keeps the two from drifting into disagreeing about what a field is called. Every operation on both carries the same permission check — a resolver that reached a repository directly would be a second door into the same house.
Why the interface moved#
A page that needs three unrelated things made three requests, and a page showing six columns
of a long list downloaded all of every row. /api/v1/migrations answers 31 KB and
/api/v1/schedules/runs 22 KB; a table showing six columns of each reads perhaps a tenth
of it.
Pages moved one at a time, and whole — half a page on each surface is two caches to reason about. A REST endpoint stays until nothing calls it, which is why the list below is longer than what the interface uses.
What the interface still asks over REST#
Not leftovers, in most cases, but the things GraphQL is the wrong shape for:
- Files
-
Exporting keys, and importing them. A file is not a field.
- Values
-
Reading and writing the contents of a key, and the list of encodings.
- Signing in
-
The form post, sign-out, first-run setup, the provider redirect, and the invitation and password-reset pages. These run before there is a session to ask a question with.
/api/v1/about-
One small record, read once.
- The sockets
-
The notification hub, the console and the command watch. Each carries a kind of event to whoever is listening; a subscription per page would be a second socket saying the same thing.
Authentication#
Both surfaces are authenticated by the session cookie the interface already holds. There is no separate API token mechanism: a script runs as somebody, and that somebody signs in.
GraphQL refuses GET. A query in a URL is a query in a proxy log, a browser history and a
referrer header, and Keydra’s queries name connection ids and key patterns.
Introspection and the schema document are on in development and off in production. Neither is a vulnerability by itself — an authenticated caller reaches what they may reach either way — but a schema is a map of every field and argument, and a production deployment has nobody who needs the map.
OpenAPI#
A running Keydra publishes its own OpenAPI document:
curl -s http://localhost:8181/api/openapi > keydra-openapi.yamlIn the development profile, /q/swagger-ui browses it interactively.
That document is the authoritative schema: request and response shapes, status codes and validation rules come from the code that serves them. The tables below name the endpoints and what each requires; the document names the fields.
The GraphQL schema is served at /graphql/schema.graphql in development, where
introspection is also on. Both are off in production.
WebSockets and streams#
| Path | Description |
|---|---|
|
The target’s own MONITOR stream, with secrets redacted before they leave the engine. Off until asked for, because it costs the server work while it is on. |
|
A command console session against one target. Typed results are streamed back as they arrive. |
|
The notification hub. One socket for the whole interface, carrying every server-side change as an envelope of a category, a payload and a timestamp. A broadcast about one target reaches only the sockets whose owner may see it. |
The notification envelope#
Everything on the hub is the same shape:
{
"category": "ConnectionStatusChanged",
"payload": { },
"ts": "2026-01-01T00:00:00Z"
}A broadcast about one target reaches only the sockets whose owner may see that target.
| Category | Description |
|---|---|
|
A connection profile was added. |
|
A connection profile changed. Its pooled clients and its tunnel are closed, because it may now point somewhere else. |
|
A connection profile was removed. |
|
A target went up, went down, or reported a different flavor or version. |
|
Keys on a target were created, renamed, expired or deleted. |
|
The contents of a key changed. |
|
A message arrived on a subscribed channel. |
|
A subscription opened, closed, or dropped because its connection did. |
|
A fresh reading of a watched target. |
|
Sampling started or stopped for a target. |
|
How far a migration between two targets has got. Broadcast rather than answered on the request that started it, so a page reloaded halfway through does not lose sight of a job that is still running. |
|
A scheduled job did not do what it was arranged to do. Only the failures — a toast per successful run every five minutes would bury the one that matters. |
|
A scheduled job finished, whatever it did. For a table drawing a "last run" column, which has to change on a success as much as on a failure. |
|
An alert rule moved between quiet, pending and firing. Only the transitions, so a rule firing for six hours sends one message. |
|
A session was ended, here or elsewhere. The browser holding it stops working on its next request. |
|
A sign-in that succeeded looked unlike the ones before it. |
|
How far a purge has got. A glob delete walks the keyspace a batch at a time, so it reports as it goes rather than answering once at the end — a page that showed nothing for four minutes would be indistinguishable from one that had stopped. |
|
Somebody asked for an operation on a target that waits for a second person. It carries the request’s id and the target, and nothing else: the audience is everybody who can see that target, which is wider than the set of people who could answer it. |
|
A request was approved, declined, withdrawn, expired, or finished running. Every ending, including the ones nobody pressed, because the person waiting for an answer is the one who most needs to see it arrive. |
Server-Sent Events#
Key enumeration streams as Server-Sent Events rather than being buffered into a response, so
the interface renders the first keys while a SCAN is still running. The GraphQL
subscription beside it sends only the fields the caller named — a browser showing names and
types is sent names and types, not the TTL and size of every key in a million-key database.
REST endpoints#
Grouped by the tag each resource carries. Required names the permission the endpoint checks; where a coarse role is the only gate, the role is named instead.
This is the whole surface, including the endpoints the interface no longer calls. They stay until nothing calls them — deleting one the day its last caller moved is how a client nobody remembered gets broken.
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Being signed in |
Get application name, version and build metadata |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every configured delivery, and how many rules use it |
|
|
|
Add somewhere to send alerts |
|
|
|
Remove a delivery |
|
|
|
Change a delivery |
|
|
|
Send a test message |
|
|
|
Which destinations hear about Keydra itself |
|
|
|
Choose which destinations hear about Keydra itself |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every rule, with where it currently stands |
|
|
|
Watch for a condition |
|
|
|
Remove a rule, and the record of what it said |
|
|
|
Change a rule |
|
|
|
What the rules have said |
|
|
|
The metrics a rule can watch |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Where this target’s memory went |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Operations waiting for a second person |
|
|
|
Withdraw a request you made |
|
|
|
One request, with what it would do |
|
|
|
Agree to an operation, which starts it |
|
|
|
Say no, and say why |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Being signed in |
End this session |
|
|
Being signed in |
What the caller may do, per target |
|
|
Being signed in |
Create the first administrator |
|
|
Being signed in |
Whether there is anything to sign into, and whether anybody has |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every configured destination |
|
|
|
Add a destination |
|
|
|
Remove a destination |
|
|
|
Change a destination |
|
|
|
Find out whether it works |
|
|
|
Try a destination that has not been saved |
|
|
|
Generate a key pair for encrypting backups |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Being signed in |
List all connection profiles with their last known status |
|
|
|
Create a connection profile |
|
|
|
Delete a connection profile |
|
|
|
Get one connection profile |
|
|
|
Update a connection profile |
|
|
|
Probe a saved profile and record the result |
|
|
|
Probe a profile that has not been saved |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Commands the console refuses to run on this target |
|
|
|
Forget your own command history on this target |
|
|
|
Command lines you have previously run against this target |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Being signed in |
The databases this target holds |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Which key the stored secrets are under |
|
|
|
Re-encrypt everything with the key that writes now |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every grant, with names beside the ids |
|
|
|
Grant a role on a scope |
|
|
|
Take a grant back |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every group, with what is directly inside it |
|
|
|
Create a group |
|
|
|
Remove a group |
|
|
|
Put a person, or another group, into this group |
|
|
|
Take something out of a group |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every configured provider |
|
|
|
Add a provider |
|
|
|
Remove a provider |
|
|
|
Change a provider |
|
|
|
Map a claim value to a Keydra group |
|
|
|
Remove a mapping |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Who is running and what they depend on |
|
|
|
Put an instance back into service |
|
|
|
Take an instance out of service |
|
|
|
Ask everything Keydra reaches whether it is there, now |
|
|
|
When things Keydra reaches started and stopped answering |
|
|
|
Who is running, and what each of them is holding |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Open to anybody |
Whether a link can still be used |
|
|
Open to anybody |
Set a password with a link |
|
|
|
Send somebody a link to set their own password |
|
|
Open to anybody |
Ask for a link because a password has been forgotten |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Stream keys matching a filter |
|
|
|
Copy a key to a new name |
|
|
|
Delete one or more keys |
|
|
|
Set or clear a key’s TTL |
|
|
|
Export keys as the store’s own serialisation |
|
|
|
Restore exported keys |
|
|
|
Migrations started from this target |
|
|
|
Move keys to another target |
|
|
|
Stop a migration |
|
|
|
Delete everything a pattern matches |
|
|
|
Rename a key |
|
|
|
List the namespace level below a prefix |
|
|
|
Give a lease back |
|
|
|
Whether this target’s changes are being heard |
|
|
|
Take or renew a lease on this target’s changes |
|
|
|
Ask the target to announce its changes |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Being signed in |
Every migration this instance knows about |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Stop sampling this target |
|
|
|
Sampling state and the readings collected so far |
|
|
|
Start sampling this target |
|
|
|
The largest keys in a sample of the keyspace |
|
|
|
Clients currently attached to the server |
|
|
|
Disconnect a client |
|
|
|
Readings over a window |
|
|
|
Raw server statistics, grouped by section |
|
|
|
One reading of this target’s vital signs, taken now |
|
|
|
Clear the slow log |
|
|
|
Commands the server recorded as slow, newest first |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Being signed in |
Everything you prefer |
|
|
Being signed in |
Set one preference |
|
|
Being signed in |
Forget one preference |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Publish a message |
|
|
|
Stop listening on this target |
|
|
|
What this target is currently subscribed to |
|
|
|
Listen on a target’s channels |
|
|
|
Every subscription this server currently holds open |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every role and what it carries |
|
|
|
Create a custom role |
|
|
|
Remove a custom role |
|
|
|
Change a custom role |
|
|
|
Every permission there is |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every schedule, with when it last ran and when it runs next |
|
|
|
Arrange work to happen on its own |
|
|
|
Remove a schedule, and the record of what it did |
|
|
|
Change a schedule |
|
|
|
Run it now |
|
|
|
The kinds of work that can be scheduled |
|
|
|
What the schedules have done |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Being signed in |
Turn off your second factor |
|
|
Being signed in |
Whether you have a second factor, and how many recovery codes are left |
|
|
Being signed in |
Begin pairing an authenticator |
|
|
Being signed in |
Prove the pairing with one code |
|
|
Being signed in |
Replace your recovery codes |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Users the target knows about |
|
|
|
Create or replace a user |
|
|
|
Remove a user from the target |
|
|
|
Permission categories the target defines |
|
|
|
What has been done, newest first |
|
|
|
The action names recorded so far |
|
|
Open to anybody |
Who Keydra thinks is asking, and what they may do |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every server group, with the targets in it |
|
|
|
Create a server group |
|
|
|
Remove a server group |
|
|
|
Take a target out of this group |
|
|
|
Put a target into this group |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
How this target is keeping its data |
|
|
|
Rewrite the append-only log, compacting it |
|
|
|
Write a snapshot in the background |
|
|
|
Everything this target is configured to do |
|
|
|
Change one setting while the server runs |
|
|
|
Write the running configuration back to the server’s own file |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Being signed in |
End every session except this one |
|
|
Being signed in |
The browsers you are signed in on |
|
|
Being signed in |
End one of your sessions |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
What is asked of whoever signs in |
|
|
|
Require a second factor, or stop requiring one |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
Being signed in |
The ways in this instance offers |
|
|
Being signed in |
Where the provider sends people back to |
|
|
Being signed in |
Begin signing in through a provider |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
How this target is arranged and what it supports |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Every configured tunnel |
|
|
|
Add a tunnel |
|
|
|
Remove a tunnel |
|
|
|
Change a tunnel |
|
|
|
Find out whether it works |
|
|
|
Try a jump host that has not been saved |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Everybody, with the groups they are directly in |
|
|
|
Create a local account |
|
|
|
Remove an account |
|
|
|
Change an account |
| Method | Path | Required | What it does |
|---|---|---|---|
|
|
|
Read a page of a key’s value |
|
|
|
Change a value |
|
|
|
List the decoders a client may request |
GraphQL operations#
What the Keydra interface actually asks. Mutations are here too: the surface is not read-only.
| Operation | Required | What it does |
|---|---|---|
|
|
Every account, with the roles it holds |
|
|
The command categories a rule can name, as this server spells them |
|
|
The accounts the server itself knows about, and what each may run |
|
|
The channels a firing rule can send word through |
|
|
What has fired and what has cleared, newest first |
|
|
The metrics a rule can watch, each with its unit |
|
|
The rules, with what each one is reading now and which state it is in |
|
|
Commands a target can be allowed to run, each with what allowing it means |
|
|
Every kind of action that has been recorded, for a filter to offer |
|
|
A page of the audit log, newest first |
|
Being signed in |
What this instance expects of whoever is asking |
|
|
One backup’s header, read without downloading the whole file |
|
|
Every destination, without the credentials they hold |
|
|
What is already in a destination, newest first |
|
|
The biggest keys found by sampling the keyspace |
|
|
What a target can do, which decides which tools are offered for it |
|
|
Who is connected to the target right now |
|
|
One target, by id |
|
|
Every target the caller can see, with its last known status |
|
|
What you have run against this target, newest first, with values redacted |
|
|
The numbered databases on a target, with how many keys each holds |
|
|
The commands this target refuses to run, whoever is asking |
|
Being signed in |
What the caller may do, over Keydra itself and over each target |
|
|
Which key the stored secrets are under, and how many are not |
|
|
How many flagged sign-ins there are in the window |
|
|
Sign-ins that worked but did not look like the ones before them |
|
|
One reading from every target the caller can see |
|
|
Who holds which role, over what |
|
|
Every group, with who is in it |
|
|
Every provider, with the redirect URI each one has to be told about |
|
|
What the keyspace holds, worked out by sampling it |
|
|
Whether a target announces its changes, and whether anybody is listening |
|
Being signed in |
Who is asking, the roles they hold, and whether access is being enforced |
|
|
A page of migrations, newest first, filtered to what the caller can see |
|
|
Whether a target is being sampled, and since when |
|
|
Readings between two moments, evenly divided into points |
|
|
One reading, taken now |
|
Being signed in |
How many browsers there are to page through |
|
Being signed in |
Every browser that can act as you right now, the one reading this first |
|
Being signed in |
How many sign-ins there are to page through |
|
Being signed in |
Your own recent sign-ins, newest first |
|
|
One level of the keyspace, grouped by a delimiter |
|
|
Every permission a role can carry, with the level it applies at |
|
|
Whether the server is writing to disk, and when it last did |
|
|
Sign-ins that were refused, newest first |
|
|
Every role, built in or defined here, with the permissions it carries |
|
|
The kinds of work that can be scheduled, with the permission each one needs |
|
|
What became of the arranged work, newest first |
|
|
Every schedule, with when it last ran and when it runs next |
|
|
Every server group, with which targets are in it |
|
|
The server’s own configuration, as it reports it |
|
Being signed in |
The ways of signing in this instance offers, other than a password here |
|
|
What this instance asks of whoever signs in |
|
|
The commands the server itself recorded as slow |
|
|
What this target is being listened to for, or nothing |
|
|
The shape of the target: standalone, replicated, sentinel or clustered |
|
|
Every jump host, without the keys they hold |
| Operation | Required | What it does |
|---|---|---|
|
|
Puts an account, or another group, inside a group |
|
|
Says which local group a claim value puts somebody in |
|
|
Puts a target in a server group |
|
|
Asks a target to announce its changes, keeping whatever its setting already said |
|
|
Stops a migration; what has moved stays moved |
|
|
Changes one setting on the running server |
|
|
Sends a test message through a channel and reports what happened |
|
|
Reaches the destination and reports what happened |
|
|
Opens a connection to the target and reports what came back |
|
|
Opens the tunnel and reports what happened |
|
|
Empties your own command history on this target |
|
|
Empties the server’s own slow log |
|
|
Copies a key, here or to another target |
|
|
Adds an account; the password is set by whoever accepts the invitation |
|
|
Adds a channel for firing rules to send word through |
|
|
Adds a rule watching one metric on one target |
|
|
Adds a destination for backups to be written to |
|
|
Adds a target |
|
|
Adds a group |
|
|
Adds a provider people can sign in through |
|
|
Defines a role |
|
|
Arranges a job to run on a cron expression |
|
|
Adds a server group, so a grant can name several targets at once |
|
|
Adds a jump host |
|
|
Removes an account and every grant it held |
|
|
Removes one of the server’s own accounts |
|
|
Removes a channel |
|
|
Removes a rule |
|
|
Removes a destination |
|
|
Removes a target and everything Keydra kept about it |
|
|
Removes a group and every grant it held |
|
|
Removes a provider; accounts it created stay, and can no longer sign in with it |
|
|
Removes the named keys |
|
|
Removes a role and every grant of it |
|
|
Removes a schedule |
|
|
Removes a server group and every grant scoped to it |
|
|
Removes a jump host |
|
Being signed in |
Ends every session but this one, and answers how many |
|
Being signed in |
Ends one of your sessions; it stops on that browser’s next request |
|
|
Sets or clears a key’s time to live |
|
|
Makes a key pair; the private half is shown once and never stored |
|
|
Gives a role to an account or a group, over an instance, group or target |
|
|
Takes or renews a lease on a target’s changes |
|
|
Writes keys into a target from what was exported |
|
|
Makes a link that lets an account set its own password |
|
|
Closes one client’s connection to the target |
|
|
Writes the running configuration to the server’s own file |
|
|
Sends a message to a channel and answers how many subscribers took it |
|
|
Removes every key matching a glob, a batch at a time |
|
|
Rewrites every stored secret under the current key |
|
|
Gives a lease back, closing the watch when it was the last |
|
|
Takes a member out of a group |
|
|
Removes a mapping; it stops applying at the next sign-in through that provider |
|
|
Takes a target out of a server group |
|
|
Renames a key |
|
|
Require a second factor of every local account, or stop requiring one |
|
|
Writes a backup’s keys into a target |
|
|
Takes a grant back; it stops applying on the holder’s next request |
|
|
Asks the server to rewrite its append-only file |
|
|
Runs a scheduled job now, without waiting for its cron |
|
|
Creates or replaces one of the server’s own accounts |
|
|
Starts moving keys from one target to another; answers before any have moved |
|
|
Starts sampling a target on a clock |
|
|
Stops sampling a target |
|
|
Starts listening to channels or patterns on a target |
|
|
Writes a backup of a target into a destination |
|
|
Asks the server to write a snapshot in the background |
|
|
Stops listening; answers whether anything was listening |
|
|
Changes an account |
|
|
Changes a channel |
|
|
Changes a rule |
|
|
Changes a destination |
|
|
Changes a target |
|
|
Changes a provider |
|
|
Changes what a role carries |
|
|
Changes a schedule |
|
|
Changes a jump host |
| Operation | Required | What it does |
|---|---|---|
|
|
Walks a target’s keyspace with SCAN, one key at a time. KEYS is never used. |
The schema sets a limit for every documented way of turning one request into much work: how deep a query may nest, how many fields one may name in total — which is the alias attack that depth alone does not catch — and what the parser accepts before it stops reading. Each is set rather than left at a default meant for a public API with large documents.
An unexpected failure answers one generic sentence. An exception message from inside the application is a description of the application, and the log is where that belongs.