PathGuard API

The PathGuard API banner, the project name in white capitals on a black background.

PathGuard is a REST API for KYC checks, the identity verification a service runs before it accepts a customer. It takes a name, an email address and a document type, gives the request a risk score between 0 and 1, and answers with a decision. I wrote it in JavaScript with Express 5 and PostgreSQL.

How a request is scored

The score is a sum of fixed penalties, capped at 1.

Check Penalty
Name longer than 50 characters 0.05
Name with digits or symbols 0.1
Email address that is not well formed 0.2
Generic email domain such as gmail.com 0.15
Disposable email domain such as mailinator.com 0.3
Passport 0.2
ID card or driving licence 0.1

Two thresholds turn the score into a decision. Up to 0.2 the request is approved, up to 0.5 it waits for a manual review, and above that it is rejected. Both thresholds are read from the environment, so they can change without touching the code.

An example

A request with a company address and an ID card gets the lowest score.

curl -X POST http://localhost:3000/api/v1/kyc/verifications \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Alice Dupont",
    "email": "alice@company.com",
    "documentType": "id_card"
  }'
{
  "verification_id": "ver_123e4567-e89b-12d3-a456-426614174000",
  "status": "approved",
  "risk_score": 0.1,
  "checks_performed": ["name", "email", "documentType"],
  "timestamp": "2025-10-05T14:30:00.000Z"
}

The same person with a gmail.com address and a passport scores 0.35 and goes to manual review.

How it is built

The code is split into four layers. Routes declare the endpoints, controllers read the request, services hold the scoring and the decision, and models talk to PostgreSQL through Drizzle ORM.

Every body, query string and path parameter goes through a Zod schema before it reaches a controller. A request that fails gets a 400 response that names the fields at fault.

{
  "error": "Validation failed",
  "details": [
    { "field": "email", "message": "Invalid email format" }
  ]
}

Helmet sets the security headers and Morgan logs each request. The scoring function has its own unit tests, run with Vitest.

The documentation is an OpenAPI file served by Scalar, where each endpoint can be tried from the browser. Requests sent from that page carry a header the API recognises, and it answers them from sample data. Trying the documentation never writes to the database.

Explore the code on