REST API¶
The gateway exposes three HTTP surfaces with different authentication:
| Surface | Paths | Authentication |
|---|---|---|
| Web / API | everything except /git/** |
OIDC session cookie |
| Machine API | /api/** with an Authorization: Bearer header |
Machine API credential |
| Git facade | /git/** |
Personal access token over HTTP Basic |
The three do not overlap. A personal access token reaches the facade and nothing else; a machine API credential reaches the API and nothing else — it cannot clone a marketplace, including the marketplaces an empty fetch scope would otherwise grant. Which surface answers a request is decided by what it carries, not by what it asks for.
This section documents the first. For the second see Git smart-HTTP facade.
A live reference ships with the gateway
/docs renders the OpenAPI document at /v3/api-docs. Both are behind the
OIDC login. These pages describe the same surface with the surrounding
reasoning.
Conventions¶
Authentication. Every /api/** endpoint is reached by exactly one of two
paths:
- an authenticated OIDC session, which is what the portal uses; or
- a machine API credential presented as
Authorization: Bearer <secret>, which is what infrastructure-as-code and CI use.
A request carrying no bearer header takes the session path, exactly as it always
has. A request carrying one takes the machine path and is authenticated
strictly — including when skills-gateway.dev-insecure-auth=true, which opens
the browser surface and never the bearer path.
A machine request must carry no cookie
The machine path refuses a request that presents both a bearer credential
and a Cookie header, rather than resolving it to either. Ambiguity about
which credential authorised a request is where confused-deputy defects live.
The practical consequence: a client behind a load balancer that injects its
own session-affinity cookie (AWSALB, GCLB-style) is refused with a bare
401. Strip the cookie, or exclude the gateway's hostname from affinity.
What a machine credential can reach is an allowlist, described in
Machine API credentials. Every act of human
judgement — approving, rejecting, waiving — every operation that retracts or
republishes content, every role grant and the whole of /api/tokens/** is
outside it, and no combination of scopes and no role reaches them.
Authorization. With role enforcement at its default (off), any
authenticated session may call any endpoint. With
skills-gateway.roles.enabled=true, every mutation and the audit surface
require a role and answer 403 without one; the browsing surface stays open.
Each endpoint's page states its requirement, and
Delegated administration has the
matrix. Access tokens are scoped to the calling principal server-side in either
mode.
Unauthenticated requests to /api/** receive a clean 401, not a 302 to
the identity provider, so an expired session surfaces as an error rather than an
HTML login page rendered into a fetch().
CSRF is enabled for the web surface but disabled for /api/**, which is
consumed by the portal with a session cookie. The machine path earns its own
exemption the way the git facade does and does not borrow the session path's: it
is stateless, creates no session, honours no cookie and refuses a request that
carries one, so every request there authenticates itself.
Compatibility. Within a major, this surface only grows; a breaking change moves the path prefix and ships as a major. See The API contract for what counts as breaking and how it is enforced.
Errors are RFC 7807 ProblemDetail documents:
{"type":"about:blank","title":"Bad Request","status":400,
"detail":"url scheme must be one of [http, https]"}
Status codes used across the API:
| Status | Meaning |
|---|---|
| 400 | The request violated a trust-boundary rule — a disallowed URL scheme, or a non-default ref. |
| 403 | Role enforcement is enabled and the session lacks the role the endpoint requires; or a machine credential reached an endpoint its scopes do not cover, or one no scope covers. |
| 404 | No such marketplace, snapshot or token. |
| 409 | A state conflict — a duplicate name, a decision on a snapshot that is already approved or rejected, or a re-vet of one that is not approved. |
| 422 | A name failed ^[a-z0-9][a-z0-9_-]*$. |
| 502 | Ingestion failed against the upstream. |
Endpoint index¶
| Area | Endpoints |
|---|---|
| Marketplaces and snapshots | Register, list, ingest, inspect contents, licenses, vetting, waivers, re-vet, fetchers, approve, reject, provenance |
| Access tokens | Create, list, revoke and rotate personal access tokens; provision and administer machine API credentials |
| Audit | Read the ledger; stream it as NDJSON; register, replay and delete export sinks |
| Adoption | Windowed adoption report per marketplace and SHA; identities not on the served tip |
| Webhooks | Register, list and delete subscribers; list delivery attempts |
| Retention | Preview candidates, evaluate, compact, soft-delete and restore snapshots |
| Roles | List, grant and revoke delegated-administration roles |
| Estate | Read the last declarative-estate reconciliation report; trigger a reconcile |
| Policy | Create, list, update and delete CEL deny rules; test expressions in the playground |
Session¶
GET /api/me returns the current principal, whether role enforcement is
enabled, the session's effective roles with the source of each, and whether
the identity provider truncated the membership claim. The portal uses it for
the sidebar footer and, with roles, to adapt its controls.
{"username": "alice@example.com",
"rolesEnabled": true,
"roles": [{"role": "approver", "marketplace": "acme", "source": "grant"},
{"role": "auditor", "marketplace": null, "source": "claim"}],
"claimsTruncated": false}
source |
Where the role came from |
|---|---|
config |
skills-gateway.roles.admins. No grant row; unrevocable through the API. |
grant |
A row in the grants API. |
claim |
An identity-provider claim value mapped by skills-gateway.roles.mappings. |
The same role and marketplace is reported once, attributed to the most durable
source that produced it. claimsTruncated: true means the provider dropped the
membership claim rather than the session having none — the roles listed are
then incomplete; see Identity providers.
Non-API endpoints¶
| Path | Notes |
|---|---|
/actuator/health |
The only unauthenticated path. Use the bare path for probes. |
/actuator/sbom |
CycloneDX SBOM. Authenticated. |
/v3/api-docs, /docs |
OpenAPI document and Scalar UI. Authenticated. |
/oauth2/authorization/idp, /login/oauth2/code/idp |
OIDC login and callback. |
/, /marketplaces, /marketplaces/{name}, /audit, /adoption, /tokens, /webhooks |
Forwarded to the single-page application. |