MaiGuard

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.

PhaseWhat happens
AccountTenant created; subscription and feature limits assigned.
KYBBusiness verification; documents submitted via portal.
CredentialsTEST and LIVE API keys issued; portal users use JWT session.
ConfigurationRules, tenant lists, pipelines, and notifications set up.
Integration testUse TEST key; scored traffic is test-only, does not affect live reporting.
Go-liveSwitch to LIVE key — no code changes required.

Note

Use 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

Never embed 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.

ShapeWhen to useMonitoring outcome
MinimalAlways valid — required fields onlyBasic scoring; suitable for connectivity tests and low-context flows.
RecommendedDevice/session context available (IP, device, geo, metadata)Rules + velocity + pattern detection at production quality.
Full / enterpriseSchema 1.1 typed sections — or when strict payload validation is enabled on your tenantFull screening, AML context, and ML at highest quality. Missing required schema 1.1 sections returns 422 when strict validation is on.

Note

Strict payload validation: Some enterprise tenants require the full schema 1.1 shape (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.

minimal-payload.json
{
  "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

POST/v1/transactions/score

Synchronous 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});
ALLOW

Transaction approved — proceed with payment.

REVIEW

Elevated risk — flag for manual review.

BLOCK

High risk — decline the transaction.

Request parameters

ParameterTypeRequiredDescription
userIdstringrequiredStable customer ID. Must match customerId in onboarding calls.
amountnumberrequiredTransaction amount. Must be a positive number.
currencystringrequiredISO 4217 currency code (e.g. NGN, USD, GHS).
occurredAtstringoptionalISO 8601 timestamp when the transaction occurred.
ipAddressstringoptionalClient IP address used for velocity and geo signals.
userAgentstringoptionalBrowser or app user-agent string.
deviceIdstringoptionalDevice fingerprint from your own system or SDK.
deviceSessionIdstringoptionalDevice session from Browser SDK. Must start with ds_.
locationobjectoptionalGeographic coordinates of the transaction.
location.latitudenumberoptionalGeographic latitude.
location.longitudenumberoptionalGeographic longitude.
loginContextobjectoptionalLogin context for ATO signals. Common in Full / enterprise payloads.
loginContext.minsSinceLastLoginnumberoptionalMinutes since the user's last successful login.
loginContext.failedAttemptsBeforeLoginnumberoptionalFailed login attempts immediately before this success.
loginContext.loginIpMatchesTxIpbooleanoptionalWhether the login IP matches the transaction IP.
loginContext.isFirstLoginFromDevicebooleanoptionalWhether this is the first login from this device.
includeMatchedConditionsbooleanoptionalReturn matched rule conditions in the response (default: false).
metadataobjectoptionalBusiness context for rules and segmentation. See Recommended shape.
metadata.countryCodestringoptionalISO country code of the transaction.
metadata.receiverIdstringoptionalCounterparty ID for entity graph linkage.
metadata.productstringoptionalProduct line (e.g. Collections, Payouts).
metadata.paymentMethodstringoptionalPayment method (e.g. card, bank_transfer, mobile_money).
metadata.environmentstringoptionalLIVE or TEST.
metadata.typestringoptionalTransaction type (e.g. purchase, transfer).
metadata.merchantIdstringoptionalMerchant identifier.
metadata.categorystringoptionalProduct or merchant category.
metadata.orderIdstringoptionalPartner order or transaction reference.
metadata.channelstringoptionalChannel (e.g. web, mobile, api).
eventobjectoptionalFull / enterprise — event envelope (schema 1.1).
event.schemaVersionstringoptionalPayload schema version (e.g. 1.1).
transactionobjectoptionalFull / enterprise — transaction semantics: kind, direction, channel, paymentMethod, product.
actorobjectoptionalFull / enterprise — initiating party: id, type, countryCode.
accountobjectoptionalFull / enterprise — source account: id, number, type, currency.
counterpartyobjectoptionalFull / enterprise — beneficiary for screening: id, name, type, countryCode, bankCode.
merchantobjectoptionalFull / enterprise — merchant context: id, countryCode, merchantCategoryCode.
sessionobjectoptionalFull / enterprise — auth session: sessionId, deviceSessionId, loginMethod, mfaUsed, mfaMethod.
deviceobjectoptionalFull / enterprise — device profile: platform, browser, timezone, isRooted, isEmulator, appVersion.
networkobjectoptionalFull / enterprise — network context: ipAddress, userAgent.
geoobjectoptionalFull / enterprise — geo with countryCode: latitude, longitude, countryCode.
behavioralobjectoptionalFull / enterprise — behavioral biometrics (e.g. typingSpeed, touchPressureVariance).

Response — 200 OK

score-response.json
{
  "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.

score-cached-response.json
{
  "success": true,
  "cached": true,
  "data": {
    "transactionId": "550e8400-e29b-41d4-a716-446655440000",
    "riskScore": 42,
    "decision": "REVIEW",
    "matchedRules": [],
    "velocity": 6
  }
}

Response fields

FieldDescription
transactionIdServer-generated UUID for this score (persisted asynchronously).
riskScore0–100 composite risk. Higher = riskier.
decisionALLOW | REVIEW | BLOCK
matchedRulesRules that fired. matchedConditions only when request included includeMatchedConditions: true.
velocityTransaction count for this user in the last hour (rule input).
shadowEvaluationOptional — shadow rules only; does not change decision.
patternAnalysisOptional — behavioral/device/amount anomalies when pattern engine runs.
mlEvaluationOptional — 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.

score-validation-error.json
{
  "error": "Validation Error",
  "message": "Expected positive number",
  "details": [
    { "code": "too_small", "path": ["amount"], "message": "Expected positive number" }
  ]
}
score-strict-validation-error.json
{
  "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

POST/v1/transactions/batch-score

Up 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.

batch-score-response.json
{
  "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)

batch-score-async.json
{
  "success": true,
  "data": {
    "jobId": "job_uuid",
    "status": "PENDING",
    "totalItems": 250,
    "message": "Job queued for processing"
  }
}

Login events

POST/v1/events/login

Record 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

PUT/v1/customers/:customerId/onboard

Stores 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

Identity alignment is required. The 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

See the Customer Onboarding Implementation Guide for a complete endpoint matrix, examples, and rate-limit details.

List transactions

GET/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 — pagination
decision, status — filter by outcome
riskScoreMin / Max, userId — filter by risk or user
amountMin / Max, startDate / endDate — range filters
environment — LIVE | TEST
includeMetadata, includeTotal, sortBy, sortOrder

Integration 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.json
{
  "success": true,
  "data": { ... }
}

Error

error.json
{
  "error": "Validation Error",
  "message": "Validation failed",
  "details": [{ "path": ["amount"], "message": "Expected positive number" }]
}
StatusTypeDescription
202AcceptedAsync job queued — poll GET /v1/jobs/:jobId
400Validation ErrorInvalid body — check details[] for path and message
401UnauthorizedInvalid or missing API key
403ForbiddenInsufficient permissions or plan limit exceeded
404Not FoundResource not found or not in your tenant
409ConflictDuplicate resource or concurrency conflict
429RateLimitExceededRate limit exceeded — honor Retry-After and RateLimit-* headers
500InternalServerErrorTransient — retry with exponential backoff + jitter
503ServiceUnavailablePersistence queue unavailable — retry immediately

Rate limits

Default limits (may vary by plan). On 429 responses, honor Retry-After and RateLimit-* response headers.

Endpoint / scopeLimitWindow
All endpoints (global guard)100Per minute, per IP
POST /v1/transactions/score1,000Per minute, per tenant
POST /v1/transactions/batch-score10Per minute, per tenant
POST /v1/webhooks/transactions/:path1,000Per minute, per tenant path
Bulk file import5Per hour, per tenant
Auth login / register / forgot-password5 / 3 / 3Per 15 min or per IP/hour
Passkey registration / authentication5 / 10Per 15 minutes, per IP

Was this page helpful?