Back to Technical Documentation

Backend Api

Edit on GitHub

Backend API Reference

The Equalify backend is a single AWS Lambda function that acts as a router, directing requests to appropriate handlers based on the URL path and authentication status.

Entry Point

The main handler (apps/backend/index.ts) routes requests based on path prefixes:

Path PrefixRouterAuthentication
/publicpublicRouterNone
/authauthRouterJWT required
/internalinternalRouterInternal only
/scheduledscheduledRouterEventBridge
/hasurahasuraRouterWebhook secret
Cognito triggerscognitoRouterCognito events

Route Organization

Public Routes (/public)

Endpoints that don't require authentication:

EndpointDescription
createUserSign up a new user
authenticateLog in and issue tokens
checkIfUserExistsCheck if an email is already registered
scanWebhookReceives scan results from scanner Lambdas
getAuditChartGet chart data for a shared/public audit
getAuditTableGet paginated blocker table for a shared/public audit
exportAuditTableExport blocker table for a shared/public audit
getAuditSummaryGet summary stats for a shared/public audit
getAuditSummaryFastGet cached/fast summary stats for a shared/public audit
getMostCommonUrlsPaginated "URLs with most blockers" list for a shared/public audit
getMostCommonBlockersPaginated "most common blockers" list for a shared/public audit
getAuditRecommendationsBlockers grouped by content hash, ranked by occurrences, for a shared/public audit
getBlockerUrlsPaginated list of every occurrence (URL) of one grouped blocker for a shared/public audit
putMetricsRecord frontend telemetry metrics
getBlockerSummaryGet AI-generated blocker summary
flagBlockerSummaryFlag an AI-generated blocker summary
requestAccessRequest access under SSO (admin review required)
Note: the router matches on path only (apps/backend/utils/router.ts) — it does not check HTTP method, so the Method column below reflects convention, not enforcement.

Auth Routes (/auth)

Endpoints requiring a valid JWT token:

EndpointMethodDescription
getAccountGETRetrieve user account details
updateUserPOSTUpdate user profile
saveAuditPOSTCreate a new audit
updateAuditPOSTModify an existing audit
deleteAuditPOSTDelete an audit
rescanAuditPOSTTrigger a new scan for an audit
getAuditDetailsGETGet audit configuration
getAuditResultsGETGet scan results with blockers
getAuditChartGETGet chart data for blockers over time
getAuditTableGETGet paginated blocker table data
getAuditProgressGETGet current scan progress
getAuditSummaryGETGet summary stats for an audit
getAuditSummaryFastGETGet cached/fast summary stats for an audit
getMostCommonUrlsGETPaginated "URLs with most blockers" list (backs the Summary View card)
getMostCommonBlockersGETPaginated "most common blockers" list (backs the Summary View card)
getAuditRecommendationsGETRecommendations view: one row per unique blocker (content_hash_id) in the latest scan, ranked by occurrences; status (active / ignored / all), repeat (all / repeated / single), page, pageSize
getBlockerUrlsGETEvery occurrence of one grouped blocker in the latest scan, one row per occurrence; content_hash_id, page, pageSize (backs the "View all occurrences" drawer and its CSV copy)
exportAuditTableGETExport blocker table as CSV
exportAuditTablePdfSourceLinksGETExport PDF source links for blockers
getLogsGETGet activity logs
inviteUserPOSTInvite a new user
trackUserPOSTTrack user analytics events
trackSessionPOSTRecord an authenticated app load or login (optionally with SSO org fields from Microsoft Graph) for monthly KPIs
saveQuickScanPOSTRun and save a one-off quick scan
getQuickScansGETList quick scans
fetchRemoteCsvPOSTFetch a remote CSV of URLs
syncFromRemoteCsvPOSTSync audit URLs from a remote CSV
crawlUrlPOSTCrawl a URL to discover pages
getBedrockModelsGETList available Bedrock models for AI features
getSystemStatsGETGet system-wide usage stats plus a month-over-month KPI series (admin)
getAccessRequestsGETList pending SSO access requests (admin)
reviewAccessRequestPOSTApprove or deny an SSO access request (admin)

Scheduled Routes (/scheduled)

Endpoints triggered by AWS EventBridge:

  • runEveryMinute - Processes scheduled audits and triggers scans; also closes out stuck scans (no webhook activity for STUCK_SCAN_INACTIVITY_MINUTES, default 60) - see Scanning Services → Stuck Scans and Timeouts
  • runEveryDay - Daily scheduled tasks (e.g. processing scheduled audit emails)

Hasura Routes (/hasura)

Webhook endpoints for Hasura event triggers (protected by webhook secret).

Authentication Flow

JWT Verification

// For Cognito tokens
const verifier = CognitoJwtVerifier.create({
  userPoolId: process.env.USER_POOL_ID,
  tokenUse: "id",
  clientId: process.env.WEB_CLIENT_ID
});
const claims = await verifier.verify(token);

// For SSO tokens
const rawClaims = await verifySsoToken(token);
const { normalizedClaims, hasuraClaims } = await ensureSsoUser(rawClaims);

Token Structure

Tokens include Hasura claims for GraphQL authorization:

{
  "sub": "user-uuid",
  "email": "user@example.com",
  "https://hasura.io/jwt/claims": {
    "x-hasura-allowed-roles": ["user"],
    "x-hasura-default-role": "user",
    "x-hasura-user-id": "user-uuid",
    "x-hasura-org-id": "org-uuid"
  }
}

Note: Hasura only defines anonymous and user roles — there is no admin Hasura role. Admin-only endpoints (e.g. getSystemStats, reviewAccessRequest) are enforced at the Lambda layer, not via Hasura permissions.

Database Access

Direct PostgreSQL

Using serverless-postgres for connection pooling:

import { db } from '#src/utils';

await db.connect();
const result = await db.query({
  text: `SELECT * FROM "audits" WHERE "id" = $1`,
  values: [auditId],
});
await db.clean();

GraphQL via Hasura

For complex queries with relationships:

import { graphqlQuery } from '#src/utils';

const response = await graphqlQuery({
  query: `query ($audit_id: uuid!) {
    audits_by_pk(id: $audit_id) {
      scans(order_by: {created_at: desc}, limit: 1) {
        blockers { ... }
      }
    }
  }`,
  variables: { audit_id: auditId },
});

Creating an Audit

When a user creates an audit (saveAudit):

  • Insert audit record with configuration
  • Insert all URLs associated with the audit
  • If saveAndRun is true:
  • Create a new scan record
  • Hand the URLs to the scan router Lambda via queueScanJobs
// apps/backend/utils/queueScanJobs.ts - shared by saveAudit, saveQuickScan,
// rescanAudit and the scheduled runEveryMinute job
await queueScanJobs({ auditId, scanId, urls });

queueScanJobs builds { auditId, scanId, urlId, url, type, isStaging } jobs and invokes aws-lambda-scan-sqs-router asynchronously, splitting the list into invokes of at most 512 KB because async Lambda invokes reject payloads over 1 MB (about 3,500 URLs). If an invoke fails the error propagates to the caller, and pages that never reached the queue are closed out later by the stuck-scan sweep.

Webhook Processing

The scanWebhook endpoint processes results from scanners:

  • Validate required fields (auditId, urlId)
  • Handle failed scans (log error, update progress)
  • For successful scans:
  • Store blockers with content hashing for deduplication
  • Link blockers to messages (accessibility rules) and tags
  • Update scan progress atomically
  • Mark scan complete when all URLs processed, denormalizing blocker_count/equalified_count onto the scans row

Environment Variables

VariableDescription
DB_USERPostgreSQL username
DB_HOSTPostgreSQL host
DB_NAMEDatabase name
DB_PASSWORDDatabase password / Hasura admin secret
USER_POOL_IDCognito User Pool ID
WEB_CLIENT_IDCognito Web Client ID
SSO_ENABLEDEnable SSO authentication
SSO_CLIENT_IDAzure AD SSO client ID
SSO_TENANTAzure AD tenant ID
SSO_JWKSAzure AD JWKS endpoint for SSO token verification
WEBHOOKSECRETSecret for Hasura webhooks
GRAPHQL_URLHasura GraphQL endpoint used by graphqlQuery
SQS_ROUTER_FUNCTION_NAMEFunction name of the SQS Router Lambda invoked by queueScanJobs (from saveAudit, saveQuickScan, rescanAudit, runEveryMinute)
STUCK_SCAN_INACTIVITY_MINUTESMinutes without a webhook result before runEveryMinute closes a scan out as timed out (default 60)
CRAWLER_FUNCTION_NAMEFunction name of the crawler Lambda invoked from crawlUrl

For frontend integration details, see the Frontend Guide.