Admin
Reference for the instance-administration endpoints under /api/v1/admin: user
accounts, user groups, instance settings, delegated admin-scope grants, the
instance audit log, system health, CI runners and runner hosts, and rate-limit
administration. Every endpoint on this page requires an instance administrator.
Resource overview
Administration endpoints hang off /api/v1/admin/.... This page covers:
- users — list, fetch, create, update, and delete accounts, plus password reset, token revocation, repository-ownership listing, user impersonation, and the registration-approval queue;
- user groups — the seeded and custom groups that carry a default role and rate-limit tier, and their membership;
- settings — the instance configuration key/value store;
- admin-scope grants — delegating individual admin capabilities to non-super-admin operators;
- audit log — the append-only record of administrative and state-changing actions;
- system health and logs — a point-in-time snapshot of database, disk, runner, and queue health, plus listing and rotating the server log files;
- CI runners and runner hosts — registering and managing CI runners and the CICD runner-host pool;
- rate-limit administration — viewing and adjusting the per-group budgets and the per-repository, per-application, per-token, and per-user overrides.
Repository transfer endpoints also live under /api/v1/admin
(/api/v1/admin/repos/{owner}/{repo}/transfer and /api/v1/admin/transfers).
They are documented with the repository resource on
repos.md. The
budgets and behavior of the rate limiter itself are described in
rate-limits.md; this page documents only the endpoints that
read and change those limits.
Auth & scopes
See conventions.md for the accepted credential types. Access to these endpoints is gated two ways, in order:
- The request must be authenticated. An unauthenticated request is answered
401 Unauthorized. - The caller must be an instance administrator. A user holding the super-admin
flag (
is_admin) passes every check. A non-super-admin must hold the specific admin scope the endpoint requires, delegated through an admin-scope grant; otherwise the response is403 Forbiddenwith the body{ "error": "admin scope required: <scope>" }.
The admin scopes used on this page:
| Scope | Gates |
|---|---|
admin:users |
User accounts and user groups |
admin:impersonation |
Impersonating a user (in addition to admin:users) |
admin:settings |
Instance settings |
admin:system |
System health and log management |
admin:roles |
Admin-scope grants (delegation) |
admin:runners |
CI runners and runner hosts |
admin:rate_limit |
Rate-limit configuration and overrides |
admin:audit_read |
Audit-log access-drop counter on system health |
The impersonation endpoints compose two scopes: a non-super-admin caller must
hold both admin:users and admin:impersonation. A super-admin satisfies
both. When a scope is missing the response is 403 Forbidden naming the first
missing scope.
The instance audit log itself (/api/v1/admin/audit-log) requires the
super-admin flag rather than a delegatable scope. Granting admin:roles to
another user is restricted to a super-admin, so a scoped administrator cannot
bootstrap their own privileges.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET |
/api/v1/admin/users |
List users (paginated) |
POST |
/api/v1/admin/users |
Create a user |
GET |
/api/v1/admin/users/{uid} |
Fetch a user |
PATCH |
/api/v1/admin/users/{uid} |
Update a user |
DELETE |
/api/v1/admin/users/{uid} |
Delete a user |
POST |
/api/v1/admin/users/{uid}/password |
Set a user's password |
POST |
/api/v1/admin/users/{uid}/revoke-tokens |
Revoke a user's tokens |
GET |
/api/v1/admin/users/pending |
List accounts awaiting approval |
POST |
/api/v1/admin/users/{uid}/approve |
Approve a pending account |
POST |
/api/v1/admin/users/{uid}/reject |
Reject a pending account |
GET |
/api/v1/admin/users/{uid}/repos |
List a user's repositories |
POST |
/api/v1/admin/users/{uid}/impersonate |
Start impersonating a user |
DELETE |
/api/v1/admin/users/{uid}/impersonate |
Stop impersonating a user |
GET |
/api/v1/admin/user-groups |
List user groups |
POST |
/api/v1/admin/user-groups |
Create a user group |
GET |
/api/v1/admin/user-groups/{id} |
Fetch a user group |
PATCH |
/api/v1/admin/user-groups/{id} |
Update a user group |
DELETE |
/api/v1/admin/user-groups/{id} |
Delete a user group |
GET |
/api/v1/admin/user-groups/{id}/members |
List group members |
POST |
/api/v1/admin/user-groups/{id}/members |
Add a group member |
DELETE |
/api/v1/admin/user-groups/{id}/members/{uid} |
Remove a group member |
GET |
/api/v1/admin/settings |
List all settings |
GET |
/api/v1/admin/settings/{key} |
Read one setting |
PUT |
/api/v1/admin/settings/{key} |
Write one setting |
POST |
/api/v1/admin/settings |
Write several settings at once |
GET |
/api/v1/admin/admin-scopes |
List grants for one scope (paginated) |
GET |
/api/v1/admin/users/{uid}/admin-scopes |
List a user's scope grants |
POST |
/api/v1/admin/users/{uid}/admin-scopes |
Grant a scope to a user |
DELETE |
/api/v1/admin/users/{uid}/admin-scopes/{scope} |
Revoke a scope |
GET |
/api/v1/admin/audit-log |
List audit-log entries (paginated) |
GET |
/api/v1/admin/audit-log/{id} |
Fetch one audit-log entry |
GET |
/api/v1/admin/health |
System health snapshot |
GET |
/api/v1/admin/logs |
List server log files |
POST |
/api/v1/admin/logs/rotate |
Rotate server log files |
GET |
/api/v1/admin/runners |
List CI runners |
POST |
/api/v1/admin/runners |
Register a CI runner |
DELETE |
/api/v1/admin/runners/{id} |
Delete a CI runner |
POST |
/api/v1/admin/runners/{id}/rotate-token |
Rotate a runner's token |
GET |
/api/v1/admin/hosts |
List runner hosts (paginated) |
GET |
/api/v1/admin/hosts/{id} |
Fetch one runner host |
GET |
/api/v1/admin/rate-limits |
Read the per-group budget matrix |
PATCH |
/api/v1/admin/rate-limits/groups/{group_id}/{scope} |
Update one group/scope budget |
Users
GET /api/v1/admin/users
List user accounts, paginated. Requires admin:users.
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
q |
string | — | Filter by username, email, or display name. |
page |
integer | 1 |
Page number. Translated to limit/offset internally. |
per_page |
integer | 50 |
Page size. |
limit |
integer | — | Alternative to per_page; explicit limit/offset take precedence when supplied. |
offset |
integer | — | Zero-based row offset. |
Response
{
"users": [
{
"id": "<uuid>",
"username": "alice",
"email": "alice@example.com",
"display_name": "Alice",
"is_admin": false,
"status": "active",
"created_at": "2026-01-04T12:00:00Z",
"last_active_at": "2026-02-19T09:31:00Z"
}
],
"total": 137,
"limit": 50,
"offset": 0
}
status is active or suspended. last_active_at is null for an account
that has never been updated since creation.
Status codes
| Status | When |
|---|---|
200 OK |
Page returned. |
401 Unauthorized |
No valid credential. |
403 Forbidden |
Caller is not an administrator and lacks admin:users. |
Example
curl -H "Authorization: Bearer <token>" \
"https://api.gitvetrix.com/api/v1/admin/users?q=alice&page=1&per_page=50"
POST /api/v1/admin/users
Create a user account. Requires admin:users.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
username |
string | yes | Account username. |
email |
string | yes | Account email. |
password |
string | yes | Initial password. |
display_name |
string | no | Display name. |
is_admin |
boolean | no | Grant the super-admin flag. Defaults to false. |
{
"username": "bob",
"email": "bob@example.com",
"password": "...",
"display_name": "Bob",
"is_admin": false
}
Response
201 Created with the user record (the same shape as the fetch response below).
Status codes
| Status | When |
|---|---|
201 Created |
User created. |
400 Bad Request |
Invalid body, or username, email, or password is missing. |
403 Forbidden |
Caller lacks admin:users. |
Example
curl -X POST -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"username":"bob","email":"bob@example.com","password":"..."}' \
https://api.gitvetrix.com/api/v1/admin/users
GET /api/v1/admin/users/{uid}
Fetch one user account. Requires admin:users.
Path parameters
| Name | Type | Description |
|---|---|---|
uid |
string | User UUID. |
Response
{
"id": "<uuid>",
"username": "alice",
"email": "alice@example.com",
"display_name": "Alice",
"is_admin": false,
"status": "active",
"created_at": "2026-01-04T12:00:00Z",
"last_active_at": "2026-02-19T09:31:00Z"
}
Status codes
| Status | When |
|---|---|
200 OK |
User returned. |
400 Bad Request |
uid is not a valid UUID. |
404 Not Found |
No user with that id. |
Example
curl -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/users/<uuid>
PATCH /api/v1/admin/users/{uid}
Update a user account. Requires admin:users. Every field is optional; only the
fields supplied are changed.
Request body
| Field | Type | Description |
|---|---|---|
email |
string | New email. |
display_name |
string | New display name. |
is_admin |
boolean | Set or clear the super-admin flag. |
is_active |
boolean | Activate or suspend the account. |
status |
string | active or suspended; an alternative to is_active. |
{ "status": "suspended" }
Response
200 OK with the updated user record.
Status codes
| Status | When |
|---|---|
200 OK |
User updated. |
400 Bad Request |
Invalid body or a malformed uid. |
404 Not Found |
No user with that id. |
Example
curl -X PATCH -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"status":"suspended"}' \
https://api.gitvetrix.com/api/v1/admin/users/<uuid>
DELETE /api/v1/admin/users/{uid}
Delete a user account. Requires admin:users. The user's outstanding tokens are
revoked first, then the account and its dependent records are removed.
A user who has authored content that other records still reference — issues,
merge requests, comments, reviews, or workflow activity — cannot be deleted
while that content exists. The request is answered 409 Conflict with a reason,
and the administrator must reassign or remove the authored content first.
Path parameters
| Name | Type | Description |
|---|---|---|
uid |
string | User UUID. |
Status codes
| Status | When |
|---|---|
204 No Content |
User deleted. |
400 Bad Request |
uid is not a valid UUID. |
409 Conflict |
The user has authored content that blocks deletion. The body carries error and a reason. |
Example
curl -X DELETE -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/users/<uuid>
Other user operations
These operations are also gated on admin:users:
POST /api/v1/admin/users/{uid}/password— set a user's password. Body{ "password": "..." }. Returns204 No Content;404for an unknown id.POST /api/v1/admin/users/{uid}/revoke-tokens— revoke all of a user's access tokens. Returns204 No Content.GET /api/v1/admin/users/pending— list accounts awaiting approval (paginated withlimit/offset).POST /api/v1/admin/users/{uid}/approveandPOST /api/v1/admin/users/{uid}/reject— act on a pending registration.
GET /api/v1/admin/users/{uid}/repos
List the repositories owned by one user. Requires admin:users.
Path parameters
| Name | Type | Description |
|---|---|---|
uid |
string | User UUID. |
Response
A JSON array, empty when the user owns no repositories.
[
{
"id": "<uuid>",
"name": "backend",
"description": "Service API",
"is_private": true,
"default_branch": "main",
"created_at": "2026-01-04T12:00:00Z",
"updated_at": "2026-02-19T09:31:00Z"
}
]
Status codes
| Status | When |
|---|---|
200 OK |
Repositories returned. |
400 Bad Request |
uid is not a valid UUID. |
403 Forbidden |
Caller lacks admin:users. |
Example
curl -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/users/<uuid>/repos
POST /api/v1/admin/users/{uid}/impersonate
Begin impersonating a user. The response carries an access token that
authenticates as the target user for subsequent API calls. Requires both
admin:users and admin:impersonation (a super-admin satisfies both). The
request also records a server-side impersonation session, scoped to the
acting administrator and the target, which the DELETE below revokes.
This endpoint is exempt from rate limiting: it is an incident-recovery surface, so a misconfigured rate-limit rule can never lock an administrator out of it.
Path parameters
| Name | Type | Description |
|---|---|---|
uid |
string | Target user UUID. |
Response
201 Created. access_token is the token that authenticates as the target;
expires_at is when the impersonation session lapses. The remaining fields are
retained for older clients. The user object is the same shape returned by the
authentication endpoints (login, refresh, /auth/me) — not the richer
status/last_active_at shape returned by GET/LIST /admin/users.
approval_state is omitted when the target account has no recorded approval
state.
{
"access_token": "<jwt>",
"user": {
"id": "<uuid>",
"username": "alice",
"email": "alice@example.com",
"display_name": "Alice",
"bio": "",
"avatar_url": "",
"is_admin": false,
"approval_state": "active",
"created_at": "2026-01-15T09:12:00Z"
},
"token": "imp_...",
"session_id": "<uuid>",
"expires_at": "2026-02-19T13:31:00Z"
}
Status codes
| Status | When |
|---|---|
201 Created |
Impersonation session created; token returned. |
400 Bad Request |
uid is not a valid UUID, or the target is itself an administrator (an administrator cannot be impersonated). |
403 Forbidden |
Caller lacks admin:users or admin:impersonation; or a target gate refused the token (see below). |
404 Not Found |
No user with that id. |
When auth.require_email_verification is enabled and the target's email is
unverified, the request is refused 403 with { "error": "email_not_verified" }.
When signup.require_approval is enabled and the target is not yet active, the
request is refused 403 with { "error": "approval_pending" } or
{ "error": "approval_rejected" } and a message. Both gates exist so that
impersonation cannot route around a policy that would otherwise block the target
from signing in themselves. Each refusal writes an audit entry
(user.login_blocked_email_not_verified / user.login_blocked_approval) with
details.surface set to impersonation; the acting administrator is the audit
actor and the target id is recorded under details.target_user_id.
Example
curl -X POST -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/users/<uuid>/impersonate
DELETE /api/v1/admin/users/{uid}/impersonate
End impersonation by revoking every active impersonation session the calling
administrator holds for the target user. Requires both admin:users and
admin:impersonation, and is exempt from rate limiting for the same
incident-recovery reason as the POST. Idempotent: returns 204 No Content
even when no session was open.
Status codes
| Status | When |
|---|---|
204 No Content |
Sessions revoked (or none were open). |
400 Bad Request |
uid is not a valid UUID. |
403 Forbidden |
Caller lacks admin:users or admin:impersonation. |
Example
curl -X DELETE -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/users/<uuid>/impersonate
User groups
User groups carry a default role and a rate-limit tier. The seeded groups are
anonymous, general, and admin; an operator can create custom groups. All
user-group endpoints require admin:users, and return 503 when the user-group
store is not configured on the deployment.
GET /api/v1/admin/user-groups
List all groups with live member counts. System groups are returned first.
Response
{
"groups": [
{
"id": "<uuid>",
"slug": "general",
"name": "General",
"description": "Authenticated users",
"role": "user",
"is_system": true,
"member_count": 42,
"created_at": "2026-01-04T12:00:00Z",
"updated_at": "2026-01-04T12:00:00Z"
}
],
"total": 3
}
Status codes
| Status | When |
|---|---|
200 OK |
Groups returned. |
403 Forbidden |
Caller lacks admin:users. |
503 Service Unavailable |
User-group store not configured. |
Example
curl -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/user-groups
POST /api/v1/admin/user-groups
Create a custom group. The slug is derived from name.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Group name. Must contain at least one alphanumeric character. |
description |
string | no | Free-text description. |
role |
string | no | Default role for members. |
grant_admin |
boolean | no | Required to be true when role is instance_admin. |
Status codes
| Status | When |
|---|---|
201 Created |
Group created (returns the group record). |
400 Bad Request |
Invalid body, missing name, or a name with no alphanumeric character. |
409 Conflict |
The slug or name is already in use. |
422 Unprocessable Entity |
Invalid role. |
GET /api/v1/admin/user-groups/{id}
Fetch one group with its current member count. 400 for a malformed id,
404 when the group does not exist.
PATCH /api/v1/admin/user-groups/{id}
Update a group's name, description, or role. The slug of a system group is
immutable (409). 404 for an unknown id; 422 for an invalid role.
DELETE /api/v1/admin/user-groups/{id}
Delete a custom group. A system group cannot be deleted (409). Returns
204 No Content on success; 404 for an unknown id.
Group membership
GET /api/v1/admin/user-groups/{id}/members— list a group's members.POST /api/v1/admin/user-groups/{id}/members— add a member.DELETE /api/v1/admin/user-groups/{id}/members/{uid}— remove a member.
Settings
Instance settings are a key/value store. Sensitive values (for example SMTP and
OAuth client secrets) are masked on read, and a write that echoes the mask back
is a no-op so the stored secret survives. Setting reads and writes require
admin:settings.
GET /api/v1/admin/settings
Return every setting as an array of entries.
Response
[
{ "key": "oauth2.dcr.enabled", "value": "false", "description": "Allow dynamic client registration" }
]
Status codes
| Status | When |
|---|---|
200 OK |
Settings returned. |
403 Forbidden |
Caller lacks admin:settings. |
GET /api/v1/admin/settings/{key}
Read one setting. A sensitive key returns a masked placeholder rather than the stored value.
Response
{ "key": "oauth2.dcr.enabled", "value": "false", "description": "Allow dynamic client registration" }
Example
curl -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/settings/oauth2.dcr.enabled
PUT /api/v1/admin/settings/{key}
Write one setting. The value is always a string; coerce booleans and numbers to their string form. The change takes effect immediately — no restart is required.
Path parameters
| Name | Type | Description |
|---|---|---|
key |
string | Setting key. |
Request body
{ "value": "true" }
Response
{ "key": "oauth2.dcr.enabled", "value": "true" }
Status codes
| Status | When |
|---|---|
200 OK |
Setting written (or a no-op when the masked placeholder was echoed back). |
400 Bad Request |
Invalid body, or a value the setting rejects as malformed. |
404 Not Found |
Unknown setting key. |
422 Unprocessable Entity |
Value is out of the setting's allowed bounds. |
Example
curl -X PUT -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"value":"true"}' \
https://api.gitvetrix.com/api/v1/admin/settings/oauth2.dcr.enabled
Batch write
POST /api/v1/admin/settings writes several keys in one request, applying the
same validation and masking rules as the single-key PUT.
POST /api/v1/admin/settings/smtp/test sends a test message with the current
SMTP configuration.
Admin-scope grants
Delegate an individual admin capability to a non-super-admin user. All four
endpoints require admin:roles, and return 503 when the admin-grants store is
not configured on the deployment.
GET /api/v1/admin/admin-scopes
List every grant of one scope — useful for answering "who currently holds
admin:users". This endpoint uses limit/offset pagination (see
Pagination).
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
scope |
string | — | The admin scope to list grants for. Required. |
limit |
integer | 50 |
Page size, clamped to a maximum of 500. |
offset |
integer | 0 |
Zero-based row offset. |
Response
{
"grants": [
{
"user_id": "<uuid>",
"scope": "admin:users",
"granted_by": "<uuid>",
"created_at": "2026-02-19T09:31:00Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
Status codes
| Status | When |
|---|---|
200 OK |
Page returned. |
400 Bad Request |
scope query parameter is missing. |
422 Unprocessable Entity |
scope is not a recognized admin scope. |
503 Service Unavailable |
Admin-grants store not configured. |
Example
curl -H "Authorization: Bearer <token>" \
"https://api.gitvetrix.com/api/v1/admin/admin-scopes?scope=admin:users&limit=50"
GET /api/v1/admin/users/{uid}/admin-scopes
List every scope granted to one user.
Response
{ "grants": [ { "user_id": "<uuid>", "scope": "admin:users", "granted_by": "<uuid>", "created_at": "2026-02-19T09:31:00Z" } ], "total": 1 }
POST /api/v1/admin/users/{uid}/admin-scopes
Grant a scope to a user. Idempotent: re-granting an existing scope returns 200.
Request body
{ "scope": "admin:users" }
Status codes
| Status | When |
|---|---|
200 OK |
Scope granted (or already present). |
400 Bad Request |
Invalid body or a malformed uid. |
403 Forbidden |
Granting admin:roles without the super-admin flag. |
422 Unprocessable Entity |
Unknown scope. |
DELETE /api/v1/admin/users/{uid}/admin-scopes/{scope}
Revoke a scope. Idempotent: returns 204 No Content even when the grant was
already absent. 422 for an unrecognized scope value.
Audit log
The instance audit log is the append-only record of administrative and state-changing actions. Both endpoints require the super-admin flag.
GET /api/v1/admin/audit-log
List audit entries, most recent first, paginated.
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 |
Page number. |
per_page |
integer | 100 |
Page size, clamped to a maximum of 500. |
actor |
string | — | Filter by actor username. An unknown username returns an empty page rather than 404. |
action |
string | — | Filter by action prefix (for example admin.). |
from |
string | — | Lower bound — an RFC 3339 timestamp or a YYYY-MM-DD date. |
to |
string | — | Upper bound (inclusive on a bare date) — same formats as from. |
Legacy limit/offset parameters are also accepted when page is not supplied.
Response
{
"entries": [
{
"id": "4821",
"actor_id": "<uuid>",
"actor_username": "alice",
"action": "admin.settings_changed",
"resource_type": "setting",
"resource_id": "oauth2.dcr.enabled",
"resource": "setting:oauth2.dcr.enabled",
"ip_address": "203.0.113.7",
"user_agent": "curl/8.0",
"details": { "key": "oauth2.dcr.enabled", "old": "false", "new": "true" },
"created_at": "2026-02-19T09:31:00Z"
}
],
"total": 512,
"page": 1,
"per_page": 100
}
id is a string. resource_type and resource_id are derived by splitting the
stored resource on the first :. Secret values inside details are redacted.
Status codes
| Status | When |
|---|---|
200 OK |
Page returned. |
403 Forbidden |
Caller is not a super-admin. |
Example
curl -H "Authorization: Bearer <token>" \
"https://api.gitvetrix.com/api/v1/admin/audit-log?action=admin.&per_page=50"
GET /api/v1/admin/audit-log/{id}
Fetch one audit entry by its numeric id. Returns the same entry shape as the list endpoint.
Status codes
| Status | When |
|---|---|
200 OK |
Entry returned. |
400 Bad Request |
id is not a positive integer. |
403 Forbidden |
Caller is not a super-admin. |
404 Not Found |
No entry with that id. |
System health
GET /api/v1/admin/health
Return a point-in-time health snapshot. Requires admin:system.
Response (abridged)
{
"stats": { "users_count": 137, "repos_count": 88, "ci_jobs_count": 12043, "storage_bytes": 9663676416 },
"db": {
"reachable": true,
"pool_open": 5,
"pool_max": 25,
"conns_by_application": {
"vetrix-server": 5,
"vetrix-worker": 2,
"vetrix-runner-controller": 1
}
},
"disk": { "...": "..." },
"runners": { "...": "..." },
"queue_depth": 0,
"uptime_seconds": 86400
}
db.conns_by_application groups live PostgreSQL connections by the
application_name each pool stamps, so an operator can see the cross-process
idle-connection floor and attribute each connection to the process that holds
it. It is best-effort: it is omitted if the pool is unreachable or the query
errors, and the rest of the snapshot still renders. Connections that set no
application name collapse under the empty-string key.
The optional access_log_drops counter is included only when the caller also
holds admin:audit_read; it is omitted otherwise. Service-probe blocks
(redis, opensearch, rabbitmq, child_zombies) appear only when the
corresponding probe is wired on the deployment.
Status codes
| Status | When |
|---|---|
200 OK |
Snapshot returned. |
403 Forbidden |
Caller lacks admin:system. |
Example
curl -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/health
Log management
The server writes three on-disk log files (access.log, error.log, and
exceptions.log). These endpoints list and rotate those files and require
admin:system. On a deployment where file logging is not configured they
degrade gracefully rather than erroring: the list returns [] and the rotate
returns { "rotated": 0 }.
GET /api/v1/admin/logs
List the server log files with their current size and last-modified time.
Response
A JSON array, one entry per log file.
[
{ "name": "access.log", "size_bytes": 1048576, "modified_at": "2026-02-19T09:31:00Z" },
{ "name": "error.log", "size_bytes": 4096, "modified_at": "2026-02-19T09:30:00Z" }
]
Status codes
| Status | When |
|---|---|
200 OK |
Files returned (or [] when file logging is not configured). |
403 Forbidden |
Caller lacks admin:system. |
500 Internal Server Error |
The log directory could not be read. |
Example
curl -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/logs
POST /api/v1/admin/logs/rotate
Rotate the server log files immediately, returning the number of files rotated.
Response
{ "rotated": 3 }
Status codes
| Status | When |
|---|---|
200 OK |
Rotation completed (rotated is 0 when file logging is not configured). |
403 Forbidden |
Caller lacks admin:system. |
500 Internal Server Error |
Rotation failed; the body carries the reason. |
Example
curl -X POST -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/logs/rotate
CI runners
CI-runner endpoints require admin:runners.
GET /api/v1/admin/runners
List registered CI runners. Returns a JSON array; the runner token hash is never included.
Response
[
{
"id": "<uuid>",
"name": "linux-amd64-1",
"tags": ["linux", "docker"],
"status": "online",
"last_seen_at": "2026-02-19T09:31:00Z",
"registered_at": "2026-01-04T12:00:00Z",
"isolation_kind": "docker",
"ssh_capable": false
}
]
Example
curl -H "Authorization: Bearer <token>" \
https://api.gitvetrix.com/api/v1/admin/runners
POST /api/v1/admin/runners
Register a runner. The raw token is returned once in the response and stored only as a hash thereafter.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Runner name. |
tags |
string[] | no | Scheduling tags. |
Response
{
"raw_token": "vetrix_...",
"token": "vetrix_...",
"runner": { "id": "<uuid>", "name": "linux-amd64-1", "...": "..." }
}
raw_token and token carry the same value (token is a legacy alias).
Status codes
| Status | When |
|---|---|
201 Created |
Runner registered. |
400 Bad Request |
Invalid body or missing name. |
DELETE /api/v1/admin/runners/{id}
Delete a runner. 400 for a malformed id; 204 No Content on success.
POST /api/v1/admin/runners/{id}/rotate-token
Rotate a runner's token, invalidating the previous value atomically. The runner must re-authenticate with the new token on its next heartbeat.
Response
{ "raw_token": "vetrix_..." }
404 when no runner has that id.
Runner hosts
The runner-host endpoints expose the CICD runner-host pool and reuse the
admin:runners scope.
GET /api/v1/admin/hosts— list hosts in the{ items, total, page, per_page }page envelope (see Pagination). Each item carries the host's capacity, in-use count, state, last heartbeat, and a derived healthy flag.GET /api/v1/admin/hosts/{id}— fetch one host, field-for-field identical to a list item.POST /api/v1/admin/hosts/{id}/decommission— remove a drained host from the fleet.POST /api/v1/admin/hosts/{id}/drainandGET /api/v1/admin/hosts/{id}/drain— start draining a host and read drain progress.
Rate-limit administration
These endpoints read and adjust the rate limiter described in
rate-limits.md. They require admin:rate_limit — a scope
deliberately separate from admin:settings so the "raise a limit during an
incident" operator role does not inherit the rest of instance settings. Limits
can only be narrowed by overrides, never widened past the group budget.
GET /api/v1/admin/rate-limits
Read the full per-group budget matrix.
PATCH /api/v1/admin/rate-limits/groups/{group_id}/{scope}
Update one group's budget for one scope. Every field is optional; only the fields supplied are changed.
Request body
| Field | Type | Description |
|---|---|---|
requests_per_window |
integer | Allowed requests per window. |
window_seconds |
integer | Window length in seconds. |
burst |
integer | Burst allowance. |
action |
string | throttle, allow, or disabled. |
enabled |
boolean | Enable or disable the rule. |
notes |
string | Free-form audit note (not persisted on the row). |
Related endpoints
GET /api/v1/admin/rate-limits/groups/{group_id}— read one group's rules.POST /api/v1/admin/rate-limits/groups/{group_id}/{scope}/reset— reset a group/scope rule to its seeded default.- Per-target overrides, each with list / upsert / delete operations under
/api/v1/admin/rate-limits/overrides/{repos,apps,tokens,users}/.... An override row carriesrequests_per_window,window_seconds,burst,action,enabled, and areason. GET /api/v1/admin/rate-limits/violations— the recent-violations feed (.csvfor export).
Errors
These endpoints use the shared error envelope and status-code conventions in errors.md. Two patterns recur on this page:
401vs403. An unauthenticated request is401; an authenticated caller missing the required admin scope is403with{ "error": "admin scope required: <scope>" }.503for unwired features. Endpoints whose backing store is optional (user groups, admin-scope grants) answer503with a message naming the missing component when that store is not configured on the deployment.
Deleting a user that still owns referenced content returns 409 Conflict with a
reason, as described under the delete endpoint above.
Rate limits
These endpoints are metered under the standard scopes in
rate-limits.md: reads under api.read and mutations under
api.write. The administrator group is metered with the allow action, so an
administrator is never throttled out of these endpoints, but requests are still
counted.