API overview
The Web API (web_api_app.py) is the only HTTP surface. It is served by the Web API container
exclusively — the worker exposes nothing.
Application
app = FastAPI(
title='automatic-uw-tool',
description='Automatic Underwriting Tool',
docs_url=settings.FAST_API_DOCS_URL,
redoc_url=settings.FAST_API_REDOC_URL,
**extra_args # root_path, non-release environments only
)
-
CORS —
allow_origin_regex = FAST_API_CORS_ALLOW_ORIGIN(..payability.cominrelease,..dev.payability.comin dev), credentials allowed, all methods and headers. -
Sentry —
SentryAsgiMiddlewarewraps the app;sentry_sdk.initusesENV_NAMEas the environment. -
Routers —
misc_router,tasks_router,tools_router,entrust_router, andentrust_router.webhook_router(an unauthenticated sibling for the Onfido callback). -
Root path — in dev the app is mounted under
/automatic-uw-tool-web-api, so dev URLs look like/automatic-uw-tool-web-api/tasks/execute. Inreleaseit is mounted at/. All paths on these pages are shown without the dev prefix. -
Interactive docs —
/docsand/redocare enabled in dev and disabled inrelease(both set toNonebysettings_setup.py).
Authentication
Every /tasks, /tools and /entrust/sessions route declares
dependencies=[Depends(JWTAuthorizer)]. JWTAuthorizer:
-
reads the
x-session-tokenheader (APIKeyHeader,auto_error=False), -
validates it via
GET {PAYABILITY_INTERNAL_API_URL}/internal/token/validate, -
decodes the JWT without signature verification to read
login_key, -
exposes
get_source(),get_login_key(),get_login_id(),get_supplier_key().
| Status | Cause |
|---|---|
|
The auth response has no |
|
Propagated from the auth API ( |
|
A |
|
Anything else, including auth-API 5xx. |
Unauthenticated routes: GET /health-check and POST /entrust/webhook — the latter is trusted
by HMAC signature instead. See Auth & permissions.
Authorization
Two independent gates:
Source (ProcessSource, from the token): internal, supplier_dashboard or onboarding.
Several endpoints are internal-source-only regardless of permissions; supplier-dashboard callers
are always scoped to their own supplier_key.
Permissions (BaseEndpointHandler.check_permissions): looked up per login_key through the
Payability internal API. INT_SUPER_USER is appended to every check as a fallback, and
SUPPLIER_DASHBOARD callers skip the check entirely. An auth-API failure during the check
answers 500; a caller without any listed perm gets 403.
Route table
| Method | Path | Handler | Auth and scope |
|---|---|---|---|
|
|
inline |
none |
|
|
|
JWT; internal needs |
|
|
|
JWT; internal source only; |
|
|
|
JWT; internal source only |
|
|
|
JWT; internal source only |
|
|
|
JWT; internal source only |
|
|
|
JWT (any source) |
|
|
|
JWT; internal needs |
|
|
|
none — Onfido |
Details: Task endpoints, Tool endpoints, Entrust endpoints.
GET /health-check
Unauthenticated liveness probe used by the ALB target group.
@router.get(path='/health-check', description='Health Check')
def health_check():
return {'status': 'OK'}
Response: {"status": "OK"}.
Response conventions
-
Success responses are pydantic models (Schemas) or a
JSONResponsebuilt fromjsonable_encoder. -
Controlled errors use the envelope
{"code": <status>, "status": "<reason phrase>", "message": "<text>"}fromBaseEndpointHandler.build_json_response. -
Domain errors (
TaskExecutionErrorsubclasses) serialize as{"error_class": "…", "error_body": {…}}with status400. -
Some handlers return
{"error_message": "…"}for unexpected exceptions (400or500depending on the handler).
Handler → source file
| Endpoint | File |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Routers |
|
Base, schemas, logging |
|