Vetrix Docs

API Conventions

Conventions shared by every endpoint in the gitvetrix.com REST API: hosts, authentication, scopes, pagination, the error envelope, and the canonical merge-request paths. Per-resource reference pages assume the behavior described here and link back to it rather than repeating it.

Hosts

Purpose Host
REST API https://api.gitvetrix.com/api/v1
Web UI, sign-in, OAuth2 consent https://www.gitvetrix.com
Local development stack https://api.gitvetrix.test/api/v1

All REST endpoints live under the /api/v1 prefix; the version is part of the path. The OpenAPI servers block in ../openapi/ lists the same hosts.

Authentication

Every request that touches non-public data carries a bearer credential in the Authorization header:

Authorization: Bearer <token>

Three credential types are accepted. They are validated by separate middleware and never cross paths.

Session JWT (interactive)

Exchange username and password for a short-lived JSON Web Token:

POST /api/v1/auth/login
Content-Type: application/json

{ "username": "alice", "password": "..." }

A successful login returns the access token and the caller's profile, and sets a vetrix_access_token cookie (and a refresh cookie) on the response:

{
  "access_token": "<jwt>",
  "user": { "id": "<uuid>", "username": "alice", "...": "..." }
}

Send the JWT as Authorization: Bearer <jwt>. Access tokens are short-lived (15 minutes by default); the browser refreshes them through the refresh cookie. GET /api/v1/auth/me returns the authenticated user.

Personal access token (PAT)

A personal access token is a long-lived credential a user creates for scripts and CLI use. The raw token has the form vetrix_<random>; it is shown once at creation and stored only as a hash. Send it exactly like a JWT:

Authorization: Bearer vetrix_xxxxxxxxxxxxxxxxxxxxxx

PATs are also accepted as the password field of HTTP Basic auth, which is how git over HTTP and the package registries consume them.

OAuth2 access token

Third-party applications obtain an access token through the OAuth2 authorization-code flow. OAuth2 access tokens have the form vetrix_oat_<random> (refresh tokens are vetrix_ort_<random>). They are sent as bearer tokens, the same as the other two types.

Scopes

Two scope catalogs gate access. Which one applies depends on how the request authenticated.

OAuth2 scopes (granted at consent, verb:resource form) — for OAuth2 access tokens. Enforcement is additive to the underlying permission check: the caller must hold both the scope and the ACL permission.

Scope Grants
read:user Read the caller's profile fields
read:repo / write:repo / admin:repo Browse / push / administer repositories
read:issue / write:issue Read / create issues, comments, labels
read:pipeline / write:pipeline View / trigger CI pipelines
read:package / write:package Pull / push registry artifacts
read:audit Read the instance audit log (instance admins only)
openid / profile / email OpenID Connect sign-in and profile claims
mcp:read Use the Model Context Protocol endpoint

Personal-access-token scopes (resource:verb form) — selected when a PAT is created: repo:read, repo:write, repo:admin, issue:read, issue:write, ci:read, ci:write, registry:read, registry:write, admin:users, admin:system, mcp:read, mcp:write.

The string mcp:read appears in both catalogs and names the same user-facing grant — connecting an AI assistant to repositories the caller can read over the MCP endpoint — but each catalog is enforced independently.

Session JWTs carry no scope list: they speak for the full set of permissions the signed-in user holds and bypass scope enforcement.

Pagination

List endpoints use one of two conventions.

Page / per-page envelope

Most list endpoints — for example the repository issue list (GET /api/v1/repos/{owner}/{repo}/issues) — take page and per_page query parameters and return a fixed envelope:

Parameter Default Bounds
page 1 1-based; values below 1 fall back to 1
per_page 25 A value of 0 or below falls back to the default; the upper bound varies by endpoint (see below)
{
  "items": [ ... ],
  "total": 137,
  "page": 1,
  "per_page": 25
}

total is the unpaginated row count, so a client can compute the last page as ceil(total / per_page).

Out-of-range handling is not uniform across endpoints. The issue list above enforces no upper bound: a per_page of 0 or below falls back to the default, but any positive value is honored, so per_page=200 returns up to 200 items. The common pagination helper — used by the pipeline list and other resource lists — instead treats any per_page outside [1, 100] (zero, negative, or above 100) as the default rather than clamping it, so for those lists per_page=200 returns 25 items per page, not 100. Some admin and ACL lists default to 50 or 100, accept a larger maximum, and clamp an out-of-range value to that maximum. Check the per-resource page when the exact bound matters.

A handful of endpoints accept the same page and per_page parameters but return a bare JSON array instead of the envelope. The repository pipeline list (GET /api/v1/repos/{owner}/{repo}/pipelines) is one: the response body is the array of pipeline objects directly, with no enclosing items/total object, so a client requests the next page when it receives a full page. Read the per-resource page to confirm which shape an endpoint returns.

Limit / offset

A few endpoints instead take limit and offset (zero-based) directly and report the count under total. The admin scope-grant list, GET /api/v1/admin/admin-scopes?scope=<scope>, is one: limit defaults to 50 and is clamped to 500, offset defaults to 0, and the response is { "grants": [ ... ], "total": <int>, "limit": <int>, "offset": <int> }. The OpenAPI LimitQuery / OffsetQuery parameters describe this form.

Errors

Every error response is JSON with a top-level error string:

{ "error": "repository not found" }

Some responses add an optional detail string, and validation failures may add a stable machine-readable error_code. Branch client logic on the HTTP status and error_code rather than on the human-readable error copy. See errors.md for the full envelope and the status conventions (including why a private resource returns 404 rather than 403).

Rate limits

Requests are metered by a Redis-backed limiter. A throttled request receives 429 Too Many Requests with X-RateLimit-* and Retry-After headers. See rate-limits.md for the scope buckets, default limits, and the full header set.

Merge requests: /merges is canonical

Merge-request endpoints live under:

/api/v1/repos/{owner}/{repo}/merges/...

The older /pulls/... paths are deprecated. Each /pulls/... route answers with an HTTP 308 Permanent Redirect to the equivalent /merges/... path; the 308 preserves the request method, so a redirected POST stays a POST. New integrations should call /merges directly.

Three wire-level identifiers intentionally keep the pull name for backward compatibility and are not renamed: the merge_requests data is exposed through the pull_request webhook event, and the live-update WebSocket path is /ws/repos/{owner}/{repo}/pulls/{number}.