API¶
All API endpoints are prefixed with /api/v1. The Nix binary cache endpoints live at the root (outside /api/v1).
Reference¶
The full OpenAPI 3.1 specification is in the repository at docs/gradient-api.yaml. View it interactively:
Authentication¶
Endpoints under /api/v1 (except /auth/*, /health, and /config) require a bearer token:
Authorization: Bearer <token>
Two token types are accepted:
| Type | How to obtain | Prefix |
|---|---|---|
| JWT | POST /api/v1/auth/basic/login |
none |
| API key | POST /api/v1/user/keys |
GRAD |
Response Envelope¶
Every JSON response is wrapped in:
{ "error": false, "message": <payload> }
On errors, error is true and message is a string describing the problem.
Quick Reference¶
Auth (no authentication required)¶
| Method | Path | Description |
|---|---|---|
POST |
/auth/basic/register |
Register a new user |
POST |
/auth/basic/login |
Log in, returns JWT |
POST |
/auth/check-username |
Check username availability |
GET |
/auth/verify-email?token=… |
Verify email address |
POST |
/auth/resend-verification |
Resend verification email |
POST |
/auth/oauth/authorize |
Get OIDC authorization URL |
GET |
/auth/oidc/login |
Redirect to OIDC provider |
GET |
/auth/oidc/callback |
OIDC callback handler |
POST |
/auth/logout |
Logout |
GET |
/health |
Health check |
GET |
/config |
Server feature flags |
User¶
| Method | Path | Description |
|---|---|---|
GET |
/user |
Current user info |
DELETE |
/user |
Delete account |
GET |
/user/keys |
List API keys |
POST |
/user/keys |
Create API key |
DELETE |
/user/keys |
Delete API key |
GET |
/user/keys/permissions |
List the permission catalogue |
PATCH |
/user/keys/{api_id} |
Update an API key's name / permissions / org pin |
GET |
/user/settings |
Get profile settings |
PATCH |
/user/settings |
Update profile settings |
Configuring API-key options¶
Each API key carries its own permission set and an optional organization pin:
curl -X POST $API/user/keys \
-H "Authorization: Bearer $SESSION" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-runner",
"permissions": ["triggerEvaluation", "viewOrg"],
"organization": "acme",
"expires_in_days": 90
}'
The key's effective authority on every request is user_role_mask & key_mask,
intersected with the org's role assignment for the caller. A key pinned to an
organization 404s for every other org. The full permission catalogue is at
GET /user/keys/permissions.
To tighten an existing key without rotating the secret:
curl -X PATCH $API/user/keys/$KEY_ID \
-H "Authorization: Bearer $SESSION" \
-H "Content-Type: application/json" \
-d '{ "permissions": ["viewOrg"] }'
API-key-authenticated requests cannot create, edit, revoke, or delete API keys - only session-authenticated calls can. This prevents a leaked key from minting more powerful siblings.
Cache pinning¶
A key may be pinned to a single cache as an alternative to organization
pinning (the two are mutually exclusive). Cache-pinned keys carry a
CachePermission bitmask and can be used only on routes targeting the pinned
cache. Creating a cache-pinned key requires the manageCacheMembers permission
on the target cache. Use the availableCache field on
GET /user/keys/permissions to enumerate valid capability names.
Organizations¶
| Method | Path | Description |
|---|---|---|
GET |
/orgs |
List organizations |
PUT |
/orgs |
Create organization |
GET |
/orgs/{org} |
Get organization |
PATCH |
/orgs/{org} |
Update organization |
DELETE |
/orgs/{org} |
Delete organization |
GET/POST/PATCH/DELETE |
/orgs/{org}/users |
Manage members |
GET/POST |
/orgs/{org}/ssh |
Get / regenerate SSH key |
GET |
/orgs/{org}/subscribe |
List subscribed caches |
POST/DELETE |
/orgs/{org}/subscribe/{cache} |
Subscribe / unsubscribe |
Workers¶
Workers are gradient-worker processes that connect to the server over WebSocket to execute fetch, eval, build, and sign jobs.
| Method | Path | Description |
|---|---|---|
POST |
/orgs/{org}/workers |
Register a worker - returns peer_id and optionally a one-time token |
GET |
/orgs/{org}/workers |
List registered workers (merges live state) |
DELETE |
/orgs/{org}/workers/{worker_id} |
Unregister a worker |
GET |
/admin/workers |
List all currently connected workers (superuser or GRADIENT_GLOBAL_STATS_PUBLIC) |
All endpoints under /admin/* require the calling user to have the
superuser flag set on their account.
Register a worker:
worker_id must be a UUID v4. On a NixOS host running the gradient-worker service the worker auto-generates one on first start and persists it to /var/lib/gradient-worker/worker-id - read it with:
cat /var/lib/gradient-worker/worker-id
# Server generates the token (returned once, store it immediately)
curl -X POST https://gradient.example.com/api/v1/orgs/myorg/workers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"worker_id": "550e8400-e29b-41d4-a716-446655440001"}'
Response (server-generated token):
{
"error": false,
"message": {
"peer_id": "550e8400-e29b-41d4-a716-446655440000",
"token": "a1b2c3..."
}
}
Alternatively, supply a pre-generated token (openssl rand -base64 48 - exactly 64 standard base64 characters). The server stores its hash and does not return it in the response:
curl -X POST https://gradient.example.com/api/v1/orgs/myorg/workers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"worker_id\": \"550e8400-e29b-41d4-a716-446655440001\", \"token\": \"$(openssl rand -base64 48)\"}"
Response (pre-supplied token - no token field):
{
"error": false,
"message": {
"peer_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
Write the peer_id and token to the peers file on the worker host:
echo "<peer_id>:<token>" > /run/secrets/gradient-worker-peers
Set GRADIENT_WORKER_PEERS_FILE (or the NixOS peersFile option) to this path.
List workers:
GET /orgs/{org}/workers returns registered workers merged with live connection info:
{
"error": false,
"message": [
{
"worker_id": "build-01",
"registered_at": "2026-04-12T10:00:00Z",
"live": {
"architectures": ["x86_64-linux"],
"system_features": ["kvm", "big-parallel"],
"max_concurrent_builds": 8,
"assigned_job_count": 2,
"draining": false
}
}
]
}
live is null if the worker is not currently connected.
Unregister a worker:
DELETE /orgs/{org}/workers/{worker_id} removes the registration. The worker stays connected until it disconnects, then cannot reconnect.
Projects¶
| Method | Path | Description |
|---|---|---|
GET |
/projects/{org} |
List projects |
PUT |
/projects/{org} |
Create project |
GET/PATCH/DELETE |
/projects/{org}/{project} |
Get / update / delete |
GET |
/projects/{org}/{project}/details |
Aggregated project data |
GET |
/projects/{org}/{project}/entry-points |
Root builds |
POST |
/projects/{org}/{project}/check-repository |
Test repo access |
POST |
/projects/{org}/{project}/evaluate |
Trigger evaluation |
POST/DELETE |
/projects/{org}/{project}/active |
Enable / disable |
Evaluations¶
| Method | Path | Description |
|---|---|---|
GET |
/evals/{id} |
Get evaluation |
POST |
/evals/{id} |
Abort ({"method":"abort"}) |
GET |
/evals/{id}/builds |
List builds |
POST |
/evals/{id}/builds |
Stream all build logs (NDJSON) |
Builds¶
| Method | Path | Description |
|---|---|---|
POST |
/builds |
Submit direct build (multipart) |
GET |
/builds/direct/recent |
Recent direct builds |
GET |
/builds/{id} |
Build with outputs |
GET/POST |
/builds/{id}/log |
Get log / stream live log |
GET |
/builds/{id}/graph |
Full dependency graph |
GET |
/builds/{id}/dependencies |
Direct dependencies |
GET |
/builds/{id}/downloads |
List artefacts |
GET |
/builds/{id}/download/{filename} |
Download artefact |
Caches¶
| Method | Path | Description |
|---|---|---|
GET |
/caches |
List caches |
PUT |
/caches |
Create cache |
GET/PATCH/DELETE |
/caches/{cache} |
Get / update / delete |
POST/DELETE |
/caches/{cache}/active |
Enable / disable |
GET |
/caches/{cache}/key |
Public signing key |
Commits¶
| Method | Path | Description |
|---|---|---|
GET |
/commits/{id} |
Get commit |
Nix Binary Cache (root, no /api/v1 prefix)¶
Private caches require HTTP Basic Auth (any username, JWT or API key as password - returns 401 without credentials).
Substituter surface (used by nix, nixos-rebuild, etc.):
| Method | Path | Description |
|---|---|---|
GET |
/cache/{cache}/nix-cache-info |
Cache metadata (add ?json for JSON) |
GET |
/cache/{cache}/gradient-cache-info |
Gradient cache metadata (add ?json for JSON) |
GET |
/cache/{cache}/{hash}.narinfo |
Path info (add ?json for JSON) |
GET |
/cache/{cache}/nar/{hash}.nar.zst |
NAR archive |
Inspection surface (NAR content inspection and build logs):
| Method | Path | Description |
|---|---|---|
GET |
/cache/{cache}/ls/{hash} |
JSON tree listing of the NAR (nix-serve .ls v1 schema) |
GET |
/cache/{cache}/serve/{hash}/{path} |
Extract a single file (bytes) or directory (tar.zst) from a NAR |
GET |
/cache/{cache}/log/{drv} |
Build log for <drv>.drv (substituter compat - nix log) |
The inspection endpoints (/ls, /serve) are rate-limited at 60 req/min. The /log endpoint is rate-limited at ~300 req/min on its own tier. All endpoints return 404 when the hash or derivation is unknown.
Example: Trigger an Evaluation¶
TOKEN=$(curl -s -X POST https://gradient.example.com/api/v1/auth/basic/login \
-H 'Content-Type: application/json' \
-d '{"loginname":"alice","password":"secret"}' | jq -r .message)
curl -X POST "https://gradient.example.com/api/v1/projects/my-org/my-project/evaluate" \
-H "Authorization: Bearer $TOKEN"
Response:
{ "error": false, "message": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }