Skip to Content
ContributeBackendAPI Structure

API Structure

FastAPI routers live under app/routers/, one resource family per module (test.py, test_set.py, metric.py, endpoint.py, architect.py, …). Paths follow REST conventions unless noted below.

Authentication backstop

There is no per-route auth declaration to remember. After all routers are included (core and EE), main.py walks every registered route and injects authentication and authorization dependencies:

  • apply_auth_backstop injects require_current_user_or_token on every route whose exact path is not in PUBLIC_ROUTES, unless the route already declares an auth dependency (directly or transitively via get_tenant_db_session). This guarantees a route is never accidentally exposed.
  • apply_authz_backstop then injects a require_permission(capability) check on every non-exempt route.

PUBLIC_ROUTES (no authentication) lives in app/auth/public_routes.py:

public_routes.py
PUBLIC_ROUTES: list[str] = [
    "/",
    "/auth/login/{provider}",
    "/auth/login/email",
    "/auth/callback",
    "/auth/logout",
    "/auth/register",
    "/auth/verify-email",
    "/auth/magic-link",
    "/auth/refresh",
    # ...other /auth/* endpoints
    "/home",
    "/feedback/",
    "/health",
    "/docs",
    "/redoc",
    "/openapi.json",
]

The check is an exact match against the fully-resolved path, so trailing slashes matter. The list is a mutable module attribute so EE features can extend it from their bootstrap before their routers are included.

For token vs. session auth and the RBAC capability model, see Backend Authentication and Authorization (RBAC).

Standard endpoints

Most resource routers follow the same shape:

  • GET /\{resource\}/ — list (filtering, sorting, pagination; total in X-Total-Count)
  • GET /\{resource\}/\{id\} — get one by ID
  • POST /\{resource\}/ — create
  • PUT /\{resource\}/\{id\} — update
  • DELETE /\{resource\}/\{id\} — delete
example-router.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session

from rhesis.backend.app.database import get_db
from rhesis.backend.app.schemas import TestCreate, Test
from rhesis.backend.app.crud import create_test, get_test

router = APIRouter(prefix="/tests", tags=["tests"])

@router.post("/", response_model=Test)
def create_test_endpoint(test: TestCreate, db: Session = Depends(get_db)):
    return create_test(db=db, test=test)

@router.get("/{test_id}", response_model=Test)
def read_test(test_id: str, db: Session = Depends(get_db)):
    db_test = get_test(db, test_id=test_id)
    if db_test is None:
        raise HTTPException(status_code=404, detail="Test not found")
    return db_test

Query parameters

List endpoints accept skip, limit, sort_by, sort_order, and OData-style $filter and $select:

query-example.txt
GET /tests/?$filter=priority eq 1&sort_by=created_at&sort_order=desc&skip=10&limit=10

The list endpoint sets X-Total-Count with the unpaginated total (exposed via CORS). See the OData Query Guide for the full filter syntax.

API documentation

FastAPI generates OpenAPI docs automatically: Swagger UI at /docs, ReDoc at /redoc, and the schema at /openapi.json.

Error handling

Errors use standard HTTP status codes (400, 401, 403, 404, 500) with a JSON body carrying a detail message.