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.com in release, ..dev.payability.com in dev), credentials allowed, all methods and headers.

  • Sentry — SentryAsgiMiddleware wraps the app; sentry_sdk.init uses ENV_NAME as the environment.

  • Routers — misc_router, tasks_router, tools_router, entrust_router, and entrust_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. In release it is mounted at /. All paths on these pages are shown without the dev prefix.

  • Interactive docs — /docs and /redoc are enabled in dev and disabled in release (both set to None by settings_setup.py).

Authentication

Every /tasks, /tools and /entrust/sessions route declares dependencies=[Depends(JWTAuthorizer)]. JWTAuthorizer:

  1. reads the x-session-token header (APIKeyHeader, auto_error=False),

  2. validates it via GET {PAYABILITY_INTERNAL_API_URL}/internal/token/validate,

  3. decodes the JWT without signature verification to read login_key,

  4. exposes get_source(), get_login_key(), get_login_id(), get_supplier_key().

Status Cause

400

The auth response has no source claim.

401, 404

Propagated from the auth API (400/401/404 are passed through).

409

A KeyError while reading the auth response (a required claim is missing).

500

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

GET

/health-check

inline

none

POST

/tasks/execute

ExecuteTaskEndpointHandler

JWT; internal needs DEV/FINANCE; supplier dashboard scoped to its own supplier_key

GET

/tasks/result

GetTaskResultEndpointHandler

JWT; internal source only; detailed=true additionally needs DEV/FINANCE/REST_UW_ACCESS

GET

/tasks/{correlation_id}

GetTaskStatusEndpointHandler

JWT; internal source only

DELETE

/tasks/result

DeleteTaskResultsEndpointHandler

JWT; internal source only

PUT

/tasks/{correlation_id}/final_statuses

UpdateFinalStatusEndpointHandler

JWT; internal source only

GET

/tools

GetToolsEndpointHandler

JWT (any source)

POST

/entrust/sessions

CreateEntrustSessionEndpointHandler

JWT; internal needs DEV/FINANCE; supplier dashboard scoped to itself; onboarding self-service

POST

/entrust/webhook

HandleEntrustWebhookEndpointHandler

none — Onfido X-SHA2-Signature HMAC

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 JSONResponse built from jsonable_encoder.

  • Controlled errors use the envelope {"code": <status>, "status": "<reason phrase>", "message": "<text>"} from BaseEndpointHandler.build_json_response.

  • Domain errors (TaskExecutionError subclasses) serialize as {"error_class": "…​", "error_body": {…​}} with status 400.

  • Some handlers return {"error_message": "…​"} for unexpected exceptions (400 or 500 depending on the handler).

Handler → source file

Endpoint File

POST /tasks/execute

interface/web_api/endpoint_handling/execute_task.py

GET /tasks/result

interface/web_api/endpoint_handling/get_task_result.py

GET /tasks/{id}

interface/web_api/endpoint_handling/get_task_status.py

DELETE /tasks/result

interface/web_api/endpoint_handling/delete_task_results.py

PUT /tasks/{id}/final_statuses

interface/web_api/endpoint_handling/update_final_status.py

GET /tools

interface/web_api/endpoint_handling/get_tools.py

POST /entrust/sessions

interface/web_api/endpoint_handling/create_entrust_session.py

POST /entrust/webhook

interface/web_api/endpoint_handling/handle_entrust_webhook.py

Routers

interface/web_api/router/{misc,tasks,tools,entrust}_router.py

Base, schemas, logging

interface/web_api/endpoint_handling/base.py, interface/web_api/schemas.py, interface/web_api/web_request_logger.py