This document defines the backend API contract for the TWA app. It is the shared agreement between frontend and backend implementation and supersedes the rough endpoint notes in project-specification.md where the two differ.
Key decisions reflected here:
- Base path is
/api/v1 authSDKis the external authentication authority for signup, login, session refresh, logout, email verification, password reset, and OTP- The TWA backend does not issue JWTs or validate passwords directly
- The TWA backend stores the real app role:
jobseeker,employer, orstaff - Employers can view applicant charge fields when applicant sharing is enabled
- Jobseekers never receive charge-based ineligibility reasons
- Listings use separate
review_statusandlifecycle_status - System audit events may use
actor_id = null
/api/v1
Content-Type: application/jsonTWA uses authSDK as an external auth service.
Production integration model:
- Browser frontends call same-origin
/_authroutes backed byauthSDKfor signup, login, refresh, logout, password reset, email verification, and OTP - Frontends must complete authSDK email verification before password login will succeed
- Browser auth uses cookie-backed sessions rather than storing access or refresh tokens in browser storage
- Unsafe browser requests must include the CSRF header expected by the auth and backend middleware
- The TWA backend validates authSDK sessions using
auth-service-sdkmiddleware and online session validation - The TWA backend resolves the local app user from the auth session subject (
sub) and enforces the local TWA role from its own database - The auth provider role inside the validated session is not the TWA app role
Protected TWA routes require:
- browser clients: authSDK access cookie plus CSRF header on unsafe requests
- non-browser clients:
Authorization: Bearer <authsdk access token>
All timestamps are ISO 8601 UTC strings.
Example:
"2026-03-18T23:15:00Z"All resource IDs are UUID strings unless otherwise stated.
List endpoints use:
?page=1&page_size=20
Paginated response shape:
{
"items": [],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 125,
"total_pages": 7
}
}All non-2xx responses should follow:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "One or more fields are invalid.",
"details": {
"email": ["Email is required."]
}
}
}UNAUTHENTICATEDFORBIDDENNOT_FOUNDVALIDATION_ERRORCONFLICTACCOUNT_PENDING_APPROVALPROFILE_INCOMPLETELOCAL_ROLE_NOT_ASSIGNEDLISTING_NOT_ELIGIBLEAPPLICANT_VISIBILITY_DISABLEDSTATE_TRANSITION_NOT_ALLOWED
This is the TWA-local application role, not the auth provider role.
["jobseeker", "employer", "staff"]This is the role carried inside authSDK tokens.
["admin", "user", "service"]["own_car", "public_transit", "both"]["own_car", "any"]["pending", "approved", "rejected"]["pending", "approved", "rejected"]["open", "closed"]["submitted", "reviewed", "hired"]["email", "in_app"]{
"app_user": {
"id": "5f3e9ca0-4c62-4476-9701-d8dfb09bb201",
"auth_user_id": "9ebf8dc7-8ec1-4c97-a12f-52e7fb75f548",
"email": "jane@example.com",
"auth_provider_role": "user",
"app_role": "jobseeker",
"is_active": true,
"created_at": "2026-03-18T23:15:00Z",
"updated_at": "2026-03-18T23:15:00Z"
},
"profile_complete": true,
"next_step": null
}{
"sex_offense": false,
"violent": false,
"armed": false,
"children": false,
"drug": true,
"theft": false
}{
"id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"app_user_id": "5f3e9ca0-4c62-4476-9701-d8dfb09bb201",
"auth_user_id": "9ebf8dc7-8ec1-4c97-a12f-52e7fb75f548",
"full_name": "Jane Doe",
"phone": "3145550101",
"address": "123 Main St",
"city": "St. Louis",
"zip": "63103",
"transit_type": "public_transit",
"charges": {
"sex_offense": false,
"violent": false,
"armed": false,
"children": false,
"drug": true,
"theft": false
},
"profile_complete": true,
"status": "active",
"created_at": "2026-03-18T23:15:00Z",
"updated_at": "2026-03-18T23:15:00Z"
}{
"id": "c61c2014-33e8-4d0e-a7ef-f609d5ab0aeb",
"app_user_id": "6b8ca13d-6c89-447e-bc80-8f35c406e9c4",
"auth_user_id": "0bc43a5d-1a57-4bc2-8a5e-f2c57f1199f9",
"org_name": "Northside Logistics",
"contact_name": "Sam Carter",
"phone": "3145550199",
"address": "500 Market St",
"city": "St. Louis",
"zip": "63101",
"review_status": "pending",
"review_note": null,
"reviewed_by": null,
"reviewed_at": null,
"created_at": "2026-03-18T23:15:00Z",
"updated_at": "2026-03-18T23:15:00Z"
}{
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"employer_id": "c61c2014-33e8-4d0e-a7ef-f609d5ab0aeb",
"title": "Warehouse Associate",
"description": "Loading, unloading, and inventory assistance.",
"location_address": "2000 North Broadway",
"city": "St. Louis",
"zip": "63102",
"transit_required": "any",
"disqualifying_charges": {
"sex_offense": false,
"violent": false,
"armed": false,
"children": false,
"drug": false,
"theft": true
},
"transit_accessible": true,
"job_lat": 38.6351,
"job_lon": -90.1885,
"review_status": "approved",
"lifecycle_status": "open",
"review_note": null,
"reviewed_by": "bd29b4dc-bd93-42f8-9d1e-98f2019c13aa",
"reviewed_at": "2026-03-18T23:15:00Z",
"created_at": "2026-03-18T23:15:00Z",
"updated_at": "2026-03-18T23:15:00Z"
}{
"job": {
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate",
"city": "St. Louis",
"transit_required": "any",
"transit_accessible": true,
"review_status": "approved",
"lifecycle_status": "open"
},
"is_eligible": false,
"ineligibility_tag": "14.2 miles from your zip code"
}{
"id": "fb6f50c1-8571-48d0-9568-ac22d1f2a8e5",
"jobseeker_id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"job_listing_id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"status": "submitted",
"applied_at": "2026-03-18T23:15:00Z",
"updated_at": "2026-03-18T23:15:00Z"
}{
"notify_staff_on_apply": true,
"notify_employer_on_apply": false,
"share_applicants_with_employer": false,
"updated_by": "bd29b4dc-bd93-42f8-9d1e-98f2019c13aa",
"updated_at": "2026-03-18T23:15:00Z"
}{
"id": "ba2cdd31-986f-4e11-a5a2-7ef1d3322f65",
"actor_id": null,
"action": "gtfs_feed_refreshed",
"entity_type": "system",
"entity_id": null,
"old_value": null,
"new_value": {
"listings_recomputed": 42
},
"timestamp": "2026-03-18T23:15:00Z"
}Authentication is split between two systems:
authSDKhandles credentialed auth and browser session lifecycle- The TWA backend handles local user bootstrap, local app roles, profile state, and all app authorization decisions
The frontend should talk to authSDK through same-origin /_auth routes for login-related actions. The TWA backend should never proxy raw passwords unless there is a very specific gateway requirement later.
These endpoints are not implemented by the TWA backend. They are part of the deployed auth service and are typically reached from browser clients through same-origin /_auth.
Creates an auth identity.
{
"email": "jane@example.com",
"password": "StrongPassword123!"
}- This creates the auth identity only
- It does not assign the TWA app role
- It returns
email_verified: falsefor new password signups - After signup, the client should complete
GET {AUTH_BASE_URL}/auth/verify-email, then login, then callPOST /api/v1/auth/bootstrap
Marks the auth identity email as verified from the emailed verification link.
- This does not log the user in
- After verification, the client must still call
POST {AUTH_BASE_URL}/auth/login
Requests a fresh verification email without requiring an authenticated session.
{
"email": "jane@example.com"
}- Returns
200 {"sent": true}for unknown, verified, and unverified emails - The client should use this after
email_not_verifiedor when a user says they did not receive the original email
Authenticates the user through authSDK.
{
"email": "jane@example.com",
"password": "StrongPassword123!",
"audience": "twa-api"
}Defined by authSDK, not by the TWA backend contract. For browser clients, the normal success path establishes a cookie-backed session instead of returning browser-managed access and refresh tokens.
- When the verified-email login policy is enabled, correct credentials for an unverified account return
400 {"detail":"Email is not verified.","code":"email_not_verified"} - No authenticated browser session is established in that branch
Refreshes the authenticated authSDK session.
Logs the user out through authSDK and clears the browser session cookies.
Used by auth-service-sdk middleware for session validation.
Returns the CSRF token that browser clients echo on unsafe requests while using cookie-backed sessions.
Used by auth-service-sdk middleware for JWT verification.
Creates or returns the local TWA app user for an already authenticated authSDK user.
This is the first TWA-specific call after successful email verification and successful session login.
Any authenticated authSDK end user with audience twa-api.
Jobseeker example:
{
"role": "jobseeker"
}Employer example:
{
"role": "employer",
"employer_profile": {
"org_name": "Northside Logistics",
"contact_name": "Sam Carter",
"phone": "3145550199"
}
}- Public users may bootstrap only
jobseekeroremployer - Bootstrap is idempotent for the same authenticated user
- A public user cannot switch from
jobseekertoemployeror vice versa after bootstrap without staff intervention - Staff accounts are created internally, not through this endpoint
{
"app_user": {
"id": "5f3e9ca0-4c62-4476-9701-d8dfb09bb201",
"auth_user_id": "9ebf8dc7-8ec1-4c97-a12f-52e7fb75f548",
"email": "jane@example.com",
"auth_provider_role": "user",
"app_role": "jobseeker",
"is_active": true
},
"next_step": "complete_jobseeker_profile"
}Employer bootstrap response example:
{
"app_user": {
"id": "6b8ca13d-6c89-447e-bc80-8f35c406e9c4",
"auth_user_id": "0bc43a5d-1a57-4bc2-8a5e-f2c57f1199f9",
"email": "hiring@northside.com",
"auth_provider_role": "user",
"app_role": "employer",
"is_active": true
},
"next_step": "await_staff_approval"
}409 CONFLICTif an existing local role conflicts with the requested bootstrap role403 FORBIDDENif the authenticated token is not an end-user token
Returns the current authenticated auth context plus local TWA app context.
Any authenticated authSDK user with audience twa-api.
{
"app_user": {
"id": "5f3e9ca0-4c62-4476-9701-d8dfb09bb201",
"auth_user_id": "9ebf8dc7-8ec1-4c97-a12f-52e7fb75f548",
"email": "jane@example.com",
"auth_provider_role": "user",
"app_role": "jobseeker",
"is_active": true,
"created_at": "2026-03-18T23:15:00Z",
"updated_at": "2026-03-18T23:15:00Z"
},
"profile_complete": true,
"employer_review_status": null,
"next_step": null
}If the user is authenticated in authSDK but has not been bootstrapped into TWA yet:
{
"app_user": null,
"profile_complete": false,
"employer_review_status": null,
"next_step": "bootstrap_role"
}Returns the logged-in jobseeker profile.
jobseeker
{
"profile": {
"id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"full_name": "Jane Doe",
"phone": "3145550101",
"address": "123 Main St",
"city": "St. Louis",
"zip": "63103",
"transit_type": "public_transit",
"charges": {
"sex_offense": false,
"violent": false,
"armed": false,
"children": false,
"drug": true,
"theft": false
},
"profile_complete": true,
"status": "active"
}
}Creates or updates the logged-in jobseeker profile.
jobseeker
{
"full_name": "Jane Doe",
"phone": "3145550101",
"address": "123 Main St",
"city": "St. Louis",
"zip": "63103",
"transit_type": "public_transit",
"charges": {
"sex_offense": false,
"violent": false,
"armed": false,
"children": false,
"drug": true,
"theft": false
}
}{
"profile": {
"id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"profile_complete": true,
"updated_at": "2026-03-18T23:15:00Z"
}
}Returns all approved, open job listings, including eligibility information for the logged-in jobseeker.
jobseeker
pagepage_sizesearchcitytransit_requiredis_eligible
{
"items": [
{
"job": {
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate",
"description": "Loading, unloading, and inventory assistance.",
"location_address": "2000 North Broadway",
"city": "St. Louis",
"zip": "63102",
"transit_required": "any",
"transit_accessible": true,
"review_status": "approved",
"lifecycle_status": "open"
},
"is_eligible": false,
"ineligibility_tag": "14.2 miles from your zip code"
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}- Charge-based rejection reasons must never be returned here
- The frontend should use
is_eligibleto disable apply
Returns one approved, open job listing for the logged-in jobseeker.
jobseeker
{
"job": {
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate",
"description": "Loading, unloading, and inventory assistance.",
"location_address": "2000 North Broadway",
"city": "St. Louis",
"zip": "63102",
"transit_required": "any",
"transit_accessible": true,
"review_status": "approved",
"lifecycle_status": "open"
},
"eligibility": {
"is_eligible": true,
"ineligibility_tag": null
}
}Creates an application for the logged-in jobseeker.
jobseeker
{
"job_listing_id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2"
}{
"application": {
"id": "fb6f50c1-8571-48d0-9568-ac22d1f2a8e5",
"jobseeker_id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"job_listing_id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"status": "submitted",
"applied_at": "2026-03-18T23:15:00Z",
"updated_at": "2026-03-18T23:15:00Z"
}
}403 PROFILE_INCOMPLETE409 CONFLICTif already applied422 LISTING_NOT_ELIGIBLE
Returns applications for the logged-in jobseeker.
jobseeker
{
"items": [
{
"id": "fb6f50c1-8571-48d0-9568-ac22d1f2a8e5",
"status": "submitted",
"applied_at": "2026-03-18T23:15:00Z",
"job": {
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate",
"city": "St. Louis",
"lifecycle_status": "open"
}
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Returns the logged-in employer profile and review state.
employer
{
"employer": {
"id": "c61c2014-33e8-4d0e-a7ef-f609d5ab0aeb",
"org_name": "Northside Logistics",
"contact_name": "Sam Carter",
"phone": "3145550199",
"address": "500 Market St",
"city": "St. Louis",
"zip": "63101",
"review_status": "approved",
"review_note": null
}
}Updates the logged-in employer profile.
employer
{
"org_name": "Northside Logistics",
"contact_name": "Sam Carter",
"phone": "3145550199",
"address": "500 Market St",
"city": "St. Louis",
"zip": "63101"
}{
"employer": {
"id": "c61c2014-33e8-4d0e-a7ef-f609d5ab0aeb",
"updated_at": "2026-03-18T23:15:00Z"
}
}Creates a new listing owned by the logged-in employer.
employer
- Employer
review_statusmust beapproved
{
"title": "Warehouse Associate",
"description": "Loading, unloading, and inventory assistance.",
"location_address": "2000 North Broadway",
"city": "St. Louis",
"zip": "63102",
"transit_required": "any",
"disqualifying_charges": {
"sex_offense": false,
"violent": false,
"armed": false,
"children": false,
"drug": false,
"theft": true
}
}{
"listing": {
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"review_status": "pending",
"lifecycle_status": "open",
"transit_accessible": true,
"job_lat": 38.6351,
"job_lon": -90.1885,
"created_at": "2026-03-18T23:15:00Z"
}
}Returns listings owned by the logged-in employer.
employer
pagepage_sizereview_statuslifecycle_status
{
"items": [
{
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate",
"review_status": "approved",
"lifecycle_status": "open",
"created_at": "2026-03-18T23:15:00Z"
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Returns a single listing owned by the logged-in employer.
employer
{
"listing": {
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate",
"description": "Loading, unloading, and inventory assistance.",
"review_status": "approved",
"lifecycle_status": "open",
"transit_accessible": true,
"review_note": null
}
}Returns applicants for a listing owned by the logged-in employer.
employer
- Listing must belong to the authenticated employer
share_applicants_with_employermust betrue
{
"items": [
{
"application_id": "fb6f50c1-8571-48d0-9568-ac22d1f2a8e5",
"status": "submitted",
"applied_at": "2026-03-18T23:15:00Z",
"jobseeker": {
"id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"full_name": "Jane Doe",
"phone": "3145550101",
"address": "123 Main St",
"city": "St. Louis",
"zip": "63103",
"transit_type": "public_transit",
"charges": {
"sex_offense": false,
"violent": false,
"armed": false,
"children": false,
"drug": true,
"theft": false
}
}
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}403 APPLICANT_VISIBILITY_DISABLED
Returns summary metrics for the staff dashboard.
staff
{
"pending_employers": 3,
"pending_listings": 5,
"active_jobseekers": 42,
"open_applications": 16,
"open_listings": 12
}Returns employers pending review.
staff
{
"items": [
{
"id": "c61c2014-33e8-4d0e-a7ef-f609d5ab0aeb",
"org_name": "Northside Logistics",
"contact_name": "Sam Carter",
"review_status": "pending",
"created_at": "2026-03-18T23:15:00Z"
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Updates employer review status.
staff
{
"review_status": "approved",
"review_note": "Approved after manual review."
}pending -> approvedpending -> rejectedrejected -> approvedapproved -> rejected
{
"employer": {
"id": "c61c2014-33e8-4d0e-a7ef-f609d5ab0aeb",
"review_status": "approved",
"review_note": "Approved after manual review.",
"reviewed_by": "bd29b4dc-bd93-42f8-9d1e-98f2019c13aa",
"reviewed_at": "2026-03-18T23:15:00Z"
}
}Returns all employers with filter support.
staff
pagepage_sizereview_statussearch
{
"items": [
{
"id": "c61c2014-33e8-4d0e-a7ef-f609d5ab0aeb",
"org_name": "Northside Logistics",
"review_status": "approved"
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Returns listings pending review.
staff
{
"items": [
{
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate",
"employer": {
"id": "c61c2014-33e8-4d0e-a7ef-f609d5ab0aeb",
"org_name": "Northside Logistics"
},
"review_status": "pending",
"lifecycle_status": "open",
"created_at": "2026-03-18T23:15:00Z"
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Updates listing review state or lifecycle state.
staff
{
"review_status": "approved",
"lifecycle_status": "open",
"review_note": "Transit check passed."
}Alternative close example:
{
"lifecycle_status": "closed",
"review_note": "Closed after hire."
}pending -> approvedpending -> rejectedrejected -> approvedapproved -> rejectedopen -> closed
{
"listing": {
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"review_status": "approved",
"lifecycle_status": "open",
"review_note": "Transit check passed.",
"reviewed_by": "bd29b4dc-bd93-42f8-9d1e-98f2019c13aa",
"reviewed_at": "2026-03-18T23:15:00Z"
}
}Returns all listings with filters.
staff
pagepage_sizereview_statuslifecycle_statusemployer_idcitysearch
{
"items": [
{
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate",
"review_status": "approved",
"lifecycle_status": "open"
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Returns all jobseekers with filters.
staff
pagepage_sizesearchstatustransit_typecharge_sex_offensecharge_violentcharge_armedcharge_childrencharge_drugcharge_theft
{
"items": [
{
"id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"full_name": "Jane Doe",
"city": "St. Louis",
"zip": "63103",
"transit_type": "public_transit",
"status": "active"
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Returns a full staff view of a jobseeker, including applications.
staff
{
"jobseeker": {
"id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"full_name": "Jane Doe",
"phone": "3145550101",
"address": "123 Main St",
"city": "St. Louis",
"zip": "63103",
"transit_type": "public_transit",
"charges": {
"sex_offense": false,
"violent": false,
"armed": false,
"children": false,
"drug": true,
"theft": false
},
"status": "active"
},
"applications": [
{
"id": "fb6f50c1-8571-48d0-9568-ac22d1f2a8e5",
"status": "submitted",
"job_listing_id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2"
}
]
}Updates a jobseeker profile as staff.
staff
{
"full_name": "Jane Doe",
"phone": "3145550101",
"address": "123 Main St",
"city": "St. Louis",
"zip": "63103",
"transit_type": "both",
"charges": {
"sex_offense": false,
"violent": false,
"armed": false,
"children": false,
"drug": true,
"theft": false
},
"status": "active"
}{
"jobseeker": {
"id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"updated_at": "2026-03-18T23:15:00Z"
}
}Returns all open, approved listings for a specific jobseeker, including staff-visible eligibility reasons.
staff
{
"items": [
{
"job": {
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate",
"city": "St. Louis"
},
"is_eligible": false,
"ineligibility_reasons": [
"charge_theft_disqualified",
"transit_unreachable"
]
}
]
}Returns all active jobseekers for a specific listing, including staff-visible eligibility reasons.
staff
{
"items": [
{
"jobseeker": {
"id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"full_name": "Jane Doe",
"city": "St. Louis"
},
"is_eligible": true,
"ineligibility_reasons": []
}
]
}Returns all applications with filters.
staff
pagepage_sizestatusjob_listing_idjobseeker_idemployer_id
{
"items": [
{
"id": "fb6f50c1-8571-48d0-9568-ac22d1f2a8e5",
"status": "submitted",
"applied_at": "2026-03-18T23:15:00Z",
"jobseeker": {
"id": "8de8d1a0-f72c-4306-b72b-cb4a735de4e1",
"full_name": "Jane Doe"
},
"job": {
"id": "56d0a9b0-876b-47a9-b84a-4cd80d1fd7e2",
"title": "Warehouse Associate"
}
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Updates application status.
staff
{
"status": "hired"
}submitted -> reviewedreviewed -> hiredsubmitted -> hired
{
"application": {
"id": "fb6f50c1-8571-48d0-9568-ac22d1f2a8e5",
"status": "hired",
"updated_at": "2026-03-18T23:15:00Z"
}
}- Marking one application as
hireddoes not block future applications elsewhere - Staff may separately close the listing
Returns notification settings.
staff
{
"notify_staff_on_apply": true,
"notify_employer_on_apply": false,
"share_applicants_with_employer": false,
"updated_by": "bd29b4dc-bd93-42f8-9d1e-98f2019c13aa",
"updated_at": "2026-03-18T23:15:00Z"
}Updates notification settings.
staff
{
"notify_staff_on_apply": true,
"notify_employer_on_apply": true,
"share_applicants_with_employer": true
}{
"config": {
"notify_staff_on_apply": true,
"notify_employer_on_apply": true,
"share_applicants_with_employer": true,
"updated_by": "bd29b4dc-bd93-42f8-9d1e-98f2019c13aa",
"updated_at": "2026-03-18T23:15:00Z"
}
}Returns audit entries with filters.
staff
pagepage_sizeactor_identity_typeentity_idactiondate_fromdate_to
{
"items": [
{
"id": "ba2cdd31-986f-4e11-a5a2-7ef1d3322f65",
"actor_id": null,
"action": "gtfs_feed_refreshed",
"entity_type": "system",
"entity_id": null,
"old_value": null,
"new_value": {
"listings_recomputed": 42
},
"timestamp": "2026-03-18T23:15:00Z"
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Returns in-app notifications for the current user.
Any authenticated role.
pagepage_sizeunread_only
{
"items": [
{
"id": "7db67d92-a43b-4f8f-adb5-335d2d210426",
"type": "application_submitted",
"channel": "in_app",
"title": "New application received",
"body": "Jane Doe applied to Warehouse Associate.",
"read_at": null,
"created_at": "2026-03-18T23:15:00Z"
}
],
"meta": {
"page": 1,
"page_size": 20,
"total_items": 1,
"total_pages": 1
}
}Marks a notification as read.
Any authenticated role.
{
"notification": {
"id": "7db67d92-a43b-4f8f-adb5-335d2d210426",
"read_at": "2026-03-18T23:15:00Z"
}
}TWA authorization is based on the local TWA app user, not the raw authSDK token role.
Authorization flow:
authSDKauthenticates the user and establishes a session with audiencetwa-api- The TWA backend validates the session and reads
subasauth_user_id - The TWA backend resolves the local TWA app user by
auth_user_id - The TWA backend authorizes routes using the local
app_role:jobseeker,employer, orstaff
A job is visible on the jobseeker board only when:
- the caller is authenticated and locally assigned the
jobseekerrole review_status = approvedlifecycle_status = open
A jobseeker may apply only when:
- authenticated through
authSDK - locally assigned the
jobseekerrole - profile is complete
- listing is approved and open
- listing is eligible for the jobseeker
- the jobseeker has not already applied to that listing
An employer may create listings only when:
- authenticated through
authSDK - locally assigned the
employerrole - employer
review_status = approved
Staff routes require:
- authenticated through
authSDK - locally assigned the
staffrole in the TWA database
Employers may view applicants only when:
- they own the listing
- staff has enabled
share_applicants_with_employer
When enabled, the employer receives the full applicant profile including charge fields, based on the clarified business rule.
- Jobseekers never receive charge-based ineligibility reasons
- Staff receives full eligibility reasoning
- Employers do not receive matching screens unless future requirements add them
The backend must write audit entries for:
- employer review changes
- listing review changes
- listing closure
- jobseeker edits by staff
- application status changes
- notification config changes
- GTFS refresh and other system maintenance events
Recommended audit action strings:
employer_approvedemployer_rejectedlisting_approvedlisting_rejectedlisting_closedjobseeker_updatedapplication_submittedapplication_reviewedapplication_hirednotification_config_updatedgtfs_feed_refreshed
backend/app/routers/
auth.py
jobseekers.py
jobs.py
applications.py
employer.py
admin.py
notifications.py
These are not blockers for the contract, but should be decided during implementation:
- Whether non-browser bearer-token clients should remain a first-class supported integration surface
- Whether jobseeker profile completion should be one PATCH call or a multi-step wizard
- Whether in-app notifications should support categories, links, or action buttons
- Whether staff should be able to reopen closed listings later
After this contract, the next most useful document is:
- a database schema and migration spec with final tables, constraints, indexes, and audit behavior