This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
minfraud-api-node is MaxMind's official Node.js/TypeScript client library for the minFraud web services:
- minFraud Score: Returns a risk score (0.01-99) indicating fraud likelihood
- minFraud Insights: Score + additional data about IP, email, device, and shipping/billing addresses
- minFraud Factors: Insights + risk score reasons and subscores (deprecated)
- Report Transactions API: Report transaction outcomes to improve fraud detection
The library is server-side only and provides strongly-typed request/response models for fraud detection.
Key Technologies:
- TypeScript with strict type checking
- Node.js 18+ (targets active LTS versions)
- Uses Node.js built-in
fetchfor HTTP requests - Jest for testing
- ESLint + Prettier for code quality
- TypeDoc for API documentation
- Depends on @maxmind/geoip2-node for IP geolocation data
src/
├── request/ # Transaction input models (Device, Email, Billing, etc.)
│ ├── transaction.ts # Main Transaction class that aggregates all inputs
│ └── *.ts # Individual request component classes
├── response/
│ ├── models/ # Response models (Score, Insights, Factors)
│ ├── records.ts # TypeScript interfaces for response data records
│ └── web-records.ts # Raw API response types (snake_case)
├── webServiceClient.ts # HTTP client for minFraud web services
├── constants.ts # Enums for API values (EventType, Processor, etc.)
├── utils.ts # Utility functions (camelCase/snake_case conversion)
├── errors.ts # Custom error classes
└── types.ts # Type definitions
Requests use camelCase and are converted to snake_case for the API:
// User provides: new Device({ ipAddress: '1.2.3.4' })
// Sent to API: { ip_address: '1.2.3.4' }Responses use snake_case and are converted to camelCase in model constructors:
// API returns: { risk_score: 50, funds_remaining: 10.50 }
// Model exposes: response.riskScore, response.fundsRemainingThe camelizeResponse() utility in utils.ts handles deep conversion for response data.
The snakecaseKeys() utility converts request objects to snake_case.
Response models follow clear inheritance:
Score→ base model with risk score, disposition, warningsInsightsextendsScore→ adds billing/shipping addresses, device, email, credit card dataFactorsextendsInsights→ adds risk score reasons and subscores (deprecated)
Transactions are composed of optional components:
const transaction = new minFraud.Transaction({
device: new minFraud.Device({ ipAddress: '1.2.3.4' }),
email: new minFraud.Email({ address: 'test@example.com' }),
billing: new minFraud.Billing({ /* ... */ }),
// ... other components
});Each component validates its inputs in the constructor and throws ArgumentError for invalid data.
The WebServiceClient provides direct methods for each endpoint:
const client = new WebServiceClient(accountID, licenseKey, timeout, host);
const response = await client.score(transaction);
const response = await client.insights(transaction);
const response = await client.factors(transaction);
await client.reportTransaction(report);IMPORTANT: When defining enum-like fields with a fixed set of values:
-
Define union types in
web-records.tsfor the snake_case API response:export type CreditCardType = 'charge' | 'credit' | 'debit'; export type EmailDomainClassification = 'business' | 'education' | 'government' | 'isp_email';
-
Import and reuse these types in
records.tsfor the camelCase public API:import { CreditCardType, DispositionAction, DispositionReason, EmailDomainClassification, EmailDomainVisitStatus, } from './web-records'; export interface CreditCardRecord { readonly type: CreditCardType; // Use the imported type, NOT string }
Do NOT use plain string type when a union type exists in web-records.ts. This pattern:
- Provides better type safety for library consumers
- Maintains a single source of truth for enum values
- Follows the established pattern for
CreditCardType,DispositionAction,DispositionReason, etc.
Exception: Use plain string only when the API documentation explicitly states "additional values may be added in the future" AND you want to allow any string value (e.g., phone.numberType).
The Insights and Factors responses include IP geolocation data from the @maxmind/geoip2-node library:
// In Insights constructor:
const insights = new GeoInsights(response.ip_address) as records.IpAddress;
// Augment with minFraud-specific fields
insights.country.isHighRisk = response.ip_address.country.is_high_risk;
insights.risk = response.ip_address.risk;# Install dependencies
npm install
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run specific test file
npx jest src/webServiceClient.spec.ts# Lint code (ESLint + TypeScript)
npm run lint
# Format code (Prettier)
npm run prettier:ts
npm run prettier:json
# Build TypeScript
npm run build
# Build and deploy documentation
npm run build:docs
npm run deploy:docsTests use .spec.ts files co-located with source:
src/webServiceClient.spec.ts- Web service client testssrc/request/*.spec.ts- Request component testssrc/response/models/*.spec.ts- Response model testssrc/utils.spec.ts- Utility function testse2e/- End-to-end integration tests (JS and TS)
Tests typically use nock to mock HTTP responses:
import nock from 'nock';
nock('https://minfraud.maxmind.com')
.post('/minfraud/v2.0/score')
.reply(200, { risk_score: 50, id: '...', /* ... */ });
const response = await client.score(transaction);
expect(response.riskScore).toBe(50);When adding new fields to models:
- Update test fixtures/mocks to include the new field
- Add assertions to verify the field is properly mapped
- Test both presence and absence (undefined handling)
- Verify camelCase conversion from snake_case source
-
Add the property to the component class with validation:
/** * Description of the field. */ public fieldName?: Type;
-
Update the constructor if validation is needed:
if (props.fieldName && !isValid(props.fieldName)) { throw new ArgumentError('Invalid fieldName'); } this.fieldName = props.fieldName;
-
Add to the interface (e.g.,
DeviceProps,EmailProps) for type safety -
Add tests that verify validation and serialization
-
Update CHANGELOG.md with the change
-
Add the property with proper type annotation:
/** * Description of the field, including availability (which endpoints). */ public readonly fieldName?: Type;
-
Update the constructor to map from the response:
// For simple fields: this.fieldName = response.field_name; // For nested objects that need camelization: this.fieldName = this.maybeGet<records.FieldType>(response, 'field_name');
-
Update corresponding record interfaces in
records.tsorweb-records.ts -
Add tests that verify the field mapping and type
-
Update CHANGELOG.md with the change
When the API adds new enum values (e.g., new payment processors, event types):
-
Update
src/constants.tswith the new value:export enum Processor { Stripe = 'stripe', NewProcessor = 'new_processor', // ... }
-
Add tests if the enum has validation logic
-
Update CHANGELOG.md with the change
Always update CHANGELOG.md for user-facing changes.
Important: Do not add a date to changelog entries until release time.
- If the next version doesn't exist, create it as
X.Y.Z (unreleased)or justX.Y.Z(the header format varies) - If it already exists without a date, add your changes there
- The release date will be added when the version is actually released
8.2.0
------------------
* Added `NewProcessor` to the `Processor` enum.
* Added the output `/email/first_seen`. This is available as the
`firstSeen` property on `response.email`.API responses use snake_case but must be exposed as camelCase.
Solution: Use camelizeResponse() for nested objects:
this.email = this.maybeGet<records.Email>(response, 'email');
private maybeGet<T>(response: Response, prop: keyof Response): T | undefined {
return response[prop] ? (camelizeResponse(response[prop]) as T) : undefined;
}Request components should validate inputs in constructors.
Solution: Import validators from the validator package:
import validator from 'validator';
if (!validator.isEmail(props.address)) {
throw new ArgumentError('Invalid email address');
}The client can throw various error types.
Solution: Check for error codes in catch blocks:
try {
const response = await client.score(transaction);
} catch (error) {
// error has shape: { code: string, error: string, url?: string }
if (error.code === 'INSUFFICIENT_FUNDS') {
// Handle insufficient funds
}
}The IP address data comes from @maxmind/geoip2-node and needs augmentation.
Solution: Cast the GeoInsights object and add minFraud-specific fields:
const insights = new GeoInsights(response.ip_address) as records.IpAddress;
insights.risk = response.ip_address.risk;- TypeScript strict mode - All files use strict type checking
- ESLint - Configured with TypeScript ESLint rules (see
eslint.config.mjs) - Prettier - Consistent formatting enforced
- Readonly response fields - Response model properties are
readonly - Optional chaining - Use
?.for optional nested properties - TypeDoc comments - Document public APIs with JSDoc-style comments
npm install# Tidy code (auto-fix issues)
precious tidy -g
# Lint code (check for issues)
precious lint -g
# Run tests
npm test
# Build
npm run buildNote: Precious is already set up and handles code formatting and linting. Use precious tidy -g to automatically fix issues, and precious lint -g to check for remaining problems.
- Node.js 18+ required (targets active LTS: 18, 20, 22)
- Uses Node.js built-in
fetch(no external HTTP libraries) - TypeScript 5.x
This library is part of MaxMind's multi-language client library ecosystem. When adding features:
- Field names should match other client libraries (PHP, Python, etc.) after camelCase conversion
- Model structure should parallel other implementations where possible
- Error handling patterns should be consistent
- Documentation style should follow established patterns
Refer to other minFraud client implementations for guidance on new features.