Documentation for v2.11.0. Read the latest, v2.12.0.

Reference

Maintenance API

maintenance.yaml version 2.11.0

The administrative surface of Sanshain: users, roles, groups, maintainers, Producer and Consumer administration, version deletion, settings and observability.

Kept separate from api.yaml deliberately. That file is the contract build plugins and CLIs are written against and must stay stable; this one changes whenever the admin interface does. One document would imply one stability promise for both.

Authorisation. Every endpoint states what it requires. Any signed-in caller marks the read-only listings the dashboard needs before it knows who is looking; everything else names a permission. Producer-scoped endpoints additionally admit a maintainer of the Producer named in the path. An account listed in SANSHAIN_ROOT_USERS holds every permission.

See Roles and permissions for the concepts.

Tokens, scopes, sessions and error shapes are explained in Using the API.

Endpoints#

MethodPathSummaryPermissionScope
GET/admin/auth-configRead: auth-configmanage_auth_configglobal
PUT/admin/auth-configReplace: auth-configmanage_auth_configglobal
POST/admin/auth-config/testCreate or run: auth-config/testmanage_auth_configglobal
GET/admin/backup/sqliteRead: backup/sqliterun_destructive_operationsglobal
GET/admin/branches/{name}/timelineRead: branches/name/timelineany signed-in callerglobal
GET/admin/trunk/graphRead: trunk/graphany signed-in callerglobal
GET/admin/trunk/timelineRead: trunk/timelineany signed-in callerglobal
POST/admin/cache/clearCreate or run: cache/clearmanage_settingsglobal
POST/admin/cleanup/dependenciesCreate or run: cleanup/dependenciesmanage_settingsglobal
POST/admin/cleanup/trunkCreate or run: cleanup/trunkmanage_settingsglobal
POST/admin/cleanup/snapshotsCreate or run: cleanup/snapshotsmanage_settingsglobal
GET/admin/consumersRead: consumersany signed-in callerglobal
GET/admin/consumers/{name}Read: consumers/nameany signed-in callerglobal
DELETE/admin/consumers/{name}Delete: consumers/namemanage_consumersglobal
GET/admin/consumers/{name}/endpointsRead: consumers/name/endpointsany signed-in callerglobal
GET/admin/endpoint-versionsRead: endpoint-versionsany signed-in callerglobal
GET/admin/endpoint-yamlRead: endpoint-yamlany signed-in callerglobal
GET/admin/directory-groupsRead: directory-groupsmanage_rolesglobal
GET/admin/groupsRead: groupsmanage_rolesglobal
POST/admin/groupsCreate or run: groupsmanage_rolesglobal
POST/admin/groups/directoryCreate or run: groups/directorymanage_rolesglobal
PUT/admin/groups/{id}Replace: groups/idmanage_rolesglobal
DELETE/admin/groups/{id}Delete: groups/idmanage_rolesglobal
POST/admin/groups/{id}/membersCreate or run: groups/id/membersmanage_rolesglobal
DELETE/admin/groups/{id}/members/{user_id}Delete: groups/id/members/user_idmanage_rolesglobal
GET/admin/maintainersRead: maintainersmanage_rolesglobal
POST/admin/nuke/consumersCreate or run: nuke/consumersrun_destructive_operationsglobal
POST/admin/nuke/databaseCreate or run: nuke/databaserun_destructive_operationsglobal
POST/admin/nuke/producersCreate or run: nuke/producersrun_destructive_operationsglobal
POST/admin/nuke/usersCreate or run: nuke/usersrun_destructive_operationsglobal
GET/admin/observability/audit-logsRead: observability/audit-logsview_auditglobal
GET/admin/observability/audit-logs/exportRead: observability/audit-logs/exportview_auditglobal
GET/admin/observability/debug-configRead: observability/debug-configany signed-in callerglobal
POST/admin/observability/debug-config-updateCreate or run: observability/debug-config-updatemanage_settingsglobal
GET/admin/observability/logsRead: observability/logsany signed-in callerglobal
GET/admin/observability/statsRead: observability/statsany signed-in callerglobal
GET/admin/graph/diffRead: graph/diffany signed-in callerglobal
GET/admin/peripheryRead: peripheryany signed-in callerglobal
GET/admin/graph/scopeRead: graph scopeany signed-in callerglobal
GET/admin/producersRead: producersany signed-in callerglobal
POST/admin/producers/metadataCreate or run: producers/metadatamanage_producersglobal
POST/admin/producers/provideCreate or run: producers/providemanage_producersglobal
GET/admin/producers/{name}Read: producers/nameany signed-in callerglobal
DELETE/admin/producers/{name}Delete: producers/namemanage_producersper Producer: also its maintainers
GET/admin/producers/{name}/branch-membershipsRead: producers/name/branch-membershipsany signed-in callerglobal
GET/admin/producers/{name}/diffRead: producers/name/diffany signed-in callerglobal
GET/admin/producers/{name}/blameRead: producers/name/blameany signed-in callerglobal
GET/admin/producers/{name}/structureRead: producers/name/structureany signed-in callerglobal
GET/admin/branchesRead: branchesany signed-in callerglobal
POST/admin/branchesWrite: branchesrelease_gaglobal
PUT/admin/branches/{name}Write: branches/namemanage_producersglobal
DELETE/admin/branches/{name}Write: branches/namemanage_producersglobal
GET/admin/branches/{name}/graphRead: branches/name/graphany signed-in callerglobal
GET/admin/producers/{name}/endpointsRead: producers/name/endpointsany signed-in callerglobal
GET/admin/producers/{name}/full-specRead: producers/name/full-specany signed-in callerglobal
PUT/admin/producers/{name}/externalUpdate: producers/name/externalmanage_producersglobal
GET/admin/producers/{name}/maintainersRead: producers/name/maintainersmanage_producersper Producer: also its maintainers
POST/admin/producers/{name}/maintainersCreate or run: producers/name/maintainersmanage_rolesglobal
DELETE/admin/producers/{name}/maintainers/groups/{group_id}Delete: producers/name/maintainers/groups/group_idmanage_rolesglobal
DELETE/admin/producers/{name}/maintainers/users/{user_id}Delete: producers/name/maintainers/users/user_idmanage_rolesglobal
GET/admin/producers/{name}/versionsRead: producers/name/versionsany signed-in callerglobal
DELETE/admin/producers/{name}/versions/{api_type}/{version}Delete: producers/name/versions/api_type/versionmanage_producersper Producer: also its maintainers
POST/admin/producers/{name}/versions/{api_type}/{version}/promoteWrite: producers/name/versions/api_type/version/promoterelease_gaglobal
GET/admin/producers/{name}/versions/{api_type}/{version}/dependentsRead: producers/name/versions/api_type/version/dependentsany signed-in callerglobal
GET/admin/rolesRead: rolesmanage_rolesglobal
POST/admin/settings/registrationCreate or run: settings/registrationmanage_settingsglobal
GET/admin/settings/auto-approveRead: settings/auto-approvemanage_settingsglobal
POST/admin/settings/auto-approveCreate or run: settings/auto-approvemanage_settingsglobal
GET/admin/settings/gravatarRead: settings/gravatarmanage_settingsglobal
POST/admin/settings/gravatarCreate or run: settings/gravatarmanage_settingsglobal
GET/admin/settings/cacheRead: settings/cachemanage_settingsglobal
POST/admin/settings/cacheCreate or run: settings/cachemanage_settingsglobal
GET/admin/settings/databaseRead: settings/databasemanage_settingsglobal
GET/admin/settings/dependency-max-ageRead: settings/dependency-max-agemanage_settingsglobal
POST/admin/settings/dependency-max-ageCreate or run: settings/dependency-max-agemanage_settingsglobal
GET/admin/settings/trunk-max-ageRead: settings/trunk-max-agemanage_settingsglobal
POST/admin/settings/trunk-max-ageCreate or run: settings/trunk-max-agemanage_settingsglobal
GET/admin/instance-settingsRead: instance-settingsany signed-in callerglobal
GET/admin/ticket-url-templateRead: ticket-url-templateany signed-in callerglobal
POST/admin/settings/ticket-url-templateCreate or run: settings/ticket-url-templatemanage_settingsglobal
GET/admin/settings/snapshot-max-ageRead: settings/snapshot-max-agemanage_settingsglobal
POST/admin/settings/snapshot-max-ageCreate or run: settings/snapshot-max-agemanage_settingsglobal
GET/admin/webhooksRead: webhooks/webhooksmanage_settingsglobal
POST/admin/webhooksCreate or run: webhooks/webhooksmanage_settingsglobal
GET/admin/webhooks/{id}Read: webhooks/webhooks/idmanage_settingsglobal
PATCH/admin/webhooks/{id}Update: webhooks/webhooks/idmanage_settingsglobal
DELETE/admin/webhooks/{id}Delete: webhooks/webhooks/idmanage_settingsglobal
POST/admin/webhooks/{id}/testCreate or run: webhooks/webhooks/id/testmanage_settingsglobal
POST/admin/webhooks/{id}/rotate-secretCreate or run: webhooks/webhooks/id/rotate-secretmanage_settingsglobal
GET/admin/webhooks/{id}/deliveriesRead: webhooks/webhooks/id/deliveriesmanage_settingsglobal
POST/admin/webhooks/{id}/deliveries/{delivery_id}/redeliverCreate or run: webhooks/webhooks/id/deliveries/delivery_id/redelivermanage_settingsglobal
GET/admin/producers/{name}/webhooksRead: producers/name/webhooksmanage_producersper Producer: also its maintainers
POST/admin/producers/{name}/webhooksCreate or run: producers/name/webhooksmanage_producersper Producer: also its maintainers
GET/admin/producers/{name}/webhooks/{id}Read: producers/name/webhooks/idmanage_producersper Producer: also its maintainers
PATCH/admin/producers/{name}/webhooks/{id}Update: producers/name/webhooks/idmanage_producersper Producer: also its maintainers
DELETE/admin/producers/{name}/webhooks/{id}Delete: producers/name/webhooks/idmanage_producersper Producer: also its maintainers
POST/admin/producers/{name}/webhooks/{id}/testCreate or run: producers/name/webhooks/id/testmanage_producersper Producer: also its maintainers
POST/admin/producers/{name}/webhooks/{id}/rotate-secretCreate or run: producers/name/webhooks/id/rotate-secretmanage_producersper Producer: also its maintainers
GET/admin/producers/{name}/webhooks/{id}/deliveriesRead: producers/name/webhooks/id/deliveriesmanage_producersper Producer: also its maintainers
POST/admin/producers/{name}/webhooks/{id}/deliveries/{delivery_id}/redeliverCreate or run: producers/name/webhooks/id/deliveries/delivery_id/redelivermanage_producersper Producer: also its maintainers
GET/admin/consumers/{name}/webhooksRead: consumers/name/webhooksmanage_producersper Consumer: also maintainers of the Producer of the same name
POST/admin/consumers/{name}/webhooksCreate or run: consumers/name/webhooksmanage_producersper Consumer: also maintainers of the Producer of the same name
GET/admin/consumers/{name}/webhooks/{id}Read: consumers/name/webhooks/idmanage_producersper Consumer: also maintainers of the Producer of the same name
PATCH/admin/consumers/{name}/webhooks/{id}Update: consumers/name/webhooks/idmanage_producersper Consumer: also maintainers of the Producer of the same name
DELETE/admin/consumers/{name}/webhooks/{id}Delete: consumers/name/webhooks/idmanage_producersper Consumer: also maintainers of the Producer of the same name
POST/admin/consumers/{name}/webhooks/{id}/testCreate or run: consumers/name/webhooks/id/testmanage_producersper Consumer: also maintainers of the Producer of the same name
POST/admin/consumers/{name}/webhooks/{id}/rotate-secretCreate or run: consumers/name/webhooks/id/rotate-secretmanage_producersper Consumer: also maintainers of the Producer of the same name
GET/admin/consumers/{name}/webhooks/{id}/deliveriesRead: consumers/name/webhooks/id/deliveriesmanage_producersper Consumer: also maintainers of the Producer of the same name
POST/admin/consumers/{name}/webhooks/{id}/deliveries/{delivery_id}/redeliverCreate or run: consumers/name/webhooks/id/deliveries/delivery_id/redelivermanage_producersper Consumer: also maintainers of the Producer of the same name
GET/admin/settings/webhook-allowed-hostsRead: settings/webhook-allowed-hostsmanage_settingsglobal
POST/admin/settings/webhook-allowed-hostsCreate or run: settings/webhook-allowed-hostsmanage_settingsglobal
GET/admin/usersRead: usersmanage_usersglobal
POST/admin/usersCreate or run: usersmanage_usersglobal
DELETE/admin/users/{id}Delete: users/idmanage_usersglobal
PUT/admin/users/{id}/display-nameUpdate: users/id/display-namemanage_usersglobal
POST/admin/users/{id}/approveCreate or run: users/id/approvemanage_usersglobal
GET/admin/users/{id}/maintainsRead: users/id/maintainsmanage_rolesglobal
GET/admin/users/{id}/rolesRead: users/id/rolesmanage_rolesglobal
POST/admin/users/{id}/rolesCreate or run: users/id/rolesmanage_rolesglobal
DELETE/admin/users/{id}/roles/{role}Delete: users/id/roles/rolemanage_rolesglobal

/admin/auth-config#

GET /admin/auth-config#

Read: auth-config

Authentication: session token · Permission: manage_auth_config · Scope: global

Requires the manage_auth_config permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

PUT /admin/auth-config#

Replace: auth-config

Authentication: session token · Permission: manage_auth_config · Scope: global

Requires the manage_auth_config permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/auth-config/test#

POST /admin/auth-config/test#

Create or run: auth-config/test

Authentication: session token · Permission: manage_auth_config · Scope: global

Requires the manage_auth_config permission. Connects to the directory named by the submitted LDAP configuration and binds with its service account; nothing is saved.

Responses

StatusDescriptionBody
200

Connected and bound.

—
400

The connection or the bind failed; error says why in plain words. When the directory could not be reached at all (name did not resolve, no route, port refused, connect timeout), diagnostics carries the container's own network diagnosis, one line each: the addresses the name resolves to, the resolvers in use, and per address the result of an ICMP echo, the kernel's route lookup and a TCP connect to the port. Absent for a failure after the connection was up (TLS, stall, bind).

ConnectionTestFailure
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/backup/sqlite#

GET /admin/backup/sqlite#

Read: backup/sqlite

Authentication: session token API token (full) · Permission: run_destructive_operations · Scope: global

Requires the run_destructive_operations permission. A consistent snapshot of the SQLite database, produced by VACUUM INTO while Sanshain runs, streamed as application/vnd.sqlite3 with a Content-Disposition filename sanshain-<instance version>-<UTC timestamp>.db. Only one snapshot runs at a time. Meant for the scheduled backup: a full API token of an account holding the permission is accepted beside a session, a read_only token is refused with 403. See the deployment guide, Backup and restore.

Responses

StatusDescriptionBody
200

The database snapshot.

string (binary) (application/vnd.sqlite3)
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
409

A snapshot is already being produced, or this instance runs PostgreSQL — the message names pg_dump.

ErrorBody

/admin/branches/{name}/timeline#

GET /admin/branches/{name}/timeline#

Read: branches/name/timeline

Authentication: session token · Permission: any signed-in caller · Scope: global

The instants this branch's graph changed (every open/close event of its append-only pin store), distinct and ascending — the timeline slider's markers (ADR-0005).

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

The change instants as a JSON array of RFC 3339 strings.

—
401

Not signed in.

ErrorBody
404

No sanshain-branch with this name.

ErrorBody

/admin/trunk/graph#

GET /admin/trunk/graph#

Read: trunk/graph

Authentication: session token · Permission: any signed-in caller · Scope: global

The main graph's pin set — current, or as it was at the optional at instant, reconstructed from the append-only trunk history (ADR-0005). Pins on deleted versions are marked dangling; a non-RFC-3339 at answers 400.

Parameters

NameInRequiredTypeDescription
atquery—string

Responses

StatusDescriptionBody
200

The pin set as a JSON array.

—
401

Not signed in.

ErrorBody

/admin/trunk/timeline#

GET /admin/trunk/timeline#

Read: trunk/timeline

Authentication: session token · Permission: any signed-in caller · Scope: global

The instants the main graph changed, distinct and ascending — the timeline slider's markers (ADR-0005).

Responses

StatusDescriptionBody
200

The change instants as a JSON array of RFC 3339 strings.

—
401

Not signed in.

ErrorBody

/admin/cache/clear#

POST /admin/cache/clear#

Create or run: cache/clear

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/cleanup/dependencies#

POST /admin/cleanup/dependencies#

Create or run: cleanup/dependencies

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/cleanup/trunk#

POST /admin/cleanup/trunk#

Create or run: cleanup/trunk

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Runs the trunk TTL cleanup immediately (ADR-0004): open trunk pins not re-required within trunk_max_age_days are closed (they leave the main graph's current view; the rows stay as timeline history) and stale trunk version markers are cleared. Answers {"closed": n}.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/cleanup/snapshots#

POST /admin/cleanup/snapshots#

Create or run: cleanup/snapshots

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Runs the use-based snapshot expiry immediately: snapshots neither provided nor required within snapshot_max_age_days are deleted. GA versions are never age-culled.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/consumers#

GET /admin/consumers#

Read: consumers

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/consumers/{name}#

GET /admin/consumers/{name}#

Read: consumers/name

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The Consumer's context (#48): what its Backstage catalog-info.yaml says about it — owner, description, source location and links. context is null until a catalog push described the system.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

The Consumer's name and context.

ParticipantContext

Example 200 response (placeholder values derived from the schema)

{
  "name": "string",
  "context": {
    "owner": {
      "kind": "string",
      "name": "string"
    },
    "description": "string",
    "source_location": "string",
    "links": [
      {
        "url": "string",
        "title": "string",
        "type": "string",
        "icon": "string"
      }
    ]
  }
}
401

Not signed in.

ErrorBody
404

No such Consumer.

ErrorBody

DELETE /admin/consumers/{name}#

Delete: consumers/name

Authentication: session token · Permission: manage_consumers · Scope: global

Requires the manage_consumers permission.

Answers {"deleted": name}. Refused with 409 while any sanshain-branch's recorded graph still references the Consumer — a release cut is a frozen record, and the delete would remove its edges retroactively. The message names the branches to retire first. Trunk presence does not block.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/consumers/{name}/endpoints#

GET /admin/consumers/{name}/endpoints#

Read: consumers/name/endpoints

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

Every endpoint the Consumer is recorded as depending on, with the pinned version, its current stability and the endpoint's one-line summary.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/endpoint-versions#

GET /admin/endpoint-versions#

Read: endpoint-versions

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The blame trail of one endpoint: which versions of the line changed it, with the Author attribution recorded on each Provide.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/endpoint-yaml#

GET /admin/endpoint-yaml#

Read: endpoint-yaml

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/directory-groups#

GET /admin/directory-groups#

Read: directory-groups

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Finds groups in the configured LDAP directory whose common name contains q (at most 20), through the service account of the LDAP configuration. Each hit carries its dn, its name and the group_id of the Sanshain group that stands for it, or null when it was not added yet.

Parameters

NameInRequiredTypeDescription
qqueryyesstring

Responses

StatusDescriptionBody
200

Success.

—
400

LDAP is not configured, or the directory search failed.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/groups#

GET /admin/groups#

Read: groups

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/groups#

Create or run: groups

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/groups/directory#

POST /admin/groups/directory#

Create or run: groups/directory

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Adds a directory group by its DN ({"dn": "cn=carla,ou=teams,dc=corp,dc=com"}), so roles and maintainerships can be attached to it; its members stay in the directory. Adding one that is known already answers that group.

Responses

StatusDescriptionBody
201

Created, or the group that was there already.

—
400

The DN is empty.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/groups/{id}#

PUT /admin/groups/{id}#

Replace: groups/id

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission. Adding an admin-guarded role (admin, releaser) to the group's role set — or removing one it holds — additionally requires the caller to be an admin.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

DELETE /admin/groups/{id}#

Delete: groups/id

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission. Deleting a group that holds an admin-guarded role (admin, releaser) strips that role from every member at once, so it additionally requires the caller to be an admin.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/groups/{id}/members#

POST /admin/groups/{id}/members#

Create or run: groups/id/members

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission. Membership of a group that holds an admin-guarded role (admin, releaser) IS a grant of that role, so adding a member then additionally requires the caller to be an admin.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/groups/{id}/members/{user_id}#

DELETE /admin/groups/{id}/members/{user_id}#

Delete: groups/id/members/user_id

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission. Removing a member from a group that holds an admin-guarded role (admin, releaser) strips that role, so it additionally requires the caller to be an admin.

Parameters

NameInRequiredTypeDescription
idpathyesinteger
user_idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/maintainers#

GET /admin/maintainers#

Read: maintainers

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Every Producer with its maintainer user and group ids, in one response — the aggregate behind the dashboard's Maintainers section, so it does not ask per Producer.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/nuke/consumers#

POST /admin/nuke/consumers#

Create or run: nuke/consumers

Authentication: session token · Permission: run_destructive_operations · Scope: global

Requires the run_destructive_operations permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/nuke/database#

POST /admin/nuke/database#

Create or run: nuke/database

Authentication: session token · Permission: run_destructive_operations · Scope: global

Requires the run_destructive_operations permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/nuke/producers#

POST /admin/nuke/producers#

Create or run: nuke/producers

Authentication: session token · Permission: run_destructive_operations · Scope: global

Requires the run_destructive_operations permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/nuke/users#

POST /admin/nuke/users#

Create or run: nuke/users

Authentication: session token · Permission: run_destructive_operations · Scope: global

Deletes every user who is not an effective administrator — users with a direct admin role, admin via group membership, or a configured root username survive. Requires the run_destructive_operations permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/observability/audit-logs#

GET /admin/observability/audit-logs#

Read: observability/audit-logs

Authentication: session token · Permission: view_audit · Scope: global

Requires the view_audit permission. The most recent audit entries for the observability panel; optional limit (default 100, capped at 200).

Parameters

NameInRequiredTypeDescription
limitquery—integer

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/observability/audit-logs/export#

GET /admin/observability/audit-logs/export#

Read: observability/audit-logs/export

Authentication: session token · Permission: view_audit · Scope: global

Requires the view_audit permission. CSV of the most recent audit entries, columns id,timestamp,username,action,details,service,version, action_type,stream — every field except the bulky diff blob.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/observability/debug-config#

GET /admin/observability/debug-config#

Read: observability/debug-config

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/observability/debug-config-update#

POST /admin/observability/debug-config-update#

Create or run: observability/debug-config-update

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/observability/logs#

GET /admin/observability/logs#

Read: observability/logs

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/observability/stats#

GET /admin/observability/stats#

Read: observability/stats

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/graph/diff#

GET /admin/graph/diff#

Read: graph/diff

Authentication: session token · Permission: any signed-in caller · Scope: global

Structured diff between two graph selections (ADR-0005) — release vs release, release vs main, one graph at two instants. left/right are main[@rfc3339] or <branch>[@rfc3339]. Answers services added/removed, pins added/removed, per-pin version changes, and periphery edges (system → Peripheral Resource) added/removed; all arrays empty means no differences.

Parameters

NameInRequiredTypeDescription
leftqueryyesstring
rightqueryyesstring

Responses

StatusDescriptionBody
200

The structured diff.

—
401

Not signed in.

ErrorBody
404

A named branch does not exist.

ErrorBody

/admin/periphery#

GET /admin/periphery#

Read: periphery

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The edges from systems to their Peripheral Resources (#46) — what their Backstage catalog-info.yaml lists under dependsOn — for one graph scope: dev (the default), main[@rfc3339] or <branch>[@rfc3339], the selector grammar of /admin/graph/diff. A past instant answers the periphery as it was then.

Parameters

NameInRequiredTypeDescription
scopequery—string

Responses

StatusDescriptionBody
200

A JSON array of {system, resource} edges, sorted.

—
400

A selector instant that is not RFC 3339, or dev@….

ErrorBody
401

Not signed in.

ErrorBody
404

A named branch does not exist.

ErrorBody

/admin/graph/scope#

GET /admin/graph/scope#

Read: graph scope

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

What the dependency graph needs of one scope beyond its pins, for dev (the default), main[@rfc3339] or <branch>[@rfc3339] — the selector grammar of /admin/graph/diff:

  • producers: the producers in the scope at its instant, each with one version line per API type. dev: every producer with any version (its newest per line). main: trunk's member versions — every trunk-flagged provide records the version it provided, closed again by the trunk TTL, a retire, or a version/producer delete. A branch: its member versions (tag-flagged provides) and, for lines without one, the highest version its pins name. stability is null when the version was deleted since; last_provided_at is the latest provide known at the instant (null when none is known).
  • harvested_subscriptions: dev all, live main the trunk-flagged ones, everything else none (there is no harvested timeline).
  • systems: the context (#48) of every system that pushed one, by name — owner, description, source location and links from its Backstage catalog-info.yaml — so the node panel needs no extra request. A present-day fact, the same in every scope.

Parameters

NameInRequiredTypeDescription
scopequery—string

Responses

StatusDescriptionBody
200

The scope's producers, harvested subscriptions and system contexts.

object

Example 200 response (placeholder values derived from the schema)

{
  "producers": [
    {
      "name": "string",
      "versions": [
        {
          "api_type": "openapi",
          "version": "string",
          "stability": "snapshot",
          "last_provided_at": "string"
        }
      ]
    }
  ],
  "harvested_subscriptions": [
    {
      "client": "string",
      "channel": "string",
      "message_name": "string",
      "owner": "string",
      "trunk": false
    }
  ],
  "systems": {
    "key": {
      "owner": {
        "kind": "string",
        "name": "string"
      },
      "description": "string",
      "source_location": "string",
      "links": [
        {
          "url": "string",
          "title": "string",
          "type": "string",
          "icon": "string"
        }
      ]
    }
  }
}
400

A selector instant that is not RFC 3339, or dev@….

ErrorBody
401

Not signed in.

ErrorBody
404

A named branch does not exist.

ErrorBody

/admin/producers#

GET /admin/producers#

Read: producers

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

Every Producer with metadata and its full version lines (version, stability, endpoint count, snapshot expiry).

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/metadata#

POST /admin/producers/metadata#

Create or run: producers/metadata

Authentication: session token · Permission: manage_producers · Scope: global

Requires the manage_producers permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/provide#

POST /admin/producers/provide#

Create or run: producers/provide

Authentication: session token · Permission: manage_producers · Scope: global

Requires the manage_producers permission.

The admin page's provide: publishes a pasted specification under the same rules as POST /provide and its siblings, and answers the same way. A Producer this call creates is someone else's API and is marked external in the same request. Body: producername, api_type (openapi, asyncapi or proto), content, stability and optional dry_run.

Responses

StatusDescriptionBody
202

Accepted, as from POST /provide.

—
400

The specification was refused.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}#

GET /admin/producers/{name}#

Read: producers/name

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The Producer's context (#48): what its Backstage catalog-info.yaml says about it — owner, description, source location and links. context is null until a catalog push described the system.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

The Producer's name and context.

ParticipantContext

Example 200 response (placeholder values derived from the schema)

{
  "name": "string",
  "context": {
    "owner": {
      "kind": "string",
      "name": "string"
    },
    "description": "string",
    "source_location": "string",
    "links": [
      {
        "url": "string",
        "title": "string",
        "type": "string",
        "icon": "string"
      }
    ]
  }
}
401

Not signed in.

ErrorBody
404

No such Producer.

ErrorBody

DELETE /admin/producers/{name}#

Delete: producers/name

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

Answers {"deleted": name}. Refused with 409 while any sanshain-branch's recorded graph still references the Producer — a release cut is a frozen record, and the delete would remove its edges retroactively. The message names the branches to retire first. Trunk presence does not block.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}/branch-memberships#

GET /admin/producers/{name}/branch-memberships#

Read: producers/name/branch-memberships

Authentication: session token · Permission: any signed-in caller · Scope: global

Reverse lookup (ADR-0005): every (api_type, version, branch) this producer is currently referenced by across all sanshain-branches — "which releases pin b@1.0.0?" answered where you're already looking.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

The memberships as a JSON array.

—
401

Not signed in.

ErrorBody
404

Producer not found.

ErrorBody

/admin/producers/{name}/diff#

GET /admin/producers/{name}/diff#

Read: producers/name/diff

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

Line diff between two versions of a line (api_type, from, to query parameters), computed with the similar (Myers) diff. Both stored documents are compared, or, with path and method, one endpoint's snippet (a version without the endpoint diffs as an empty document).

Parameters

NameInRequiredTypeDescription
namepathyesstring
api_typequery—string

One of openapi, asyncapi, proto.

Default "openapi".

fromqueryyesstring

The old side's version.

toqueryyesstring

The new side's version.

pathquery—string

Diff this endpoint's snippet instead of the document; needs method.

methodquery—string

The endpoint's method (or RPC/operation); needs path.

ignore_whitespacequery—boolean

Compare lines without leading and trailing whitespace, so indentation and trailing-whitespace differences are not changes.

Default false.

contextquery—string

Unchanged lines kept around each change, or all for the whole document in one hunk.

Default "3".

formatquery—string

text answers a unified-diff patch (empty when nothing differs). json answers {from, to, hunks}; each hunk has old_start, old_len, new_start, new_len and lines of {op: ctx|add|del, old_no, new_no, text, old_text?} (old_text only on a context line whose old side differs in whitespace).

One of text, json.

Default "text".

Responses

StatusDescriptionBody
200

The diff, as text/plain or JSON per format.

—
400

Only one of path/method, or a malformed context or format.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

The Producer or one of the two versions does not exist.

ErrorBody

/admin/producers/{name}/blame#

GET /admin/producers/{name}/blame#

Read: producers/name/blame

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

Blame for one version of a line: the version's text (the stored document, or with path and method one endpoint's snippet) as a JSON array of blocks of consecutive lines last changed in the same version, oldest version first walked with the same similar diff as /diff. Each block is {version, stability, provided_by, updated_at, start_line, lines}; start_line is 1-based. A version with identical content (a promotion) changes no line; in endpoint mode a version without the endpoint is an empty text. An empty array means the version's text is empty.

Parameters

NameInRequiredTypeDescription
namepathyesstring
api_typequery—string

One of openapi, asyncapi, proto.

Default "openapi".

versionqueryyesstring

The version to blame.

pathquery—string

Blame this endpoint's snippet instead of the document; needs method.

methodquery—string

The endpoint's method (or RPC/operation); needs path.

Responses

StatusDescriptionBody
200

The blame blocks.

—
400

Only one of path/method.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

The Producer or the version does not exist.

ErrorBody

/admin/producers/{name}/structure#

GET /admin/producers/{name}/structure#

Read: producers/name/structure

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The structure view of one OpenAPI or AsyncAPI version: {service, api_type, version, endpoints}, each endpoint a tree of nodes (parameters by location, request body and responses per media type, AsyncAPI messages with headers and payload), every local $ref inlined, allOf merged with each member's origin, oneOf/anyOf as a variants node with its discriminator, and a recursive $ref cut as a cycle node. A node carries key, kind, name and, when present, type, summary, description, required, deprecated, nullable, read_only, write_only, default, example, enum, constraints, and changed_in: the version of the line (up to this one) that last changed the node's own fields, with inside when something below changed later. With path and method the answer is that endpoint with its children, every body carrying an example (example_generated when built from the schema). Without them every endpoint comes without children (children_omitted).

Parameters

NameInRequiredTypeDescription
namepathyesstring
api_typequery—string

One of openapi, asyncapi.

Default "openapi".

versionqueryyesstring

The version to show.

pathquery—string

Show this endpoint with its children; needs method.

methodquery—string

The endpoint's method (or operation); needs path.

Responses

StatusDescriptionBody
200

The structure of the version.

—
400

Only one of path/method, or a proto line.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

The Producer, the version or the endpoint does not exist.

ErrorBody

/admin/branches#

GET /admin/branches#

Read: branches

Authentication: session token · Permission: any signed-in caller · Scope: global

Every sanshain-branch (ADR-0005), newest first: id, name, created_at, created_by, source (trunk or the source branch's then-current name) and as_of (the instant of the source graph the branch was born from).

Responses

StatusDescriptionBody
200

The branch listing as a JSON array.

—
401

Not signed in.

ErrorBody

POST /admin/branches#

Write: branches

Authentication: session token · Permission: release_ga · Scope: global

Requires the release_ga permission (the releaser role; admins and root hold it implicitly) — creating a named release graph is release work, and this endpoint's primary caller is the DevOps release-cut script, which needs nothing beyond this description:

Creates a sanshain-branch: a named copy of a source graph at a chosen instant, from then on updated by tag-flagged hotfix builds.

Body (JSON):

  • name (string, required): unique among existing branches; free text ("Release Maribou"). Duplicate → 409.
  • source (string, optional): the name of an existing branch to copy from. Omitted → the trunk graph. Unknown name → 404, nothing is created.
  • as_of (string, optional): RFC 3339 instant of the source graph to copy. Omitted → now. A past instant creates the branch retroactively — trunk-at-that-date is reconstructed from the append-only history, so a forgotten release day is repairable. Malformed → 400.

The call is not idempotent: repeating it answers 409. Creation is audited (BRANCH_CREATED, naming the caller, source and instant).

Responses

StatusDescriptionBody
201

Created. The body is the branch record (see GET).

—
400

Empty or reserved name (main, dev, trunk), or as_of is not an RFC 3339 instant.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without the release_ga permission.

ErrorBody
404

The source branch does not exist. Nothing was created.

ErrorBody
409

A branch with this name already exists.

ErrorBody

/admin/branches/{name}#

PUT /admin/branches/{name}#

Write: branches/name

Authentication: session token · Permission: manage_producers · Scope: global

Requires the manage_producers permission (admins) — deliberately a higher bar than creation. Renames the branch: identity is the internal id, so membership, timeline and audit stamps survive; a pipeline still sending the old tag name gets the instructive 404 until its configuration is updated (intended for a name wrong enough to rename). Body: {"new_name": "..."}. Audited with old and new name.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Renamed; the body is the updated branch record.

—
400

Empty new name.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but not an admin.

ErrorBody
404

No sanshain-branch with this name.

ErrorBody
409

The new name is already taken by a live branch.

ErrorBody

DELETE /admin/branches/{name}#

Write: branches/name

Authentication: session token · Permission: manage_producers · Scope: global

Requires the manage_producers permission (admins). Deletes the branch and its graph rows — the deliberate, audited EOL act — and frees the name for reuse.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
204

Deleted.

—
401

Not signed in.

ErrorBody
403

Signed in, but not an admin.

ErrorBody
404

No sanshain-branch with this name.

ErrorBody

/admin/branches/{name}/graph#

GET /admin/branches/{name}/graph#

Read: branches/name/graph

Authentication: session token · Permission: any signed-in caller · Scope: global

The branch's pin set — its graph edges, each {client, service, api_type, version, path, method, valid_from, last_required_at} with the version by value (a deleted spec version renders as a dangling reference and heals when the number is re-provided). Optional at (RFC 3339) answers the branch's graph as it was at that instant; a non-RFC-3339 at answers 400.

Parameters

NameInRequiredTypeDescription
namepathyesstring
atquery—string

Responses

StatusDescriptionBody
200

The pin set as a JSON array.

—
401

Not signed in.

ErrorBody
404

No sanshain-branch with this name.

ErrorBody

/admin/producers/{name}/endpoints#

GET /admin/producers/{name}/endpoints#

Read: producers/name/endpoints

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The endpoints of one version-line entry (api_type, version query parameters), ordered by path, then method.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}/full-spec#

GET /admin/producers/{name}/full-spec#

Read: producers/name/full-spec

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The full provided document of one version-line entry, exactly as the Producer submitted it (api_type, version query parameters).

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}/external#

PUT /admin/producers/{name}/external#

Update: producers/name/external

Authentication: session token · Permission: manage_producers · Scope: global

Requires the manage_producers permission.

Marks a Producer as external, an outside API the company does not maintain ({"external": true}), or as the company's own again (false). A provide on the admin page (POST /admin/producers/provide) marks a Producer it creates. External Producers are drawn with a light grey fill in the graph and tagged in the lists (external on each Producer).

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such Producer.

ErrorBody

/admin/producers/{name}/maintainers#

GET /admin/producers/{name}/maintainers#

Read: producers/name/maintainers

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/producers/{name}/maintainers#

Create or run: producers/name/maintainers

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}/maintainers/groups/{group_id}#

DELETE /admin/producers/{name}/maintainers/groups/{group_id}#

Delete: producers/name/maintainers/groups/group_id

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Parameters

NameInRequiredTypeDescription
namepathyesstring
group_idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}/maintainers/users/{user_id}#

DELETE /admin/producers/{name}/maintainers/users/{user_id}#

Delete: producers/name/maintainers/users/user_id

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Parameters

NameInRequiredTypeDescription
namepathyesstring
user_idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}/versions#

GET /admin/producers/{name}/versions#

Read: producers/name/versions

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

One Producer's version lines, optionally narrowed with an api_type query parameter.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}/versions/{api_type}/{version}#

DELETE /admin/producers/{name}/versions/{api_type}/{version}#

Delete: producers/name/versions/api_type/version

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

The sole escape hatch from GA immutability (ADR-0003): delete the version outright, freeing its number. Consumers pinned to it hard-fail on their next require — check the dependents listing first. Audited, naming the pinned Consumers.

Parameters

NameInRequiredTypeDescription
namepathyesstring
api_typepathyesstring
versionpathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}/versions/{api_type}/{version}/promote#

POST /admin/producers/{name}/versions/{api_type}/{version}/promote#

Write: producers/name/versions/api_type/version/promote

Authentication: session token · Permission: release_ga · Scope: global

Requires the release_ga permission — enforced by the same GA gate every release goes through, so an unauthorized attempt answers the instructive 403 and is recorded as a VERSION_REJECTED audit entry with reason ga_requires_releaser.

One-click promote: releases the stored snapshot in place, exactly as if its content were re-provided with stability: ga — the original provider stays credited, the promoting Actor is named in the VERSION_PROMOTED audit entry, and AsyncAPI GA contract checks apply. Promoting an already-GA version is an idempotent no-op (promoted: false).

Parameters

NameInRequiredTypeDescription
namepathyesstring
api_typepathyesstring
versionpathyesstring

Responses

StatusDescriptionBody
202

Promoted (or already GA — idempotent no-op). Standard Provide response body.

—
401

Not signed in.

ErrorBody
403

Signed in, but without the release_ga permission.

ErrorBody
404

Unknown Producer or version.

ErrorBody
409

Rejected by the same rules a GA Provide faces — semver honesty against the GA baseline (carries proposed_version), an AsyncAPI channel-message contract owned by another Producer, or the snapshot changing between click and release.

ErrorBody

/admin/producers/{name}/versions/{api_type}/{version}/dependents#

GET /admin/producers/{name}/versions/{api_type}/{version}/dependents#

Read: producers/name/versions/api_type/version/dependents

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The Consumers currently pinned to this version — what a delete would break. Shown by the UI before a delete-version is confirmed.

Parameters

NameInRequiredTypeDescription
namepathyesstring
api_typepathyesstring
versionpathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/roles#

GET /admin/roles#

Read: roles

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/settings/registration#

POST /admin/settings/registration#

Create or run: settings/registration

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission, like the auto-approve switch beside it.

Turns self-registration on or off ({"enabled": true}). Registration is offered only in Local mode; turning it off keeps every account already created.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/settings/auto-approve#

GET /admin/settings/auto-approve#

Read: settings/auto-approve

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/settings/auto-approve#

Create or run: settings/auto-approve

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/settings/gravatar#

GET /admin/settings/gravatar#

Read: settings/gravatar

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/settings/gravatar#

Create or run: settings/gravatar

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Switches Gravatar pictures on or off ({"enabled": true}). Off by default: while on, the pages load each person's picture from gravatar.com by the SHA-256 of their e-mail address; the address itself never leaves the server.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/settings/cache#

GET /admin/settings/cache#

Read: settings/cache

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/settings/cache#

Create or run: settings/cache

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/settings/database#

GET /admin/settings/database#

Read: settings/database

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/settings/dependency-max-age#

GET /admin/settings/dependency-max-age#

Read: settings/dependency-max-age

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/settings/dependency-max-age#

Create or run: settings/dependency-max-age

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission. Body: {"days": n}, at least 1 and at most 36500 (~100 years); values outside answer 400.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/settings/trunk-max-age#

GET /admin/settings/trunk-max-age#

Read: settings/trunk-max-age

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission. The trunk TTL in days (ADR-0004), default 90; 0 disables the cleanup.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/settings/trunk-max-age#

Create or run: settings/trunk-max-age

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission. Body: {"days": n}, at most 36500 (~100 years); larger values answer 400.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/instance-settings#

GET /admin/instance-settings#

Read: instance-settings

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The harmless instance settings as values, for the admin page to show to someone who may see but not change them: auto_approve_users, snapshot_max_age_days, dependency_max_age_days, trunk_max_age_days, ticket_url_template and cache (enabled, memory_limit_mb, memory_used_mb, entry_count, hit_count, miss_count, hit_rate). Changing each stays behind manage_settings; the auth configuration, the webhook allow-list and the database are not included.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody

/admin/ticket-url-template#

GET /admin/ticket-url-template#

Read: ticket-url-template

Authentication: session token · Permission: any signed-in caller · Scope: global

Any signed-in caller.

The ticket URL template ({"template": "https://jira.acme.corp/browse/{id}"}, or null): the pages render the ticket ids of a version's origin as links with it. Set under /admin/settings/ticket-url-template.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody

/admin/settings/ticket-url-template#

POST /admin/settings/ticket-url-template#

Create or run: settings/ticket-url-template

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Set the ticket URL template: an absolute http(s) URL containing {id}, at most 2048 characters; a blank or missing template clears it. Audited as SET_TICKET_URL_TEMPLATE.

Responses

StatusDescriptionBody
200

Success.

—
400

Not an absolute http(s) URL with {id}.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/settings/snapshot-max-age#

GET /admin/settings/snapshot-max-age#

Read: settings/snapshot-max-age

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/settings/snapshot-max-age#

Create or run: settings/snapshot-max-age

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Days a snapshot survives without being provided or required. 0 disables the cleanup. At most 36500 (~100 years); larger values answer 400.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/webhooks#

GET /admin/webhooks#

Read: webhooks/webhooks

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

The hooks of this scope, newest last, without their secrets (secret_hint is a prefix of the secret's SHA-256).

Responses

StatusDescriptionBody
200

The hooks.

WebhookList

Example 200 response (placeholder values derived from the schema)

[
  {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  }
]
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/webhooks#

Create or run: webhooks/webhooks

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Register a hook (WebhookCreate). The URL must be https://, or http:// to a host the allow-list names with http://; callers without manage_settings are refused outside the allow-list (and with an empty allow-list), callers with it are exempt for good. The answer carries the signing secret once.

Responses

StatusDescriptionBody
201

Registered; the secret is shown once.

WebhookCreated

Example 201 response (placeholder values derived from the schema)

{
  "webhook": {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  },
  "secret": "string"
}
400

Not a valid hook (URL, format, event type or glob).

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/webhooks/{id}#

GET /admin/webhooks/{id}#

Read: webhooks/webhooks/id

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

The hook.

Webhook

Example 200 response (placeholder values derived from the schema)

{
  "id": 1,
  "scope": {
    "kind": "global",
    "name": "string"
  },
  "url": "string",
  "format": "json",
  "event_types": [
    "string"
  ],
  "producer_glob": "string",
  "secret_hint": "string",
  "paused": false,
  "allow_list_exempt": false,
  "created_by": "string",
  "created_at": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

PATCH /admin/webhooks/{id}#

Update: webhooks/webhooks/id

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Pause or resume (paused), replace the event-type filter (event_types, [] = every type) or the Producer glob (producer_glob, null clears it). Fields left out stay as they are.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

The hook after the patch.

Webhook

Example 200 response (placeholder values derived from the schema)

{
  "id": 1,
  "scope": {
    "kind": "global",
    "name": "string"
  },
  "url": "string",
  "format": "json",
  "event_types": [
    "string"
  ],
  "producer_glob": "string",
  "secret_hint": "string",
  "paused": false,
  "allow_list_exempt": false,
  "created_by": "string",
  "created_at": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

DELETE /admin/webhooks/{id}#

Delete: webhooks/webhooks/id

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Deletes the hook and its delivery log.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
204

Deleted.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/webhooks/{id}/test#

POST /admin/webhooks/{id}/test#

Create or run: webhooks/webhooks/id/test

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Queue a webhook.test event for this hook alone; the worker delivers it like any other event, so the delivery log shows whether the receiver, the secret and the allow-list are in order.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
202

Queued; event_id names the event the delivery log will show.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/webhooks/{id}/rotate-secret#

POST /admin/webhooks/{id}/rotate-secret#

Create or run: webhooks/webhooks/id/rotate-secret

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

A fresh secret, shown once; deliveries from now on are signed with it.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

The hook and its new secret.

WebhookCreated

Example 200 response (placeholder values derived from the schema)

{
  "webhook": {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  },
  "secret": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/webhooks/{id}/deliveries#

GET /admin/webhooks/{id}/deliveries#

Read: webhooks/webhooks/id/deliveries

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

The newest deliveries of the hook (limit, default 50, at most 200): status, attempts, next attempt, last response code and error.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

The delivery log.

WebhookDeliveryList

Example 200 response (placeholder values derived from the schema)

[
  {
    "id": 1,
    "hook_id": 1,
    "event_id": "string",
    "event_type": "string",
    "occurred_at": "string",
    "status": "pending",
    "attempts": 0,
    "next_attempt_at": "string",
    "last_response_code": 0,
    "last_error": "string",
    "delivered_at": "string",
    "updated_at": "string"
  }
]
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/webhooks/{id}/deliveries/{delivery_id}/redeliver#

POST /admin/webhooks/{id}/deliveries/{delivery_id}/redeliver#

Create or run: webhooks/webhooks/id/deliveries/delivery_id/redeliver

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Reset one delivery to pending, due now, attempt count kept — one more attempt even for a dead delivery.

Parameters

NameInRequiredTypeDescription
idpathyesinteger
delivery_idpathyesinteger

Responses

StatusDescriptionBody
202

Reset; the delivery as it now is.

WebhookDelivery

Example 202 response (placeholder values derived from the schema)

{
  "id": 1,
  "hook_id": 1,
  "event_id": "string",
  "event_type": "string",
  "occurred_at": "string",
  "status": "pending",
  "attempts": 0,
  "next_attempt_at": "string",
  "last_response_code": 0,
  "last_error": "string",
  "delivered_at": "string",
  "updated_at": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such delivery on this hook.

—

/admin/producers/{name}/webhooks#

GET /admin/producers/{name}/webhooks#

Read: producers/name/webhooks

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

The hooks of this scope, newest last, without their secrets (secret_hint is a prefix of the secret's SHA-256).

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

The hooks.

WebhookList

Example 200 response (placeholder values derived from the schema)

[
  {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  }
]
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/producers/{name}/webhooks#

Create or run: producers/name/webhooks

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

Register a hook (WebhookCreate). The URL must be https://, or http:// to a host the allow-list names with http://; callers without manage_settings are refused outside the allow-list (and with an empty allow-list), callers with it are exempt for good. The answer carries the signing secret once.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
201

Registered; the secret is shown once.

WebhookCreated

Example 201 response (placeholder values derived from the schema)

{
  "webhook": {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  },
  "secret": "string"
}
400

Not a valid hook (URL, format, event type or glob).

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/producers/{name}/webhooks/{id}#

GET /admin/producers/{name}/webhooks/{id}#

Read: producers/name/webhooks/id

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
200

The hook.

Webhook

Example 200 response (placeholder values derived from the schema)

{
  "id": 1,
  "scope": {
    "kind": "global",
    "name": "string"
  },
  "url": "string",
  "format": "json",
  "event_types": [
    "string"
  ],
  "producer_glob": "string",
  "secret_hint": "string",
  "paused": false,
  "allow_list_exempt": false,
  "created_by": "string",
  "created_at": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

PATCH /admin/producers/{name}/webhooks/{id}#

Update: producers/name/webhooks/id

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

Pause or resume (paused), replace the event-type filter (event_types, [] = every type) or the Producer glob (producer_glob, null clears it). Fields left out stay as they are.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
200

The hook after the patch.

Webhook

Example 200 response (placeholder values derived from the schema)

{
  "id": 1,
  "scope": {
    "kind": "global",
    "name": "string"
  },
  "url": "string",
  "format": "json",
  "event_types": [
    "string"
  ],
  "producer_glob": "string",
  "secret_hint": "string",
  "paused": false,
  "allow_list_exempt": false,
  "created_by": "string",
  "created_at": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

DELETE /admin/producers/{name}/webhooks/{id}#

Delete: producers/name/webhooks/id

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

Deletes the hook and its delivery log.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
204

Deleted.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/producers/{name}/webhooks/{id}/test#

POST /admin/producers/{name}/webhooks/{id}/test#

Create or run: producers/name/webhooks/id/test

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

Queue a webhook.test event for this hook alone; the worker delivers it like any other event, so the delivery log shows whether the receiver, the secret and the allow-list are in order.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
202

Queued; event_id names the event the delivery log will show.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/producers/{name}/webhooks/{id}/rotate-secret#

POST /admin/producers/{name}/webhooks/{id}/rotate-secret#

Create or run: producers/name/webhooks/id/rotate-secret

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

A fresh secret, shown once; deliveries from now on are signed with it.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
200

The hook and its new secret.

WebhookCreated

Example 200 response (placeholder values derived from the schema)

{
  "webhook": {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  },
  "secret": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/producers/{name}/webhooks/{id}/deliveries#

GET /admin/producers/{name}/webhooks/{id}/deliveries#

Read: producers/name/webhooks/id/deliveries

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

The newest deliveries of the hook (limit, default 50, at most 200): status, attempts, next attempt, last response code and error.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
200

The delivery log.

WebhookDeliveryList

Example 200 response (placeholder values derived from the schema)

[
  {
    "id": 1,
    "hook_id": 1,
    "event_id": "string",
    "event_type": "string",
    "occurred_at": "string",
    "status": "pending",
    "attempts": 0,
    "next_attempt_at": "string",
    "last_response_code": 0,
    "last_error": "string",
    "delivered_at": "string",
    "updated_at": "string"
  }
]
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/producers/{name}/webhooks/{id}/deliveries/{delivery_id}/redeliver#

POST /admin/producers/{name}/webhooks/{id}/deliveries/{delivery_id}/redeliver#

Create or run: producers/name/webhooks/id/deliveries/delivery_id/redeliver

Authentication: session token · Permission: manage_producers · Scope: per Producer: also its maintainers

Requires the manage_producers permission, or maintainership of the Producer named in the path.

Reset one delivery to pending, due now, attempt count kept — one more attempt even for a dead delivery.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger
delivery_idpathyesinteger

Responses

StatusDescriptionBody
202

Reset; the delivery as it now is.

WebhookDelivery

Example 202 response (placeholder values derived from the schema)

{
  "id": 1,
  "hook_id": 1,
  "event_id": "string",
  "event_type": "string",
  "occurred_at": "string",
  "status": "pending",
  "attempts": 0,
  "next_attempt_at": "string",
  "last_response_code": 0,
  "last_error": "string",
  "delivered_at": "string",
  "updated_at": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such delivery on this hook.

—

/admin/consumers/{name}/webhooks#

GET /admin/consumers/{name}/webhooks#

Read: consumers/name/webhooks

Authentication: session token · Permission: manage_producers · Scope: per Consumer: also maintainers of the Producer of the same name

Requires the manage_producers permission, or maintainership of the Producer of the same name as the Consumer in the path (the "same system" rule).

The hooks of this scope, newest last, without their secrets (secret_hint is a prefix of the secret's SHA-256).

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
200

The hooks.

WebhookList

Example 200 response (placeholder values derived from the schema)

[
  {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  }
]
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/consumers/{name}/webhooks#

Create or run: consumers/name/webhooks

Authentication: session token · Permission: manage_producers · Scope: per Consumer: also maintainers of the Producer of the same name

Requires the manage_producers permission, or maintainership of the Producer of the same name as the Consumer in the path (the "same system" rule).

Register a hook (WebhookCreate). The URL must be https://, or http:// to a host the allow-list names with http://; callers without manage_settings are refused outside the allow-list (and with an empty allow-list), callers with it are exempt for good. The answer carries the signing secret once.

Parameters

NameInRequiredTypeDescription
namepathyesstring

Responses

StatusDescriptionBody
201

Registered; the secret is shown once.

WebhookCreated

Example 201 response (placeholder values derived from the schema)

{
  "webhook": {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  },
  "secret": "string"
}
400

Not a valid hook (URL, format, event type or glob).

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/consumers/{name}/webhooks/{id}#

GET /admin/consumers/{name}/webhooks/{id}#

Read: consumers/name/webhooks/id

Authentication: session token · Permission: manage_producers · Scope: per Consumer: also maintainers of the Producer of the same name

Requires the manage_producers permission, or maintainership of the Producer of the same name as the Consumer in the path (the "same system" rule).

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
200

The hook.

Webhook

Example 200 response (placeholder values derived from the schema)

{
  "id": 1,
  "scope": {
    "kind": "global",
    "name": "string"
  },
  "url": "string",
  "format": "json",
  "event_types": [
    "string"
  ],
  "producer_glob": "string",
  "secret_hint": "string",
  "paused": false,
  "allow_list_exempt": false,
  "created_by": "string",
  "created_at": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

PATCH /admin/consumers/{name}/webhooks/{id}#

Update: consumers/name/webhooks/id

Authentication: session token · Permission: manage_producers · Scope: per Consumer: also maintainers of the Producer of the same name

Requires the manage_producers permission, or maintainership of the Producer of the same name as the Consumer in the path (the "same system" rule).

Pause or resume (paused), replace the event-type filter (event_types, [] = every type) or the Producer glob (producer_glob, null clears it). Fields left out stay as they are.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
200

The hook after the patch.

Webhook

Example 200 response (placeholder values derived from the schema)

{
  "id": 1,
  "scope": {
    "kind": "global",
    "name": "string"
  },
  "url": "string",
  "format": "json",
  "event_types": [
    "string"
  ],
  "producer_glob": "string",
  "secret_hint": "string",
  "paused": false,
  "allow_list_exempt": false,
  "created_by": "string",
  "created_at": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

DELETE /admin/consumers/{name}/webhooks/{id}#

Delete: consumers/name/webhooks/id

Authentication: session token · Permission: manage_producers · Scope: per Consumer: also maintainers of the Producer of the same name

Requires the manage_producers permission, or maintainership of the Producer of the same name as the Consumer in the path (the "same system" rule).

Deletes the hook and its delivery log.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
204

Deleted.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/consumers/{name}/webhooks/{id}/test#

POST /admin/consumers/{name}/webhooks/{id}/test#

Create or run: consumers/name/webhooks/id/test

Authentication: session token · Permission: manage_producers · Scope: per Consumer: also maintainers of the Producer of the same name

Requires the manage_producers permission, or maintainership of the Producer of the same name as the Consumer in the path (the "same system" rule).

Queue a webhook.test event for this hook alone; the worker delivers it like any other event, so the delivery log shows whether the receiver, the secret and the allow-list are in order.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
202

Queued; event_id names the event the delivery log will show.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/consumers/{name}/webhooks/{id}/rotate-secret#

POST /admin/consumers/{name}/webhooks/{id}/rotate-secret#

Create or run: consumers/name/webhooks/id/rotate-secret

Authentication: session token · Permission: manage_producers · Scope: per Consumer: also maintainers of the Producer of the same name

Requires the manage_producers permission, or maintainership of the Producer of the same name as the Consumer in the path (the "same system" rule).

A fresh secret, shown once; deliveries from now on are signed with it.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
200

The hook and its new secret.

WebhookCreated

Example 200 response (placeholder values derived from the schema)

{
  "webhook": {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  },
  "secret": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/consumers/{name}/webhooks/{id}/deliveries#

GET /admin/consumers/{name}/webhooks/{id}/deliveries#

Read: consumers/name/webhooks/id/deliveries

Authentication: session token · Permission: manage_producers · Scope: per Consumer: also maintainers of the Producer of the same name

Requires the manage_producers permission, or maintainership of the Producer of the same name as the Consumer in the path (the "same system" rule).

The newest deliveries of the hook (limit, default 50, at most 200): status, attempts, next attempt, last response code and error.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger

Responses

StatusDescriptionBody
200

The delivery log.

WebhookDeliveryList

Example 200 response (placeholder values derived from the schema)

[
  {
    "id": 1,
    "hook_id": 1,
    "event_id": "string",
    "event_type": "string",
    "occurred_at": "string",
    "status": "pending",
    "attempts": 0,
    "next_attempt_at": "string",
    "last_response_code": 0,
    "last_error": "string",
    "delivered_at": "string",
    "updated_at": "string"
  }
]
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such hook in this scope.

—

/admin/consumers/{name}/webhooks/{id}/deliveries/{delivery_id}/redeliver#

POST /admin/consumers/{name}/webhooks/{id}/deliveries/{delivery_id}/redeliver#

Create or run: consumers/name/webhooks/id/deliveries/delivery_id/redeliver

Authentication: session token · Permission: manage_producers · Scope: per Consumer: also maintainers of the Producer of the same name

Requires the manage_producers permission, or maintainership of the Producer of the same name as the Consumer in the path (the "same system" rule).

Reset one delivery to pending, due now, attempt count kept — one more attempt even for a dead delivery.

Parameters

NameInRequiredTypeDescription
namepathyesstring
idpathyesinteger
delivery_idpathyesinteger

Responses

StatusDescriptionBody
202

Reset; the delivery as it now is.

WebhookDelivery

Example 202 response (placeholder values derived from the schema)

{
  "id": 1,
  "hook_id": 1,
  "event_id": "string",
  "event_type": "string",
  "occurred_at": "string",
  "status": "pending",
  "attempts": 0,
  "next_attempt_at": "string",
  "last_response_code": 0,
  "last_error": "string",
  "delivered_at": "string",
  "updated_at": "string"
}
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such delivery on this hook.

—

/admin/settings/webhook-allowed-hosts#

GET /admin/settings/webhook-allowed-hosts#

Read: settings/webhook-allowed-hosts

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

The egress allow-list: host patterns a hook URL may point at.

Responses

StatusDescriptionBody
200

The list, as {"hosts": [...]}.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/settings/webhook-allowed-hosts#

Create or run: settings/webhook-allowed-hosts

Authentication: session token · Permission: manage_settings · Scope: global

Requires the manage_settings permission.

Replace the allow-list ({"hosts": [...]}). A pattern is a host name with */? wildcards (hooks.slack.com, *.webhook.office.com); prefixed http:// it also admits plain HTTP to that host (http://n8n.corp.internal). Ports are not matched. An empty list means only callers with manage_settings may register hooks. Evaluated at registration and again at every delivery.

Responses

StatusDescriptionBody
200

The list as stored (trimmed, lower-cased, de-duplicated).

—
400

A pattern is not a host pattern.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/users#

GET /admin/users#

Read: users

Authentication: session token · Permission: manage_users · Scope: global

Requires the manage_users permission.

Each user carries display_name (null shows the username) and kind: service for a service account, otherwise how the person last signed in (local, ldap, oidc), or unknown before their first sign-in since 2.11.

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/users#

Create or run: users

Authentication: session token · Permission: manage_users · Scope: global

Requires the manage_users permission.

Creates a service account ({"username": "jenkins"}): no password, approved at once, never signs in. Holders of manage_users issue its API tokens on the token page (for_user), see them all with who issued each, and may revoke any of them. Its rights come from the roles granted to it like to any user.

Responses

StatusDescriptionBody
201

Created.

—
400

The name is empty or contains spaces.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
409

The name is taken or reserved for root.

ErrorBody

/admin/users/{id}#

DELETE /admin/users/{id}#

Delete: users/id

Authentication: session token · Permission: manage_users · Scope: global

Requires the manage_users permission. Deleting a user who holds an admin-guarded role (admin, releaser) — directly or via a stored group — strips those roles at once, and deleting a configured root account un-claims the reserved name, so both additionally require the caller to be an admin.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/users/{id}/display-name#

PUT /admin/users/{id}/display-name#

Update: users/id/display-name

Authentication: session token · Permission: manage_users · Scope: global

Requires the manage_users permission.

Sets the display name of a service account ({"display_name": "Penguin CI build"}); blank clears it. A person's display name comes from the directory or the OIDC provider, or they set it themselves.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
400

Not a service account, or the name is longer than 100 characters.

ErrorBody
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody
404

No such user.

ErrorBody

/admin/users/{id}/approve#

POST /admin/users/{id}/approve#

Create or run: users/id/approve

Authentication: session token · Permission: manage_users · Scope: global

Requires the manage_users permission.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/users/{id}/maintains#

GET /admin/users/{id}/maintains#

Read: users/id/maintains

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/users/{id}/roles#

GET /admin/users/{id}/roles#

Read: users/id/roles

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

POST /admin/users/{id}/roles#

Create or run: users/id/roles

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission. Granting an admin-guarded role (admin, releaser) additionally requires the caller to be an admin.

Parameters

NameInRequiredTypeDescription
idpathyesinteger

Responses

StatusDescriptionBody
201

Created. The role is granted; the body is empty.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

/admin/users/{id}/roles/{role}#

DELETE /admin/users/{id}/roles/{role}#

Delete: users/id/roles/role

Authentication: session token · Permission: manage_roles · Scope: global

Requires the manage_roles permission. Revoking an admin-guarded role (admin, releaser) additionally requires the caller to be an admin.

Parameters

NameInRequiredTypeDescription
idpathyesinteger
rolepathyesstring

Responses

StatusDescriptionBody
200

Success.

—
401

Not signed in.

ErrorBody
403

Signed in, but without what this endpoint requires.

ErrorBody

Schemas#

SystemContext#

What a system's Backstage catalog-info.yaml says about it (#48), read from the Component named like the system: spec.owner, metadata.description, the backstage.io/source-location annotation and metadata.links. One per system, replaced on every catalog push; a field the file does not have is null or empty.

FieldTypeRequiredDescription
ownerobject | nullyes

The owner ref's kind (group unless the ref says otherwise) and name, lower-cased.

owner.kindstringyes
owner.namestringyes
descriptionstring | nullyes

Trimmed, at most 1000 characters (a longer one is cut with an ellipsis).

source_locationstring | nullyes

The annotation without its url: prefix; only an http or https URL is kept.

linksobject[]yes

At most 20; a link whose url is not http or https is dropped.

links[].urlstringyes
links[].titlestringyes

The link's title, or the URL's host when it has none.

links[].typestring | nullyes
links[].iconstring | nullyes

Example (placeholder values derived from the schema)

{
  "owner": {
    "kind": "string",
    "name": "string"
  },
  "description": "string",
  "source_location": "string",
  "links": [
    {
      "url": "string",
      "title": "string",
      "type": "string",
      "icon": "string"
    }
  ]
}

ParticipantContext#

FieldTypeRequiredDescription
namestringyes
contextSystemContextyes

What a system's Backstage catalog-info.yaml says about it (#48), read from the Component named like the system: spec.owner, metadata.description, the backstage.io/source-location annotation and metadata.links. One per system, replaced on every catalog push; a field the file does not have is null or empty.

Example (placeholder values derived from the schema)

{
  "name": "string",
  "context": {
    "owner": {
      "kind": "string",
      "name": "string"
    },
    "description": "string",
    "source_location": "string",
    "links": [
      {
        "url": "string",
        "title": "string",
        "type": "string",
        "icon": "string"
      }
    ]
  }
}

WebhookScope#

Who a hook belongs to (#47).

FieldTypeRequiredDescription
kindstringyes

One of global, producer, consumer.

namestring—

The Producer or Consumer; absent for global.

Example (placeholder values derived from the schema)

{
  "kind": "global",
  "name": "string"
}

Webhook#

A registered hook (#47). The signing secret is never part of it; secret_hint is the first 8 hex characters of the secret's SHA-256, to tell hooks apart.

FieldTypeRequiredDescription
idinteger (int64)yes
scopeWebhookScopeyes

Who a hook belongs to (#47).

urlstringyes
formatstringyes

One of json, slack, teams.

event_typesstring[]yes

The event types the hook receives; empty means every type.

producer_globstring | nullyes

Only events about Producers whose name matches (* and ?); null means every Producer.

secret_hintstringyes
pausedbooleanyes
allow_list_exemptbooleanyes

Registered by a caller with manage_settings; the allow-list does not apply.

created_bystringyes
created_atstringyes

Example (placeholder values derived from the schema)

{
  "id": 1,
  "scope": {
    "kind": "global",
    "name": "string"
  },
  "url": "string",
  "format": "json",
  "event_types": [
    "string"
  ],
  "producer_glob": "string",
  "secret_hint": "string",
  "paused": false,
  "allow_list_exempt": false,
  "created_by": "string",
  "created_at": "string"
}

WebhookList#

Type: Webhook[].

WebhookCreate#

FieldTypeRequiredDescription
urlstringyes
formatstring—

One of json, slack, teams.

Default "json".

event_typesstring[]—

Empty or absent means every type; webhook.test cannot be subscribed to.

producer_globstring | null—

Example (placeholder values derived from the schema)

{
  "url": "string",
  "format": "json",
  "event_types": [
    "string"
  ],
  "producer_glob": "string"
}

WebhookCreated#

FieldTypeRequiredDescription
webhookWebhookyes

A registered hook (#47). The signing secret is never part of it; secret_hint is the first 8 hex characters of the secret's SHA-256, to tell hooks apart.

secretstringyes

The signing secret, shown this once (whsec_ + 64 hex characters).

Example (placeholder values derived from the schema)

{
  "webhook": {
    "id": 1,
    "scope": {
      "kind": "global",
      "name": "string"
    },
    "url": "string",
    "format": "json",
    "event_types": [
      "string"
    ],
    "producer_glob": "string",
    "secret_hint": "string",
    "paused": false,
    "allow_list_exempt": false,
    "created_by": "string",
    "created_at": "string"
  },
  "secret": "string"
}

WebhookDelivery#

One hook's delivery of one event — a delivery log row (#47).

FieldTypeRequiredDescription
idinteger (int64)yes
hook_idinteger (int64)yes
event_idstringyes
event_typestringyes
occurred_atstringyes
statusstringyes

One of pending, delivered, failed, dead.

attemptsintegeryes
next_attempt_atstringyes
last_response_codeinteger | nullyes
last_errorstring | nullyes
delivered_atstring | nullyes
updated_atstringyes

Example (placeholder values derived from the schema)

{
  "id": 1,
  "hook_id": 1,
  "event_id": "string",
  "event_type": "string",
  "occurred_at": "string",
  "status": "pending",
  "attempts": 0,
  "next_attempt_at": "string",
  "last_response_code": 0,
  "last_error": "string",
  "delivered_at": "string",
  "updated_at": "string"
}

WebhookDeliveryList#

Type: WebhookDelivery[].

ErrorBody#

The body every error response carries. Identical to the schema of the same name in api.yaml.

FieldTypeRequiredDescription
errorstringyes
proposed_versionstring—

Example (placeholder values derived from the schema)

{
  "error": "string",
  "proposed_version": "string"
}

ConnectionTestFailure#

The 400 of POST /admin/auth-config/test: an ErrorBody whose diagnostics is present only when the directory was unreachable.

FieldTypeRequiredDescription
errorstringyes
diagnosticsstring[]—

One line per finding, in reading order.

Example (placeholder values derived from the schema)

{
  "error": "LDAP connection failed: Could not connect to LDAP server at ldaps://ldap.example.com:636: this container has no route to 10.1.2.3: check the network, VPN or Docker network of the pod",
  "diagnostics": [
    "DNS: ldap.example.com resolves to 10.1.2.3 (1.2 ms)",
    "Resolvers: 10.96.0.10 (search default.svc.cluster.local, svc.cluster.local, cluster.local)",
    "10.1.2.3: ping no route; route no route from this container; tcp/636 no route"
  ]
}