Skip to content

API Layer & Hooks

The portal uses a thin HTTP client for API communication and React Query hooks for server state management.

HTTP Client

apiFetch(path, options?)

Generic async HTTP wrapper in api/client.ts:

  • Prepends /api/v1 to the path
  • Automatically injects CSRF token from cookies as X-CSRFToken header
  • Sets Content-Type: application/json for request bodies
  • Includes credentials (credentials: "include") for session cookies
  • Returns parsed JSON response
  • Returns undefined for 204 No Content
  • Throws ApiHttpError on non-2xx responses

ApiHttpError

Custom error class with structured error data:

class ApiHttpError extends Error {
  status: number;
  body: ApiError;
}

interface ApiError {
  message?: string;
  detail?: string;
  code?: string;
  errors?: Record<string, string>;  // field-level errors
}

ensureCsrf()

Hits GET /api/v1/auth/session to establish the CSRF cookie before login.

Auth API (api/auth.ts)

Function Method Path Description
getSession() GET /auth/session Establish CSRF cookie
login(email, password) POST /auth/login Authenticate
logout() DELETE /auth/session End session
getMe() GET /portal/me Get borrower profile

Portal API (api/portal.ts)

41 functions organized by domain:

Configuration & Dashboard

Function Method Path Returns
getBranding() GET /portal/config TenantBranding
getDashboard() GET /portal/dashboard DashboardData

Loans

Function Method Path Returns
getLoans() GET /portal/loans LoanSummary[]
getLoan(loanId) GET /portal/loans/{id} LoanDetail
getAmortization(loanId) GET /portal/loans/{id}/amortization AmortizationEntry[]

Payments

Function Method Path Returns
getPayments(loanId) GET /portal/loans/{id}/payments Payment[]
previewPayment(loanId, input) POST /portal/loans/{id}/payments/preview PaymentPreview
submitPayment(loanId, input) POST /portal/loans/{id}/payments Payment

Payment Instruments

Function Method Path Returns
getInstruments() GET /portal/payment-instruments PaymentInstrument[]
addInstrument(input) POST /portal/payment-instruments PaymentInstrument
deleteInstrument(id) DELETE /portal/payment-instruments/{id} void

Autopay

Function Method Path Returns
getAutopay(loanId) GET /portal/loans/{id}/autopay AutopayPlan \| null
enrollAutopay(loanId, input) POST /portal/loans/{id}/autopay AutopayPlan
cancelAutopay(loanId, autopayId) POST .../autopay/{id}/cancel AutopayPlan
enableAutopay(loanId, autopayId) POST .../autopay/{id}/enable AutopayPlan

Documents

Function Method Path Returns
getDocuments(type?) GET /portal/documents PortalDocument[]
getDownloadUrl(id) GET /portal/documents/{id}/download { download_url }
requestUploadLink(input) POST /portal/documents/upload UploadLinkResponse
confirmUpload(id) POST /portal/documents/{id}/confirm void

Profile

Function Method Path Returns
getProfileDetail() GET /portal/profile ProfileDetail
updateProfile(input) POST /portal/profile ProfileDetail
updateCommPreferences(input) POST /portal/profile/communication-preferences ProfileDetail

Support Cases

Function Method Path Returns
getCases() GET /portal/support/cases SupportCase[]
createCase(input) POST /portal/support/cases SupportCase
getCase(caseId) GET /portal/support/cases/{id} SupportCaseDetail
addCaseMessage(caseId, input) POST /portal/support/cases/{id}/message CaseInteraction

Hardship

Function Method Path Returns
getHardshipApplications() GET /portal/hardship/applications HardshipApplication[]
getHardshipApplication(id) GET /portal/hardship/applications/{id} HardshipApplication
submitHardship(input) POST /portal/hardship/apply HardshipApplication

React Query Hooks

Each hook file wraps portal API functions in React Query useQuery and useMutation hooks.

Query Key Structure

Hook Query Key Stale Time
useDashboard() ["portal", "dashboard"] default
useLoans() ["portal", "loans"] default
useLoan(id) ["portal", "loans", id] default
useAmortization(id) ["portal", "loans", id, "amortization"] default
usePayments(id) ["portal", "loans", id, "payments"] default
useInstruments() ["portal", "instruments"] default
useAutopay(id) ["portal", "loans", id, "autopay"] default
useDocuments(type?) ["portal", "documents", type] default
useProfileDetail() ["portal", "profile"] default
useCases() ["portal", "cases"] default
useCase(id) ["portal", "cases", id] default
useHardshipApplications() ["portal", "hardship"] default
useHardshipApplication(id) ["portal", "hardship", id] default
useBranding() ["portal", "branding"] 5 minutes

Mutation Patterns

Mutations invalidate related queries on success:

// useSubmitPayment invalidates:
// - ["portal", "loans", loanId, "payments"]  (payment list)
// - ["portal", "loans", loanId]              (loan balance)
// - ["portal", "dashboard"]                  (dashboard totals)

// useAddInstrument invalidates:
// - ["portal", "instruments"]

// useUpdateProfile invalidates:
// - ["portal", "profile"]
// - auth context (borrower name may change)

Hook Files

File Hooks
usePortalApi.ts useDashboard, useLoans, useLoan, useAmortization
usePayments.ts usePayments, usePreviewPayment, useSubmitPayment
useInstruments.ts useInstruments, useAddInstrument, useDeleteInstrument
useAutopay.ts useAutopay, useEnrollAutopay, useCancelAutopay, useEnableAutopay
useDocuments.ts useDocuments, useDownloadUrl, useUploadDocument
useProfile.ts useProfileDetail, useUpdateProfile, useUpdateCommPreferences
useCases.ts useCases, useCase, useCreateCase, useAddCaseMessage
useHardship.ts useHardshipApplications, useHardshipApplication, useSubmitHardship
useBranding.ts useBranding

Document Upload Hook

useUploadDocument() handles the three-step upload flow:

  1. Request presigned URL via requestUploadLink()
  2. Upload file directly to the presigned S3 URL via PUT
  3. Confirm upload via confirmUpload()
  4. Invalidates ["portal", "documents"] on success

See Also