API overview
The console is built on Sift’s HTTP API, and you can call the same API yourself: sign in with a session cookie, send a CSRF token on every change, and read one error shape. Every endpoint, with its roles, is listed on API endpoints.
Where it is
Section titled “Where it is”The API is served on the console’s own address, under /api. The stack publishes no
other port.
| Path on the console’s address | What it is | Authentication |
|---|---|---|
/api/v1/... | The API the console uses. | Session cookie |
/api/healthz | Liveness: {"status": "ok", "service": ..., "version": ...}. | None |
/api/readyz | Readiness: checks the database, the queue and object storage. 200 with "status": "ok", or 503 with "status": "degraded" and each check’s result. Gate an orchestrator on this one. | None |
/api/metrics | Prometheus metrics. 404 unless SIFT_METRICS_TOKEN is set. See Monitor Sift. | Authorization: Bearer <SIFT_METRICS_TOKEN>; 401 without it |
/api/openapi.json | The OpenAPI 3.1 document. Only when SIFT_ENVIRONMENT is development. | None |
/api/docs | Interactive API docs for that document. Only in development. | None |
Authenticate
Section titled “Authenticate”-
Send
POST /api/v1/auth/loginwith a JSON body:{"username": "admin", "password": "your-password"} -
Keep the
sift_sessioncookie the response sets. It isHttpOnly,SameSite=Lax, and outsidedevelopmentit isSecure, so it is only sent over HTTPS. -
Keep
csrf_tokenfrom the response body. The body is the same asGET /api/v1/auth/mereturns:{"user": {"id": "...", "username": "admin", "role": "admin", "must_change_password": false, "created_at": "...", "last_login_at": "..."},"csrf_token": "..."} -
Send the cookie on every request, and on every
POST,PUT,PATCHandDELETEalso send the token in theX-CSRF-Tokenheader.GET,HEADandOPTIONSneed no token.
A missing or wrong token is refused with 403 csrf_failed. GET /api/v1/auth/me returns
the token again for a client that has lost it. A session lasts SIFT_SESSION_TTL_HOURS
(12 by default); POST /api/v1/auth/logout, or a password change, ends every session of
that account. Repeated failed sign-ins from one address or for one username are refused
with 429 too_many_attempts and a Retry-After header.
Example: sign in and list filters
Section titled “Example: sign in and list filters”SIFT=https://sift.example.internalJAR=$(mktemp)
# Sign in; keep the cookie in the jar and the CSRF token in a variable.CSRF=$(curl -sS -c "$JAR" -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"your-password"}' \ "$SIFT/api/v1/auth/login" | jq -r .csrf_token)
# A read: the cookie is enough.curl -sS -b "$JAR" "$SIFT/api/v1/filters"
# A change: the cookie and the CSRF token.curl -sS -b "$JAR" -X POST -H "X-CSRF-Token: $CSRF" "$SIFT/api/v1/auth/logout"On a development stack over plain HTTP, use http://localhost:8080 (or your
SIFT_WEB_PORT).
Every endpoint under /api/v1 except POST /api/v1/auth/login needs a signed-in user.
Which roles may call each one is in the OpenAPI document as x-required-roles on each
operation, and on API endpoints. A role the endpoint
does not allow is refused with 403 forbidden.
| Who | May call |
|---|---|
| reviewer, operator, admin | Every read of filters, activity, alerts, the dashboard, models, labels, system status and Kindo agents and models; their own session and password; labelling a gold-set item; answering the verdict check; previewing a version or a stage change. |
| operator, admin | Every change to a filter: creating and saving versions, lifecycle moves, training, Sift’s assistant (/api/v1/copilot/...), the analyzer and the portfolio scan, approving a gold set (POST /api/v1/filters/{filter_id}/gold-set/approve) and approving a threshold (POST /api/v1/filters/{filter_id}/threshold/approve); POST /api/v1/system/ping. |
| admin | Users, the Kindo credential, webhooks, the recipe settings, and POST /api/v1/system/decisions/ping. |
The console shows the gold-set and threshold forms to a reviewer, but the server refuses a reviewer’s approval. See Users and roles.
API-only endpoints
Section titled “API-only endpoints”The portfolio scan has no screen in the console. It ranks every agent in your Kindo tenant by its filtering opportunity: it looks briefly at every agent, then more closely at the likeliest. It runs in the background, and a large tenant means many Kindo API calls and inference spend. Operator or admin.
| Method and path | What it does |
|---|---|
POST /api/v1/analysis/portfolio-scan | Queues a scan. |
GET /api/v1/analysis/portfolio-scan/{scan_id} | The scan and its ranking, as far as it has got. |
To start from one agent instead, use the analyzer in the console: see Start from an existing agent.
Errors
Section titled “Errors”Every error has one shape, whatever the endpoint:
{ "error": { "code": "validation_failed", "message": "Request failed validation.", "details": {"errors": [{"loc": ["body", "username"], "msg": "...", "type": "..."}]}, "request_id": "3f9c1e0a7b2d4c6e8a1f0b2c3d4e5f60" }}| Field | Meaning |
|---|---|
code | A stable, machine-readable code. Match on this, not on the message. |
message | A sentence for a person. |
details | More about the failure, or null. A validation error lists each failing field, without the value sent. |
request_id | The request’s id, also returned in the X-Request-ID response header and on the server’s log lines. Send your own X-Request-ID (up to 64 characters) to have it used instead. |
The general codes:
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | Not signed in, or the session is invalid, expired or ended. |
| 403 | forbidden | Your role may not call this endpoint. |
| 403 | csrf_failed | A change was sent without the right X-CSRF-Token. |
| 404 | not_found | No such resource or path. |
| 409 | conflict | The request conflicts with the current state. |
| 409 | illegal_transition | A lifecycle move the filter’s state, approvals or preconditions do not allow. |
| 422 | validation_failed | The body or parameters failed validation, or a filter spec broke one of its rules. |
| 429 | too_many_attempts | Too many failed sign-ins; wait for Retry-After. |
| 500 | internal_error | An unexpected failure. The response carries no detail; the server log has it under the request_id. |
| 503 | dependency_unavailable | A service Sift depends on did not answer. |
Endpoints that call Kindo or the decision model add their own codes, such as
kindo_not_configured (503), kindo_rate_limited (429), kindo_unavailable (503) and
decision_model_unavailable (503). Any other status without a specific code is
http_error.