Skip to content

REST API

The Piprio REST API is the programmatic interface to every customer-facing capability: schemas, artifacts, label sets, review queues, connectors, ingestion, routing, teams, exports, quality metrics, webhooks, notifications, and billing. It is served under the /api/v1 prefix and described by an OpenAPI 3.1 document that the project generates directly from the running application. The OpenAPI document is the authoritative contract. This page is the orientation around it.

This is a developer reference. It uses API identifiers, HTTP status codes, route paths, and JSON where those are the actual contract. Display names are given alongside the identifier the first time each appears.

Authentication

The API authenticates requests with a bearer token. The OpenAPI document declares a single security scheme, HTTPBearer (HTTP, scheme bearer), and protected operations require an Authorization: Bearer <token> header.

A token is obtained from the login endpoint, POST /api/v1/auth/login, which takes an email and password and returns a signed JSON Web Token. Tokens are HS256 JWTs issued by the server (PyJWT), carrying the subject, email, a unique token identifier, and a token version used to invalidate sessions. The default token lifetime is 8 hours, after which the token expires and a new login is required.

A login request:

POST /api/v1/auth/login HTTP/1.1
Host: api.piprio.example
Content-Type: application/json

{
  "email": "reviewer@example.com",
  "password": "an-example-password"
}

The response carries the access token, the token type (bearer), a summary of the authenticated user, and the organizations that user belongs to:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "user": { "id": "...", "email": "reviewer@example.com", "name": "..." },
  "organizations": [ { "slug": "acme", "name": "ACME GmbH" } ]
}

Later requests carry the token in the Authorization header:

GET /api/v1/artifacts HTTP/1.1
Host: api.piprio.example
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

A small number of endpoints are unauthenticated by design, including the login endpoint itself and the password-reset request and confirm endpoints. The session endpoint, GET /api/v1/auth/session, reports who the current token belongs to. Logout, POST /api/v1/auth/logout, requires a valid bearer token.

The platform also supports long-lived API keys for service-to-service access. API keys are prefixed dl_, presented once at creation, and stored only as a hash. When used, an API key is sent in the X-API-Key header rather than the Authorization header. The current OpenAPI document declares only the HTTPBearer scheme, so API-key usage is not yet reflected in the machine-readable contract.

The organization context for a request is carried in the X-Org-Slug header. The request headers allowed for cross-origin calls are Content-Type, Authorization, X-Request-ID, X-Org-Slug, and X-API-Key.

Rate limits

Application-level rate limiting is enforced. It is backed by Redis so limits aggregate across application workers, and it is keyed per client IP address by default, with some limits keyed per organization (using the X-Org-Slug header). The limits below are read directly from the route definitions in the backend and are the values in force today.

Endpoint Limit Keyed by
POST /api/v1/auth/login 10 / minute client IP
Password endpoints (reset, change) 5 / minute client IP
Account registration 5 / hour and 3 / hour client IP
SSO endpoints 30 / minute client IP
Organization creation 10 / hour client IP
Artifact actions (bulk operations) 5 / minute organization
Connector crawl trigger 2 / minute organization
Export job creation 10 / hour organization
Quality (inter-rater agreement) compute 30 / minute organization
Internal control-plane writes 30 / 60 seconds client IP

Endpoints without an explicit limit in the table above are not separately rate limited at the application layer today. When a limit is exceeded the API returns HTTP 429 with a Retry-After header and the standard error envelope, using the error code rate_limited. The limiter fails open if Redis is unavailable, so a cache outage does not block authentication or the control plane.

Errors

Every error response uses one envelope, defined in app/schemas/common.py as ErrorResponse. It wraps a single error object:

{
  "error": {
    "code": "stale_precondition",
    "message": "The record changed since it was last read.",
    "field": "schema_version",
    "details": { }
  }
}

The code is a stable, machine-readable string. The message is human-readable. The field and details members are optional and present only when the error is tied to a specific input field or carries extra structured context.

The HTTP status codes the API uses, with the error codes that map to each:

Status Meaning Error codes
400 Bad request or validation validation_error, invalid_field_type, schema_breaking_change, required_fields_missing, duplicate_field_name
401 Authentication failed or token expired invalid_credentials, token_expired, invalid_api_key
402 Plan limit reached plan_limit_reached
403 Authenticated but not permitted permission_denied, insufficient_role
404 Resource not found not_found
409 Conflict conflict, artifact_duplicate, schema_version_conflict, stale_precondition
422 Request body failed schema validation unprocessable
429 Rate limit exceeded rate_limited
500 Unexpected server error internal_error

Successful responses use 200 for reads and updates, 201 for created resources, 202 for accepted asynchronous work, and 204 for deletions with no body. The 409 stale_precondition code is the optimistic-concurrency signal: when two clients edit the same record, the second write is rejected so the first is not silently overwritten.

The OpenAPI document declares the success status and a 422 validation response for each operation, because those are generated automatically from the request and response models. The full set of error codes and their statuses is defined centrally in app/schemas/common.py rather than per operation.

Endpoints by resource

Operations are grouped by resource tag. Each group below names what it manages and gives one or two representative endpoints. The OpenAPI document holds the full list of 217 operations across 184 paths. The entries here are a map, not the territory.

auth: Sessions, password reset, email change, invitations, and single sign-on. POST /api/v1/auth/login, GET /api/v1/auth/session.

organizations: Organization records, members, single sign-on configuration, and organization-level settings. GET /api/v1/organizations, POST /api/v1/organizations/{org_slug}/sso.

schemas: Labeling specs that define what to extract from a document type, including activation and AI-assist toggles. GET /api/v1/schemas, POST /api/v1/schemas/{schema_id}/deactivate.

schema-versions: Immutable historical versions of a schema. GET /api/v1/schema-versions/{version_id}.

schema-templates: Prebuilt industry starting points for new schemas. GET /api/v1/schema-templates, GET /api/v1/schema-templates/{template_id}.

artifacts: The documents being labeled, plus bulk assignment and deletion. GET /api/v1/artifacts, POST /api/v1/artifacts/bulk-assign.

tag-sets: Label sets attached to an artifact, with accept, reject, and schema-assignment actions. GET /api/v1/tag-sets/{tag_set_id}, POST /api/v1/tag-sets/{tag_set_id}/accept.

review-queue: The reviewer work queue and its aggregate statistics. GET /api/v1/review-queue, POST /api/v1/review-queue/bulk-accept.

connectors: Source connections that bring documents in, with test and crawl actions. GET /api/v1/connectors, POST /api/v1/connectors/{connector_id}/crawl.

ingestion-jobs: The runs that pull and process documents from a connector. GET /api/v1/ingestion-jobs, POST /api/v1/ingestion-jobs/{job_id}/extract.

routing-rules: Rules that assign incoming work to teams or reviewers, with preview and suggestions. GET /api/v1/routing-rules, POST /api/v1/routing-rules.

teams: Teams, membership, and the organization chart. GET /api/v1/teams, POST /api/v1/teams/{team_id}/members.

users: The current user's own profile and notification preferences. GET /api/v1/users/me, PATCH /api/v1/users/me/notification-preferences.

exports: On-demand export jobs and their audit trail. POST /api/v1/exports, GET /api/v1/exports/{job_id}.

scheduled-exports: Recurring exports that run on a schedule, with run-now and history. GET /api/v1/exports/scheduled, POST /api/v1/exports/scheduled/{export_id}/run.

export-formats: The available export output formats. GET /api/v1/export-formats.

quality: Quality metrics, including inter-rater agreement and trend analysis. GET /api/v1/quality/latest, POST /api/v1/quality/compute.

webhooks: Outbound webhook endpoints and their delivery testing. GET /api/v1/webhooks, POST /api/v1/webhooks/{webhook_id}/test.

notifications: In-app notifications, unread counts, and delivery preferences. GET /api/v1/notifications, POST /api/v1/notifications/read-all.

billing: Plan usage and usage history. GET /api/v1/billing/usage, GET /api/v1/billing/usage/history.

billing-invoices: Invoices, invoice PDFs, and the billing profile. GET /api/v1/billing/invoices, GET /api/v1/billing/invoices/{invoice_id}/pdf.

feedback: Prediction feedback capture and summary, used to measure model accuracy against accepted labels. POST /api/v1/feedback/predictions, GET /api/v1/feedback/summary.

List endpoints are cursor paginated. They accept a cursor and a page_size (default 100, maximum 1000) and return results in the paginated envelope described below.

OpenAPI spec

The machine-readable OpenAPI 3.1 document is the source of truth for the API contract. It is generated from the running application, so it always matches the deployed routes and response models rather than a hand-maintained description.

The application serves the document and interactive documentation at the standard FastAPI paths: the raw document at /openapi.json, Swagger UI at /docs, and ReDoc at /redoc. None of these paths are overridden, so they are on by default.

For checked-in tooling, the project exports the same document to a file with backend/scripts/export_openapi.py. The frontend consumes that exported file to generate its typed API client. The export runs from the repository root when a virtual environment is available, or inside the application container:

docker exec -w /app piprio-web-1 \
  sh -c 'OPENAPI_OUT=/tmp/openapi.json python3 -m scripts.export_openapi'
docker cp piprio-web-1:/tmp/openapi.json ./openapi.json

Responses use the envelope defined in app/schemas/common.py. A single resource is wrapped in ApiResponse, which carries data and an optional meta. A list is wrapped in PaginatedResponse, which carries data (an array) and a meta object with total, next_cursor, prev_cursor, and page_size. Errors use ErrorResponse as described above. Reading the exported document alongside these three envelopes gives the complete request and response shape for every endpoint.