Documentation
Everything you need to integrate real-time fraud decisions into your payment flow. Integrate in under 30 minutes.
ArcheaOne adds real-time fraud detection to your payment flow. Before a transaction is processed, you send it to ArcheaOne and get a fraud decision back in milliseconds: BLOCK, REVIEW or ALLOW.
You keep full ownership of your transactions, logins and account flows. At the moments where you want a risk decision, you send an event. ArcheaOne scores it with a rules engine and a machine-learning anomaly model working together, and returns a decision with reasons. The model can escalate an ALLOW to REVIEW, but never relaxes a rule decision.
Blocked activity also appears as an alert in your dashboard, and signed webhooks go out to your systems.
Your customer
Starts a transfer or payment
Your backend
Sends the transaction to ArcheaOne before moving money
ArcheaOne API
Returns BLOCK, REVIEW or ALLOW with a risk score and reason codes
Your backend
Acts on the decision and stays in control
BLOCK
Decline the transaction
REVIEW
Hold it: step-up auth or manual review
ALLOW
Commit the debit and process normally
On BLOCK, ArcheaOne also shows a fraud alert in your dashboard and sends a signed webhook, Slack message or email for HIGH and CRITICAL risk.
assess/transaction before you commit the debit. On BLOCK, stop. On REVIEW, hold the transaction for manual release. On ALLOW, proceed.assess/access after a login attempt without blocking on it, and let the webhook drive any follow-up, such as step-up MFA or a forced password reset.assess to build up a behavioural baseline, and start gating later.rsk_test_): sandbox traffic. Test calls don't count toward your monthly usage and have no monthly cap.rsk_live_): production traffic. Every successful live assessment counts toward your plan's monthly usage (see Usage and billing).https://api.archeaone.com/api/v1Send your API key with every request, in either header:
x-api-key: rsk_test_...Authorization: Bearer rsk_test_...organizationId in the request body: unknown fields are rejected with 400. You may send an x-organization-id header, but it must match the key's organisation, or the call fails with 401.401.Use this for money movement: transfers, payments, payouts, debits, credits and refunds.
All fields are required unless marked optional. Fields not listed here are rejected with 400. Put any extra context in metadata.
| Field | Type | Description |
|---|---|---|
eventId | string | Your ID for this event. |
eventType | string | "TRANSACTION" |
timestamp | ISO 8601 string | When the event happened. |
transactionId | string | Your transaction ID. |
accountId | string | The account being debited. |
amount | number | Transaction amount. |
currency | string | ISO currency code, e.g. NGN. |
status | enum | PENDING, SUCCESS, FAILED or CANCELLED |
transactionType | enum | DEBIT, CREDIT, TRANSFER, PAYMENT or REFUND |
actor | object | Who is acting (see below). |
device | object | The device used (see below). |
correlationId | string, optional | Your own ID for linking related events. |
metadata | object, optional | Any extra key/value context. |
simSwap | object, optional | The result of your own SIM swap check (see below). |
| Field | Type | Description |
|---|---|---|
userId | string | Your ID for the end user. |
userType | string | e.g. CUSTOMER. |
email | string, optional | Must be a valid email if sent. |
phone | string, optional | |
role | string, optional | |
permissions | string[], optional |
| Field | Type | Description |
|---|---|---|
deviceId | string | Stable device identifier. |
sessionId | string | Current session ID. |
deviceName | string | e.g. iPhone 14. |
deviceType | string | e.g. MOBILE, WEB, DESKTOP. |
deviceToken | string | Device or push token. |
os | string | e.g. iOS 17. |
location | object | latitude (−90 to 90), longitude (−180 to 180), country, city (all required). |
lastSeen | ISO 8601 string | When the device was last seen. |
Payments and logins made soon after a customer's SIM card was swapped are a common sign of account takeover. If you already check SIM swap status, for example through the NIBSS SIM swap service, send the result here and ArcheaOne will take it into account. A recent swap adds the SIM_SWAP_RECENT reason code. It raises the risk but does not block on its own, so a customer who genuinely replaced a lost SIM is sent for review rather than refused. Works on transaction and login events.
| Field | Type | Description |
|---|---|---|
lastSwappedAt | ISO 8601 string, optional | When the customer's SIM was last swapped. |
swappedRecently | boolean, optional | Send true if your check only says the SIM was swapped recently, without a date. |
source | string, optional | Where the result came from, e.g. NIBSS. For your own records. |
"simSwap": {
"lastSwappedAt": "2026-10-13T08:30:00Z",
"source": "NIBSS"
}curl -X POST "https://api.archeaone.com/api/v1/risk-assessment/assess/transaction" \
-H "Content-Type: application/json" \
-H "x-api-key: rsk_test_..." \
-d '{
"eventId": "evt_001",
"eventType": "TRANSACTION",
"timestamp": "2026-06-03T15:00:00.000Z",
"transactionId": "txn_456",
"accountId": "acc_demo_001",
"amount": 50000,
"currency": "NGN",
"status": "PENDING",
"transactionType": "TRANSFER",
"actor": {
"userId": "user_123",
"userType": "CUSTOMER",
"email": "customer@example.com"
},
"device": {
"deviceId": "dev_abc123",
"sessionId": "sess_xyz789",
"deviceName": "iPhone 14",
"deviceType": "MOBILE",
"deviceToken": "token_abc",
"os": "iOS 17",
"location": {
"latitude": 6.5244, "longitude": 3.3792,
"country": "NG", "city": "Lagos"
},
"lastSeen": "2026-06-03T15:00:00.000Z"
}
}'The default response is kept small: it contains only what your application needs to act on a decision.
{
"status": 200,
"message": "Transaction risk assessment completed",
"data": {
"assessmentId": "RISK-...",
"eventId": "evt_001",
"userId": "user_123",
"decision": "BLOCK",
"riskScore": 87,
"riskLevel": "CRITICAL",
"reasons": [
"Transaction from new country: RU"
],
"reasonCodes": [
"GEOGRAPHIC_ANOMALY", "UNUSUAL_AMOUNT"
],
"assessedAt": "2026-06-03T15:00:00.000Z",
"assessor": "SYSTEM",
"metadata": {
"processingTimeMs": 124
}
}
}| Field | Description |
|---|---|
assessmentId | ArcheaOne's ID for this assessment. |
eventId, userId | Echoed from your request. |
decision | BLOCK, REVIEW or ALLOW. This is what you act on. |
riskScore | 0–100 severity signal. |
riskLevel | LOW, MEDIUM, HIGH or CRITICAL. |
reasons | Human-readable explanations. |
reasonCodes | Machine-readable codes for the rules that fired. |
assessedAt | When the assessment was made. |
metadata.processingTimeMs | Server processing time, useful for monitoring latency. |
metadata.replayed | Present and true only when this is a repeat of an eventId you already sent. |
Don't branch on message; its wording differs between endpoints.
Append ?include=metadata to get a few more operational fields. It doesn't cost anything extra; it's the same single call.
"metadata": {
"processingTimeMs": 124,
"evaluatedRules": 2,
"eventType": "TRANSACTION",
"mode": "test",
"profileAge": 42,
"location": { "latitude": 6.5244, "longitude": 3.3792, "country": "NG", "city": "Lagos" },
"deviceId": "dev_abc123"
}evaluatedRules is the number of rules that fired. profileAge is the age of the end user's behaviour profile in days. location and deviceId echo what you sent.
Takes the same eventId, eventType ("ACCESS"), timestamp, actor, device and optional correlationId / metadata fields as a transaction, plus:
| Field | Type | Description |
|---|---|---|
action | enum | LOGIN, LOGOUT, PASSWORD_RESET, FAILED_LOGIN or TWO_FA_CHALLENGE |
success | boolean | Whether the attempt succeeded. |
accessMethod | enum | PASSWORD, FINGER_PRINT, FACE_ID or 2FA |
noOfTries | number (≥ 0) | Attempts in this session. |
ipAddress | string | The end user's IP address. |
reason | string, optional | e.g. "Invalid password". |
Takes only the common fields (eventId, eventType, timestamp, actor, device, and optional correlationId / metadata). Put any event-specific details in metadata. Behavioural rules run for eventType values BEHAVIOR and ACCOUNT. Other event types are stored to build the user's history, but no built-in rules run on them, so they return ALLOW.
All three endpoints return the same response shape.
| Decision | Meaning | What to do |
|---|---|---|
| BLOCK | High risk. | Stop it. Don't process the transaction, and return an error to the user. |
| REVIEW | Suspected fraud. | Hold the transaction, step up authentication, or send it to manual review. |
| ALLOW | No elevated risk. | Process the transaction normally. |
riskScore and riskLevel only as severity signals for dashboards and thresholds.| Code | Applies to | What it flags |
|---|---|---|
TRANSACTION_VELOCITY | Transactions | Many transactions in a short time |
UNUSUAL_AMOUNT | Transactions | Amount far outside the user's normal range |
GEOGRAPHIC_ANOMALY | Transactions | Transaction from a new or unusual country |
RAPID_LOCATION_CHANGE | Transactions | Location change too fast to be physically possible |
NIGHTTIME_TRANSACTION | Transactions | Activity at unusual hours |
SMALL_TRANSFER_VELOCITY | Transactions | Burst of small transfers (card or account testing) |
NEW_ACCOUNT_PROBE | Transactions | New account probing with early transactions |
STRUCTURING_DETECTION | Transactions | Amounts split to stay under thresholds |
SIM_SWAP_RECENT | Transactions and access | Activity soon after the customer's SIM was swapped (needs simSwap) |
FAILED_LOGIN_ATTEMPTS | Access | Repeated failed logins |
NEW_DEVICE_LOGIN | Access | Login from a device not seen before |
SUSPICIOUS_IP | Access | Login from a suspicious IP address |
HISTORICAL_RISK_PATTERN | Behaviour / account | User has a history of risky activity |
NEW_ACCOUNT | Behaviour / account | Very new account |
CUSTOM_RULE | Any | One of your own dashboard rules matched (named in reasons) |
New codes may be added over time, so treat unknown codes as informational rather than as errors.
Every error returns the same JSON shape. message is a string, or for validation errors, an array of strings.
{
"status": 400,
"message": ["actor.userId should not be empty", "property organizationId should not exist"],
"timestamp": "2026-06-03T15:00:00.000Z",
"path": "/api/v1/risk-assessment/assess/transaction"
}| Status | When | What to do |
|---|---|---|
400 | A required field is missing, an enum value is invalid, or the body contains a field that isn't in the schema. | Fix the payload. message lists each problem. |
401 | The API key is missing, invalid, expired or revoked, or x-organization-id doesn't match the key. | Check the key and header. |
403 | Your plan's monthly live-call limit is reached, or your organisation is suspended. | Upgrade your plan, or contact support. |
409 | Another request with the same eventId is still being scored (for example, a retry sent while the original was in flight). | Retry after a moment: you'll get the original result. |
429 | Too many requests in a short window. | Back off and retry. |
500 | ArcheaOne couldn't complete the assessment. | Retry with backoff, and apply your fallback policy. |
The assessment endpoints (assess, assess/transaction, assess/access) accept, per client IP address:
| Window | Requests |
|---|---|
| 1 second | 25 |
| 10 seconds | 200 |
| 1 minute | 1,000 |
Going over any window returns 429. Back off and retry. If you need higher limits, contact support.
Set a client timeout on every call (the Python example uses 5 seconds) and decide in advance what happens if ArcheaOne is unreachable or returns 5xx: either let the transaction through (fail open) or hold it for review (fail closed). Most teams fail open for low-value transactions and fail closed for high-value ones.
One API call is one successful live-mode assessment: a 200 response from assess/transaction, assess/access or assess made with an rsk_live_ key.
| Situation | Counts toward usage? |
|---|---|
| Assessment returns BLOCK, REVIEW or ALLOW | Yes. All decisions count the same. |
| The transaction later fails, is declined or cancelled in your system | Yes. The assessment already happened. The status you send makes no difference. |
| Request rejected by ArcheaOne: 400, 401, 403, 409, 429 | No. Nothing is scored. |
| Assessment fails with 500 | No. |
| You retry with the same eventId within 24 hours | No. You get the original result back, with metadata.replayed: true. It isn't scored or counted again, and no new alerts are sent. |
| You send the same eventId after 24 hours, or a new eventId for the same transaction | Yes. It's treated as a new event. Use one eventId per event and reuse it only for retries. |
| Internal transactions (own-account transfers, reversals, fee postings) | Yes, if you send them. ArcheaOne doesn't know a transaction is internal, so send only the events you want scored. |
| ?include=metadata | No extra charge. It's the same single call. |
| Test-mode (rsk_test_) calls | No. |
| Webhook, Slack and email alert deliveries | No. |
| Dashboard use, managing API keys, webhooks and rules | No. |
| Plan | Live assessments per month |
|---|---|
| Free Trial (14 days) | 1,000, the same as Starter, so you can try it before paying |
| Starter | 1,000 |
| Growth | 10,000 |
| Enterprise | Unlimited |
Usage resets at 00:00 UTC on the 1st of each month. When you reach your limit, further live calls return 403 until the next month or until you upgrade. Retries of events already scored within 24 hours still return their result. There are no automatic overage charges. Your dashboard shows this month's usage and when it resets.
When an assessment returns BLOCK, ArcheaOne:
HIGH_RISK_DETECTED;HIGH or CRITICAL (you can turn this off in Settings).Alerts from test-mode traffic appear in your dashboard and go only to webhooks you created with a test key. They never send email, so you can test safely without paging your production channels.
Repeated blocks for the same user and event type within a short cool-down window produce one alert, not many. Pattern detection can also raise alerts: critical patterns are sent as HIGH_RISK_DETECTED, and others as ALERT_CREATED.
Set up webhooks in the dashboard under Webhooks. Choose a generic JSON format for your own systems, or a Slack format for a Slack incoming-webhook URL.
POST <your URL>
Content-Type: application/json
X-Webhook-Event: HIGH_RISK_DETECTED
X-Webhook-Signature: <hex HMAC-SHA256 of the raw body>{
"event": "HIGH_RISK_DETECTED",
"data": {
"assessmentId": "RISK-...",
"userId": "user_123",
"riskScore": 87,
"riskLevel": "CRITICAL",
"severity": "CRITICAL",
"alertType": "HIGH_RISK_TRANSACTION",
"description": "Risk score 87/100 — decision: BLOCK",
"decision": "BLOCK",
"reasons": ["Transaction from new country: RU"],
"reasonCodes": ["GEOGRAPHIC_ANOMALY"]
},
"timestamp": "2026-06-03T15:00:01.000Z",
"webhookId": "..."
}data may contain additional fields; ignore any you don't use.
The signature is an HMAC-SHA256 of the raw request body using your webhook secret. Compute it over the exact bytes you received, before parsing the JSON.
const crypto = require('crypto');
// Express: app.post('/archea', express.raw({ type: 'application/json' }), handler)
function isValid(rawBody, signatureHeader, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader || ''));
}2xx within 5 seconds. Anything else counts as a failure.TEST delivery.Need help integrating? Email support.archeaone@cycloudforte.com.