API Reference
API Reference
Multi-tenant REST APIs (/v1/…) for real-time transaction risk scoring, AML monitoring, customer onboarding, and data pipelines. Every call is scoped to your tenant — API keys resolve automatically.
Overview
MaiGuard combines configurable rules, velocity analysis, behavioral and device signals, entity graph, counterparty risk, PEP and sanctions screening via tenant lists, customer KYC/KYB context, and case review workflows.
| Phase | What happens |
|---|---|
| Account | Tenant created; subscription and feature limits assigned. |
| KYB | Business verification; documents submitted via portal. |
| Credentials | TEST and LIVE API keys issued; portal users use JWT session. |
| Configuration | Rules, tenant lists, pipelines, and notifications set up. |
| Integration test | Use TEST key; scored traffic is test-only, does not affect live reporting. |
| Go-live | Switch to LIVE key — no code changes required. |
Note
pk_test_* during integration. Switch to pk_live_* at go-live — no code changes required. Test traffic is excluded from live reporting and analytics.Authentication
Base URL: https://api.maiguard.com. All paths are under /v1/. Breaking changes ship under a new version prefix.
API key (server-to-server)
Authorization: Bearer pk_live_<key>High-volume paths: scoring, imports, webhooks, SDK telemetry. Never embed live keys in browsers.
JWT (portal / management)
Authorization: Bearer <access_token>From POST /v1/auth/login. Used for rules, customers, analytics, and RBAC-protected routes.
Idempotency (scoring)
Idempotency-Key: <unique-string-per-request>Optional header for deduplication on retry. Same key returns the cached response for 24 hours without re-evaluating rules.
Warning
pk_live_* keys in browser or mobile clients. Score transactions from your backend and pass only the result to the client.Payload shapes
Every tenant uses the same POST /v1/transactions/score endpoint. Send only what you have — richer payloads improve rules, velocity, pattern detection, screening, and ML quality.
| Shape | When to use | Monitoring outcome |
|---|---|---|
| Minimal | Always valid — required fields only | Basic scoring; suitable for connectivity tests and low-context flows. |
| Recommended | Device/session context available (IP, device, geo, metadata) | Rules + velocity + pattern detection at production quality. |
| Full / enterprise | Schema 1.1 typed sections — or when strict payload validation is enabled on your tenant | Full screening, AML context, and ML at highest quality. Missing required schema 1.1 sections returns 422 when strict validation is on. |
Note
event, transaction, actor, counterparty, etc.). Requests missing required sections return 422 instead of being scored with degraded context.Only required fields. Always valid for every tenant.
{
"amount": 50000,
"userId": "cust_abc123",
"currency": "NGN"
}Integration channels
REST APIs
All /v1/ endpoints for scoring, onboarding, lists, rules, imports, and analytics.
Use case: Server-to-server scoring; management APIs.
Rate limit: 1,000/min per tenant (score endpoint)
Browser SDK
Collects behavioral biometrics; passes deviceSessionId (must start with ds_) into score requests.
Use case: Web flows needing device linkage and pattern analysis.
Rate limit: Confirm with MaiGuard contact
Webhooks
Inbound: provider-signed events into MaiGuard. Outbound: MaiGuard-signed decisions to your HTTPS endpoint.
Use case: Event-driven ingestion and real-time alerts.
Rate limit: 1,000/min per tenant path (inbound)
Database pipeline
POST /v1/integrations/database — schedule or manually sync from PostgreSQL, MySQL, or MongoDB.
Use case: Scheduled pulls from your production DB.
Rate limit: AWS/DB quotas apply; use read-only credentials
SQS pipeline
POST /v1/integrations/sqs — maps SQS message fields to MaiGuard transaction fields.
Use case: High-throughput, decoupled AWS-native ingestion.
Rate limit: AWS queue quotas apply
File import
POST /v1/transactions/import (multipart) for CSV, JSON, or JSONL. Poll via importId.
Use case: Historical backfill and batch loads.
Rate limit: 5 imports/hour per tenant
Score transaction
/v1/transactions/scoreSynchronous scoring. The transaction is queued for persistence after the response; use the returned transactionId to correlate with webhooks and GET /v1/transactions.
1// POST /v1/transactions/score (API key; server-to-server only)
2const response = await fetch('https://api.maiguard.com/v1/transactions/score', {
3 method: 'POST',
4 headers: {
5 'Content-Type': 'application/json',
6 'Authorization': 'Bearer pk_live_<your-key>',
7 'Idempotency-Key': 'order-987654-score-v1', // optional; dedup 24 h
8 },
9 body: `{
10 "amount": 50000.50,
11 "userId": "cust_61e46cf7",
12 "currency": "NGN",
13 "occurredAt": "2026-05-29T10:30:00.000Z",
14 "ipAddress": "197.210.84.42",
15 "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
16 "deviceId": "7ec1a4175dd3776f4cb534b1287749811b5512ad60f5d0ba1f10c7974ffd2edc",
17 "deviceSessionId": "ds_g7xSevOyd2oUAQCfVuya0g",
18 "location": {
19 "latitude": 6.4474,
20 "longitude": 3.3903
21 },
22 "metadata": {
23 "countryCode": "NG",
24 "receiverId": "rcv_beneficiary_001",
25 "product": "Collections",
26 "paymentMethod": "card",
27 "environment": "LIVE",
28 "type": "purchase",
29 "merchantId": "merchant_456",
30 "category": "electronics",
31 "orderId": "ord_987654",
32 "channel": "web"
33 }
34}`,
35});ALLOWTransaction approved — proceed with payment.
REVIEWElevated risk — flag for manual review.
BLOCKHigh risk — decline the transaction.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | required | Stable customer ID. Must match customerId in onboarding calls. |
amount | number | required | Transaction amount. Must be a positive number. |
currency | string | required | ISO 4217 currency code (e.g. NGN, USD, GHS). |
occurredAt | string | optional | ISO 8601 timestamp when the transaction occurred. |
ipAddress | string | optional | Client IP address used for velocity and geo signals. |
userAgent | string | optional | Browser or app user-agent string. |
deviceId | string | optional | Device fingerprint from your own system or SDK. |
deviceSessionId | string | optional | Device session from Browser SDK. Must start with ds_. |
location | object | optional | Geographic coordinates of the transaction. |
location.latitude | number | optional | Geographic latitude. |
location.longitude | number | optional | Geographic longitude. |
loginContext | object | optional | Login context for ATO signals. Common in Full / enterprise payloads. |
loginContext.minsSinceLastLogin | number | optional | Minutes since the user's last successful login. |
loginContext.failedAttemptsBeforeLogin | number | optional | Failed login attempts immediately before this success. |
loginContext.loginIpMatchesTxIp | boolean | optional | Whether the login IP matches the transaction IP. |
loginContext.isFirstLoginFromDevice | boolean | optional | Whether this is the first login from this device. |
includeMatchedConditions | boolean | optional | Return matched rule conditions in the response (default: false). |
metadata | object | optional | Business context for rules and segmentation. See Recommended shape. |
metadata.countryCode | string | optional | ISO country code of the transaction. |
metadata.receiverId | string | optional | Counterparty ID for entity graph linkage. |
metadata.product | string | optional | Product line (e.g. Collections, Payouts). |
metadata.paymentMethod | string | optional | Payment method (e.g. card, bank_transfer, mobile_money). |
metadata.environment | string | optional | LIVE or TEST. |
metadata.type | string | optional | Transaction type (e.g. purchase, transfer). |
metadata.merchantId | string | optional | Merchant identifier. |
metadata.category | string | optional | Product or merchant category. |
metadata.orderId | string | optional | Partner order or transaction reference. |
metadata.channel | string | optional | Channel (e.g. web, mobile, api). |
event | object | optional | Full / enterprise — event envelope (schema 1.1). |
event.schemaVersion | string | optional | Payload schema version (e.g. 1.1). |
transaction | object | optional | Full / enterprise — transaction semantics: kind, direction, channel, paymentMethod, product. |
actor | object | optional | Full / enterprise — initiating party: id, type, countryCode. |
account | object | optional | Full / enterprise — source account: id, number, type, currency. |
counterparty | object | optional | Full / enterprise — beneficiary for screening: id, name, type, countryCode, bankCode. |
merchant | object | optional | Full / enterprise — merchant context: id, countryCode, merchantCategoryCode. |
session | object | optional | Full / enterprise — auth session: sessionId, deviceSessionId, loginMethod, mfaUsed, mfaMethod. |
device | object | optional | Full / enterprise — device profile: platform, browser, timezone, isRooted, isEmulator, appVersion. |
network | object | optional | Full / enterprise — network context: ipAddress, userAgent. |
geo | object | optional | Full / enterprise — geo with countryCode: latitude, longitude, countryCode. |
behavioral | object | optional | Full / enterprise — behavioral biometrics (e.g. typingSpeed, touchPressureVariance). |
Response — 200 OK
{
"success": true,
"data": {
"transactionId": "550e8400-e29b-41d4-a716-446655440000",
"riskScore": 42,
"decision": "REVIEW",
"matchedRules": [
{
"ruleId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"ruleName": "High velocity — 1 hour",
"action": "REVIEW",
"priority": 10,
"matchedConditions": [{ "field": "velocity", "operator": "greater_than", "value": 5 }]
}
],
"velocity": 6,
"shadowEvaluation": {
"hasShadowRules": true,
"shadowDecision": "BLOCK",
"shadowRiskScore": 78
},
"patternAnalysis": {
"hasAnomalies": true,
"totalAnomalyScore": 35,
"amountAnomaly": { "deviation": 2.4, "isAnomaly": true },
"botDetection": { "isBot": false, "score": 12 }
},
"mlEvaluation": {
"available": true,
"score": 0.72,
"confidence": 0.85,
"modelVersion": "global_txn_autogluon_live_v1",
"blended": true
}
}
}Response — idempotent replay
When Idempotency-Key matches a prior request within 24 hours, the API returns the cached body with cached: true and does not re-run rules.
{
"success": true,
"cached": true,
"data": {
"transactionId": "550e8400-e29b-41d4-a716-446655440000",
"riskScore": 42,
"decision": "REVIEW",
"matchedRules": [],
"velocity": 6
}
}Response fields
| Field | Description |
|---|---|
transactionId | Server-generated UUID for this score (persisted asynchronously). |
riskScore | 0–100 composite risk. Higher = riskier. |
decision | ALLOW | REVIEW | BLOCK |
matchedRules | Rules that fired. matchedConditions only when request included includeMatchedConditions: true. |
velocity | Transaction count for this user in the last hour (rule input). |
shadowEvaluation | Optional — shadow rules only; does not change decision. |
patternAnalysis | Optional — behavioral/device/amount anomalies when pattern engine runs. |
mlEvaluation | Optional — ML score blend metadata when tenant ML is enabled. |
Response — 400 / 422 validation
Malformed or invalid field values return 400. Tenants with strict payload validation enabled return 422 when required Full / enterprise sections are missing.
{
"error": "Validation Error",
"message": "Expected positive number",
"details": [
{ "code": "too_small", "path": ["amount"], "message": "Expected positive number" }
]
}{
"error": "Validation Error",
"message": "Strict payload validation enabled — missing required sections",
"details": [
{ "code": "invalid_type", "path": ["event"], "message": "Required for schema 1.1 payloads" },
{ "code": "invalid_type", "path": ["counterparty"], "message": "Required for AML screening context" }
]
}400Validation error — check details[] array for path and message.401Invalid or missing API key.403Plan limit exceeded or KYB_APPROVAL_REQUIRED (TEST key).422Strict payload validation — missing required schema 1.1 sections.429Rate limited — honor Retry-After header.503Persistence queue unavailable — retry with backoff.Batch score
/v1/transactions/batch-scoreUp to 1,000 transactions per request. Synchronous for batches of 100 or fewer; add ?async=true or send more than 100 items for a 202 response with a jobId to poll via GET /v1/jobs/:jobId.
1// POST /v1/transactions/batch-score
2// Sync (≤100 items): immediate results. Async: ?async=true or >100 → 202 + jobId
3const response = await fetch('https://api.maiguard.com/v1/transactions/batch-score?async=false', {
4 method: 'POST',
5 headers: {
6 'Content-Type': 'application/json',
7 'Authorization': 'Bearer pk_live_<your-key>',
8 },
9 body: `{
10 "transactions": [
11 { "amount": 5000, "userId": "user_01", "currency": "NGN", "includeMatchedConditions": true },
12 { "amount": 25000, "userId": "user_02", "currency": "USD" }
13 ],
14 "idempotencyKeys": ["idem-01", "idem-02"]
15}`,
16});Response — 200 OK (sync)
Each element in results is one scored transaction. Optional idempotencyKeys align by array index.
{
"success": true,
"data": {
"processed": 3,
"succeeded": 2,
"failed": 1,
"results": [
{ "index": 0, "success": true, "transactionId": "660e…", "riskScore": 12, "decision": "ALLOW" },
{ "index": 1, "success": true, "cached": true, "transactionId": "770e…", "riskScore": 88, "decision": "BLOCK" },
{ "index": 2, "success": false, "error": "Plan limit exceeded" }
]
}
}Response — 202 Accepted (async)
{
"success": true,
"data": {
"jobId": "job_uuid",
"status": "PENDING",
"totalItems": 250,
"message": "Job queued for processing"
}
}Login events
/v1/events/loginRecord a login attempt (success or failed). MaiGuard stores failure counts and last-login timestamp in Redis and automatically enriches the next transaction scored for that userId — no extra call needed at scoring time.
1// POST /v1/events/login — call on every login attempt (success or failed)
2// MaiGuard stores counters in Redis; auto-attached to the next score for this userId.
3
4// Failed attempt:
5await fetch('https://api.maiguard.com/v1/events/login', {
6 method: 'POST',
7 headers: {
8 'Content-Type': 'application/json',
9 'Authorization': 'Bearer pk_live_<your-key>'
10 },
11 body: JSON.stringify({
12 userId: 'cust_abc123xyz',
13 outcome: 'failed',
14 ipAddress: '203.0.113.42',
15 deviceId: 'dev_fingerprint_abc789',
16 occurredAt: new Date().toISOString()
17 })
18});
19
20// Successful login:
21await fetch('https://api.maiguard.com/v1/events/login', {
22 method: 'POST',
23 headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer pk_live_<your-key>' },
24 body: JSON.stringify({ userId: 'cust_abc123xyz', outcome: 'success', occurredAt: new Date().toISOString() })
25});Auto-enriched signals
- login_failed_1h — failure count, 1 h window
- login_failed_24h — failure count, 24 h window
- mins_since_last_login — ATO timing signal
Inline alternative
- Pass loginContext on the score request for session-level precision: minsSinceLastLogin, failedAttemptsBeforeLogin, loginIpMatchesTxIp, isFirstLoginFromDevice
Customer onboarding
/v1/customers/:customerId/onboardStores KYC/KYB context for a customer — risk profile, entity type, KYC tier, PEP status, and expected volume. Rules can then reference this context when scoring transactions for the same user. Also available: GET /v1/customers, GET /v1/customers/statistics, and bulk CSV via POST /v1/customers/onboard/bulk (JWT).
Warning
customerId in this call must exactly match the userId you pass on every score request for that customer. A mismatch means the scoring engine cannot find the KYC context, and enriched rules will not fire.1// PUT /v1/customers/:customerId/onboard (same ID as userId in scoring)
2const response = await fetch('https://api.maiguard.com/v1/customers/customer_001/onboard', {
3 method: 'PUT',
4 headers: {
5 'Content-Type': 'application/json',
6 'Authorization': 'Bearer pk_live_<your-key>',
7 },
8 body: `{
9 "riskProfile": "medium",
10 "partnerOnboardedAt": "2025-06-01T00:00:00.000Z",
11 "countryCode": "NG",
12 "entityType": "individual",
13 "displayName": "Amara Osei",
14 "kycStatus": "verified",
15 "kycTier": 2,
16 "pep": false,
17 "expectedVolume": 5000000,
18 "expectedVolumePeriod": "monthly"
19}`,
20});Tip
List transactions
/v1/transactions// GET /v1/transactions (API key or JWT: transactions.view)
const response = await fetch('https://api.maiguard.com/v1/transactions?page=1&limit=20&decision=BLOCK&riskScoreMin=70&environment=LIVE&sortBy=createdAt&sortOrder=desc', {
headers: {
'Authorization': 'Bearer pk_live_<your-key>',
},
});page, limit — paginationdecision, status — filter by outcomeriskScoreMin / Max, userId — filter by risk or useramountMin / Max, startDate / endDate — range filtersenvironment — LIVE | TESTincludeMetadata, includeTotal, sortBy, sortOrderIntegration methods
Inbound: provider posts signed events to your MaiGuard webhook URL. Outbound: MaiGuard posts decisions and alerts to your HTTPS endpoint.
1// Configure inbound webhook — POST /v1/webhooks/ingestion/config
2const cfg = await fetch('https://api.maiguard.com/v1/webhooks/ingestion/config', {
3 method: 'POST',
4 headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer pk_live_<your-key>' },
5 body: JSON.stringify({ webhookSecret: 'your_signing_secret', retryCount: 3 })
6}).then(r => r.json());
7
8// POST events to cfg.data.webhookUrl; sign raw body with HMAC-SHA256:
9// X-Webhook-Signature: HMAC-SHA256(rawBody, webhookSecret)
10
11// Outbound (MaiGuard → you): configure HTTPS endpoint in tenant portal,
12// then verify X-Webhook-Signature on every inbound request from MaiGuard.Response format
All API responses follow a consistent envelope.
Success
{
"success": true,
"data": { ... }
}Error
{
"error": "Validation Error",
"message": "Validation failed",
"details": [{ "path": ["amount"], "message": "Expected positive number" }]
}| Status | Type | Description |
|---|---|---|
202 | Accepted | Async job queued — poll GET /v1/jobs/:jobId |
400 | Validation Error | Invalid body — check details[] for path and message |
401 | Unauthorized | Invalid or missing API key |
403 | Forbidden | Insufficient permissions or plan limit exceeded |
404 | Not Found | Resource not found or not in your tenant |
409 | Conflict | Duplicate resource or concurrency conflict |
429 | RateLimitExceeded | Rate limit exceeded — honor Retry-After and RateLimit-* headers |
500 | InternalServerError | Transient — retry with exponential backoff + jitter |
503 | ServiceUnavailable | Persistence queue unavailable — retry immediately |
Rate limits
Default limits (may vary by plan). On 429 responses, honor Retry-After and RateLimit-* response headers.
| Endpoint / scope | Limit | Window |
|---|---|---|
All endpoints (global guard) | 100 | Per minute, per IP |
POST /v1/transactions/score | 1,000 | Per minute, per tenant |
POST /v1/transactions/batch-score | 10 | Per minute, per tenant |
POST /v1/webhooks/transactions/:path | 1,000 | Per minute, per tenant path |
Bulk file import | 5 | Per hour, per tenant |
Auth login / register / forgot-password | 5 / 3 / 3 | Per 15 min or per IP/hour |
Passkey registration / authentication | 5 / 10 | Per 15 minutes, per IP |
Was this page helpful?