Admin API Reference
Hammerhead has a few internal “admin APIs” that can be used to manage the server.
Authentication
The admin API is authenticated by supplying a bearer token like with any other Matrix request, with the additional requirement that the token points at an account that has the administrator flag. The first account created is automatically given admin at the time of registration, however you can manually give any subsequent account admin by either:
- Running
UPDATE accounts SET admin=true WHERE localpart='foo';via psql (localpartis the bit of the user ID between@and:). - With an existing admin account, call update user with
{"admin": true}
Pre-shared token authentication
Some endpoints, like create user, allow you to authenticate with the static “pre-shared token”
defined in the configuration file:
registration.admin_pre_shared_secret. The intent
is that you can create the first account without needing to enable registration, and to allow automation without needing
to delegate admin to yet another account (which increases your attack surface). The PSK may do more in the future.
Not all endpoints support this method of authentication. Those that do will state so explicitly.
Versioning
The admin API is locally versioned. Any time a breaking change is made to the functionality of a route, the version is
increased. This does mean, however, that there can be any number of API versions active at once, such as
/_hammerhead/v0/foo, /_hammerhead/v1/bar, and /_hammerhead/v9007199254740991/hello-world.
Consumers should always use the latest version, however removals will always be included in release notes, and historical versions will be kept around for as long as it is sensible to.
Important
Until v0.1.0 is released, this versioning is not respected.
Endpoints
Get Uptime
GET /_hammerhead/v0/uptime
Authentication: None.
Fetches the epoch from when the server started. “Started” refers to the unix timestamp at when the HTTP listeners were started, a.k.a. when the server became ready, not when the process itself started.
Response body:
{
"started_at": 1775928780089
}
| Key | Type | Description |
|---|---|---|
started_at | number | The time the server started, in unix milliseconds |
Get Version
GET /_hammerhead/v0/version
Authentication: None.
Returns full version metadata, including build date, commit hash, tagged version, OS architecture, etc. This is most useful for debugging and preparing issues, as it is very comprehensive.
Response body:
{
"build_date": 1775928757000,
"commit_hash": "c6a5ea0",
"dirty": true,
"full": "v0.0.1-dev+gc6a5ea0+dirty+d2026.04.11T17.32.37Z+go1.26.0@linux/amd64",
"go_version": "go1.26.0",
"latest_tag": "v0.0.0",
"os_arch": "linux/amd64",
"short": "v0.0.1-dev+gc6a5ea0",
"tagged_version": ""
}
| Key | Type | Description |
|---|---|---|
build_date | number | The unix timestamp (milliseconds) when the running binary was compiled |
commit_hash | string | The short commit hash the running binary was compiled with |
dirty | boolean | Whether the working tree was dirty when the running binary was compiled (uncommitted changes) |
full | string | The full version string. Used for issue reporting, as it combines all useful info into one string |
go_version | string | The Go compiler version the running binary was compiled with |
latest_tag | string | The latest git tag available at the time of compile |
os_arch | string | The architecture the binary was compiled for (OS/ARCH) |
short | string | The short version string. Used in the server’s User-Agents |
tagged_version | string | The tag the commit_hash points at, if available. Otherwise, an empty string |
Make PDU
POST /_hammerhead/v0/admin/make-pdu
Authentication: Admin account.
Creates a PDU with the given input. You currently can’t do anything with this, as the send-pdu counterpart has been
temporarily removed.
If the room_version is omitted or empty, it will be fetched from the database. If a room version cannot be found,
404 / M_NOT_FOUND is returned.
room_id, sender, and content are the only required keys in the pdu object. Anything else required will be
calculated on-demand. However, anything included in the base pdu will not be modified, meaning you can specify
(for example) custom auth_events, prev_events, depth, and origin_server_ts values.
The event’s calculated event ID will be included under unsigned.
Request body:
{
"dont_hash": true,
"dont_sign": true,
"room_version": "11",
"pdu": {
"content": {
},
"room_id": "!...",
"sender": "@..."
}
}
| Key | Type | Description |
|---|---|---|
dont_hash | boolean (default: false) | If true, hashing and signing will not be performed on the PDU |
dont_sign | boolean (default: false) | If true, hashing will be performed, but signing will not |
room_version | string (optional) | If creating a PDU for an unknown room, you can manually specify the version of the room. If omitted, the room’s version will be fetched from the database. |
pdu | object | The base PDU object |
Response body:
201 Created:
{
"auth_events": ["$..."],
"content": {
},
"depth": 1,
"origin_server_ts": 123456789,
"prev_events": ["$..."],
"room_id": "!...",
"sender": "@...",
"unsigned": {
"event_id": "$..."
}
}
Refer to the “event format” section of the specified room_version’s documentation:
https://spec.matrix.org/v1.18/rooms/.
This endpoint always successfully responds with 201 Created.
Errors:
400 / M_BAD_JSON: The request body is missing thepdufield, orpduis missing some required keys.403 / M_FORBIDDEN: You did not authenticate, or are not a server administrator.404 / M_NOT_FOUND:room_version,auth_events,prev_events, ordepthwere not supplied, and the server could not fetch required data from the database to automatically populate these fields.500 / M_UNKNOWN: Hashing, signing, or event ID calculation failed.
Reload Configuration
POST /_hammerhead/v0/admin/reload-config
Authentication: Admin account.
Immediately reloads the server configuration by re-reading the configuration path, and clears internal caches.
Warning
Configuration reloading is not comprehensive, and you should usually restart the server entirely instead.
While Hammerhead components generally all refer directly to the same configuration reference variable, some values are disassociated from the configuration while initialising (such as cache sizes), meaning reloading the configuration may not update those values.
Furthermore, not all caches are cleared. Hammerhead has a LOT of moving parts, clearing EVERYTHING is not feasible.
Request body: None (ignored).
Response body (200 OK): JSON representation of the freshly loaded configuration file.
Errors:
403 / M_FORBIDDEN: You forgot to authenticate, or are not a server administrator.500 / M_UNKNOWN: There was an issue reloading the configuration. Typically, the result of an invalid configuration, or when the configuration is no longer found at the initial path.
Delete Media
DELETE /_hammerhead/v0/admin/media/{origin}/{media_id}
Authentication: Admin account.
Deletes a specific piece of media. If origin is the current server, it can be shortened to _.
Media that does not exist always returns a successful response (unless the database returns an error).
Request body: None (ignored)
Response body:
200 OK:
{
"ok": 1,
"fail": 0
}
| Key | Type | Description |
|---|---|---|
ok | number | The number of media files that were successfully deleted (in this case, always 1) |
fail | number | The number of media files that could not be deleted (in this case, always 0) |
Errors:
400 / M_MISSING_PARAM:originormedia_idwere missing or empty.403 / M_FORBIDDEN: You forgot to authenticate, or are not a server administrator.500 / M_UNKNOWN: The server was unable to delete the media (database error, or the file system returned an error other than “file does not exist”, such as a permission error).
Purge Media
POST /_hammerhead/v0/admin/media/purge
Authentication: Admin account.
Purges media from the repository matching the specified criteria. This is irreversible and uninterruptible. Make no mistakes.
When multiple criteria are specified, they create an OR condition, not an AND. For example, providing
all_remote_media=true and all_caches=true will delete all media that originated on another homeserver, OR is a
cache file. This is no different for origin and user_id - if you supply all_remote_media=true and origin="_",
this will delete media that is from another homeserver OR belongs to this homeserver.
Definitions
Remote media: Media that was uploaded to other Matrix homeservers, and was retrieved over federation.
Local media: Media that was uploaded directly to this homeserver.
Sparse media: Local media that was asynchronously created, but never had any content uploaded to it.
External media: Media cached from non-Matrix servers, such as URL preview images. Not associated with any user.
Tip
You typically do not need to manually clean up sparse media, as there is a clean-up task that runs every few hours, and one that also runs on server startup.
Caution
Media deletion is permanent and indiscriminate. As media is not currently related to events, there is no way for the server to know if a media file is a sticker, profile picture, custom emoji, room avatar, or just a normal attachment. It also has no way of knowing if the media has ever been used. Purging media will almost always have some undesirable collateral, so you should always think thrice before deleting local media. Deleting remote media is less bad, since the media can just be fetched again over federation, but only if the original server is still online.
The user filter will only work for local media, or for remote media retrieved from another Hammerhead server.
Regular remote media does not include metadata about who created it, but Hammerhead transmits that information
with non-specced metadata.
This operation uses limited concurrency to delete entries in parallel. Read more about how limited concurrency works at
Configuration Reference § GOMAXPROCS. This is a blocking operation, if
there is a lot of work to do, your request may time out.
Request body:
{
"all_remote_media": true,
"all_local_media": true,
"all_caches": true,
"all_sparse": true,
"origin": "_",
"user": "@baduser:example.com"
}
| Key | Type | Description |
|---|---|---|
all_remote_media | boolean (default: false) | If true, all remote media (media from other Matrix homeservers) is deleted |
all_local_media | boolean (default: false) | If true, all local media is deleted |
all_caches | boolean (default: false) | If true, all cached media is deleted |
all_sparse | boolean (default: false) | If true, all “sparse” media entries are deleted |
origin | string (default: empty) | If not empty, delete all media that originates from the specified server |
user | string (default: empty) | If not empty, delete all media that was uploaded by the specified user |
Response body:
200 OK:
{
"ok": 420,
"fail": 69
}
| Key | Type | Description |
|---|---|---|
ok | number | The number of media files that were successfully deleted |
fail | number | The number of media files that could not be deleted |
Errors:
400 / M_NOT_JSON: The request body was not valid JSON.403 / M_FORBIDDEN: You forgot to authenticate, or are not a server administrator.500 / M_UNKNOWN: There was an error querying the database to fetch eligible media entries.
Delete Room
DELETE /_hammerhead/v0/admin/rooms/{room_id}
Authentication: Admin account.
Deletes a room from the database. First tries to remove all local members (leave, decline pending invites, rescind pending knocks), then tries to delete as much data associated with the room as possible.
If force is not true, and there is an error during any stage of the operation, it aborts immediately.
Actual data deletion is performed in a transaction, meaning if it fails, no data is deleted. However, membership changes
are not transactional, and are always committed immediately, non-atomically.
Tip
You typically do not need to delete rooms to reclaim space from abandoned rooms. When all local members leave a room, it is automatically deleted, and as such does not need to be manually “cleaned up”.
Request body:
{
"force": true
}
| Key | Type | Description |
|---|---|---|
force | boolean (default: false) | If true, errors during evacuation and deletion are ignored where possible |
Response body (200 OK): Empty object (literally {}).
Errors:
400 / M_NOT_JSON: The request body was not valid JSON.403 / M_FORBIDDEN: You forgot to authenticate, or are not an administrator.429 / M_LIMIT_EXCEEDED: There is already a room delete operation in progress (cannot have more than one at a time).500 / M_UNKNOWN: There was an unrecoverable error while evacuating or deleting the room.
Create User
POST /_hammerhead/v0/admin/users/create
Authentication: Admin account or pre-shared token.
Creates a new account with the specified pre-filled criteria.
The localpart must be a valid Matrix localpart, see the specification. On the other hand, password is not
subject to the same validation that would normally be applied during registration, so low-entropy/too short passwords
can be set here. It is recommended you don’t do that, though.
If password is omitted, the created user will not be able to log in (but admins can still create access tokens for
them, so the account isn’t useless).
Request body:
{
"localpart": "username.here",
"password": "$ecureP4sswordH3re!"
}
| Key | Type | Description |
|---|---|---|
localpart | string | The user’s localpart (the part of the user ID between @ and :) |
locked | boolean (default: false) | If true, the account will be locked upon creation |
suspended | boolean (default: false) | If true, the account will be suspended upon creation |
password | string (optional) | The account’s desired password |
Response body:
201 Created:
{
"account_id": 2
}
| Key | Type | Description |
|---|---|---|
account_id | number | The numberic ID of this account (in the database) |
Errors:
400 / M_NOT_JSON: The request body was not valid JSON.400 / M_INVALID_USERNAME:localpartis empty or invalid.400 / M_USER_IN_USE:localpartis already in use, or is reserved for another reason (e.g. service account).403 / M_FORBIDDEN: You forgot to authenticate, or are not a server administrator.403 / M_FORBIDDEN: You did not provide an administrator access token, and shared-secret token authentication is disabled, or the provided token did not match.
Deactivate Account
POST /_hammerhead/v0/admin/users/{user_id}/deactivate
user_id: Fully qualified user ID (@foo:bar.example), or localpart (foo).
Authentication: Admin account or pre-shared token.
Immediately deactivates an account. If the account is already deactivated, makes no change.
Performs the following steps:
- Mark account as deactivated (but not erased). Prevents further use of account immediately.
- Remove account’s administrator flag.
Ifredactistrue, issue redactions for every event the target ever sent.- Mark account as deactivated again, this time respecting the GDPR
erasedflag. - Deletes all profile data the target set.
- Deletes all account data the target set.
- Removes all devices (sessions) the target had.
- Then, with limited concurrency, for each room the target was a member of:
- If the target was invited to the room, reject the invite
- If the target was knocking on the room, rescind the join request
- If the target was joined to the room, leave the room
This is a blocking operation, your request may time out.
Request body:
{
"erase": false,
"redact": false
}
| Key | Type | Description |
|---|---|---|
erase | boolean (default: false) | If true, mark the account as GDPR erased |
redact | boolean (default: false) | If true, issue redactions for every event sent by the user. |
Response body (200 OK): Empty object (literally {}).
Response body (304 Not Modified): No data.
Errors:
400 / M_NOT_JSON: Request body was not valid JSON.403 / M_FORBIDDEN: You forgot to authenticate, or are not a server administrator.403 / M_FORBIDDEN: You did not provide an administrator access token, and shared-secret token authentication is disabled, or the provided token did not match.404 / M_NOT_FOUND: The requested user does not exist or does not belong to this server.500 / M_UNKNOWN: An unrecoverable error was encountered during deactivation.
List Users
GET /_hammerhead/v0/admin/users
Authentication: Admin account.
Lists all users registered on this homeserver. Currently, filtering, sorting, and backwards pagination are not supported. Results are ordered newest → oldest.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
since | number | The end token of the previous page (from next_batch). |
limit | number (default: 1) | The maximum nmber of results to return. Cannot be less than 1, cannot be more than 1000. |
Response body:
200 OK:
{
"chunk": [
{
"entry_id": 1,
"localpart": "foo",
"admin": true,
"locked": false,
"suspended": false,
"deactivated": false,
"erased": false,
"created_at": 1775660510816
}
],
"next_batch": 1
}
| Key | Type | Description |
|---|---|---|
chunk | Array[Account] (optional) | An array of up to {limit} accounts |
next_batch | number (optional) | The next batch token to pass to since, if there are potentially more results |
Account:
| Key | Type | Description |
|---|---|---|
entry_id | number | The numeric database ID of this account |
localpart | string | The localpart of this account |
admin | boolean | The administrator status of this account |
locked | boolean | Whether this account is locked |
suspended | boolean | Whether this account is suspended |
deactivated | boolean | Whether his account has been deactivated |
erased | boolean | In combination with deactivated, whether this account is GDPR-erased |
created_at | number | Unix timestamp (in milliseconds) when this account was registered |
Errors:
400 / M_INVALID_PARAM:sinceorlimitwere not numbers.403 / M_FORBIDDEN: You forgot to authenticate, or are not a server administrator.
Update User
PATCH /_hammerhead/v0/admin/users/{user_id}
user_id: Fully qualified user ID (@foo:bar.example), or localpart (foo).
Authentication: Admin account or pre-shared token.
Updates any of the provided attributes on the account. If an attribute is not provided in the request body, no change is made to the relevant account attribute. As such, all request keys are optional and may be omitted.
Request body:
{
"password": "foobar",
"admin": true
}
| Key | Type | Description |
|---|---|---|
password | string | The new password for this user |
admin | boolean | Sets the administrator flag for this user |
Response body (200 OK): Empty object (literally {}).
Errors:
400 / M_NOT_JSON: The request body was not valid JSON.403 / M_FORBIDDEN: You forgot to authenticate, or are not a server administrator.403 / M_FORBIDDEN: You did not provide an administrator access token, and shared-secret token authentication is disabled, or the provided token did not match.404 / M_NOT_FOUND: The requested user does not exist or does not belong to this server.