Catch fraud in real time, without sending customer data anywhere.
Ojuri is a fraud detection engine you run yourself. It scores every transaction before the money moves, tells you exactly why it made each call, and never sends a byte outside your own infrastructure. Free and open source — no contract, no per-transaction fee.
Measured server-side, request in to verdict out.
Compiled XGBoost served through ONNX Runtime.
Scoring, training, and investigation all run in your VPC.
No per-call fees, no rate limits, no vendor on the path.
Buying costs you control. Building costs you a year.
Most teams pick the least bad option and live with it. Ojuri exists because the trade-off is an artefact of how fraud tooling is sold, not something inherent to the problem.
- Your customers’ payment data is sent to somebody else’s cloud.
- You pay per transaction, so success gets more expensive.
- The verdict arrives as a score with no explanation attached.
- Their outage becomes your outage, mid-checkout.
- A year of senior engineering before it scores anything.
- You still have to solve serving, drift, and retraining.
- Analysts get no tooling until someone builds that too.
- The maintenance never ends, and it is never the roadmap.
A working engine you deploy today, running on your own hardware, with the source open in front of you.
Four jobs, running the moment you boot it.
No configuration required to see it work. Tuning it to your traffic comes later.
- 01It stops fraud before the money moves.
Every transaction is scored while the authorization is still open, then approved, declined, or sent for review. It is fast enough that a real customer never notices the check happened.
The hot path, in detail - 02It finds the ring, not just the card.
Fraud rarely arrives alone. Ojuri watches how accounts, devices, and payees connect over time, so organised rings and mule networks surface before any single transaction looks bad enough to block.
How pattern analysis works - 03It can always tell you why.
Every decision is recorded with the exact reasons behind it and the data the model saw at the time. When a regulator, a risk committee, or an angry customer asks why, the answer is a lookup — not an investigation.
What lands in the audit log - 04It learns from your analysts, not from itself.
When a reviewer overrules a decision, that judgement becomes training data. The system gets better at your traffic specifically, and it never trains on its own past guesses.
The retraining lifecycle
See it operating.
Sentinel is where your risk team lives — the review queue, the decision stream, the model registry, and the override that teaches the system what it got wrong. Shipped in the box, same licence.
There is a live one at sandbox.ojuri.io — sign in as demo / try-ojuri and look around, or send it a transaction and watch the verdict arrive.

The rest of this page is written for the engineers who will run it: the real topology, the real request and response, and an honest account of what the latency numbers do and don’t mean.
Four agents, decoupled by Kafka.
Exactly one service sits on the authorization path. The other three consume events asynchronously, so pattern analysis, retraining, and LLM investigation can lag, restart, or fail without touching a live payment.
Click any component for its role, scope, and tech specifics.
The same four jobs, specified.
- 01ONNX-served XGBoost on the hot path.
RDA — a single Fastify service in TypeScript. Zod validation at the edge, PRE rules, Redis feature lookup, ONNX inference at p99 ≈ 49µs, per-segment thresholds, POST rules, audit row, Kafka publish. Idempotent on transaction_id: a duplicate POST returns the cached decision instead of re-scoring.
- 02Graph and velocity analysis, off the hot path.
PAA consumes transactions.completed keyed by sender_id, maintains rolling 1h/24h velocity windows and per-receiver pagerank approximations, then writes feature deltas to Redis so RDA’s next prediction sees them. If PAA is down, RDA falls back to default features rather than failing.
- 03LLM investigations on a separate Kafka path.
Every DECLINE is republished to transactions.blocked, which only FIA consumes — the LLM never sees the bulk of traffic. A self-hosted Phi-3-mini-4k-instruct writes a structured report: verdict, recommended action, key indicators, narrative. Opt-in via --profile fia, since it pulls ~7.6GB of weights.
- 04A model lifecycle with teeth.
MLA watches F1 and PSI over a rolling window and triggers SMOTE-balanced XGBoost retraining when either crosses threshold. Promotion runs CANDIDATE → SHADOW → ACTIVE → RETIRED, gated on McNemar significance (p < 0.05) and ΔF1 ≥ 0.01. A replay CLI re-scores candidates against the live audit log before anything reaches production.
Scoring a live transaction in three steps.
One repo, one docker compose up, one POST. The response carries the decision, the probability, the model version, and the reason codes that drove the verdict — each with its feature contribution.
Copy .env.example to .env first — RDA won't start without AUTH_JWT_SECRET. Brings up NGINX, Postgres, Redis, Kafka, and the always-on RDA and PAA agents. MLA and FIA are opt-in.
$ git clone https://github.com/ojuri-io/ojuri.git && cd ojuri
$ cp .env.example .env
$ docker compose up -d --build
[+] Running 8/8 nginx postgres redis kafka rda paa prometheus grafana transaction_id, sender_id, receiver_id, amount, transaction_type, and timestamp (epoch ms) are required. Everything else is optional context — device, geography, identity, channel — and ~40 such fields sharpen the score when supplied. Pass transaction_id as Idempotency-Key for replay-safe POSTs.
$ curl -X POST http://localhost/v1/predict \
-H 'Content-Type: application/json' \
-d '{
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"sender_id": "user_a",
"receiver_id": "user_b",
"amount": 300.00,
"transaction_type": "TRANSFER",
"timestamp": 1717718400000,
"is_authenticated": true,
"account_age_days": 900,
"customer_type": "INDIVIDUAL",
"channel": "MOBILE",
"currency": "NGN",
"wallet_balance": 15250.75,
"is_recurring": false,
"device_is_trusted": true,
"device_type": "ANDROID",
"ip_is_vpn": false,
"ip_country": "NG",
"transaction_country": "NG",
"destination_country": "NG",
"session_to_txn_seconds": 42
}'decision is ACCEPT, REVIEW, or DECLINE. decision_source is ML, PRE_RULE, or POST_RULE. Each reason code carries its description, feature contribution, and observed value.
{
"transaction_id": "550e8400-…",
"fraud": false,
"fraud_probability": 0.0773,
"decision": "ACCEPT",
"decision_source": "ML",
"reason_codes": [
{ "code": "VELOCITY_1H", "description": "Transactions in the last hour above baseline", "contribution": 0.28, "value": 9 },
{ "code": "PAGERANK", "description": "Network-centrality score from the transaction graph", "contribution": -0.20, "value": 0.35 },
{ "code": "CLUSTERING_COEF", "description": "How tightly the sender clusters with known peers", "contribution": 0.11, "value": 0 }
],
"model_version": "default",
"threshold": 0.65,
"audit_id": "38fb28d2-…",
"latency_ms": 9,
"timestamp": 1717718400123
}Sentinel is opt-in via docker compose --profile sentinel — a plain up leaves it out, so the agents boot without waiting on a dashboard you may not want.
FIA is opt-in via docker compose --profile fia — it pulls ~7.6GB of Phi-3 weights, which most quickstart runs don’t need.
A running Ojuri you can POST to.
Every agent — detection, pattern analysis, learning, investigation — and the operator dashboard, running together on one machine at sandbox.ojuri.io. It sleeps when idle, so the first visit in a while shows a wake button and takes about three minutes to come up.
Send made-up data only. The account is shared, so every transaction you submit is visible in the audit log to anyone else signed in — and the whole environment is wiped without warning.
Cross-border transfer over a VPN from an untrusted device, seconds after login.
A preset loads into the editor. Change any field there, or add your own — what you see is what gets posted.
transaction_id and timestamp are generated at send unless you set them yourself.
Every decision above came from the model. Not every verdict does. Here a written rule matched first and decided on its own — the model was never consulted, which is what decision_source: PRE_RULE records. Abridged; the full response carries the same reason codes and lineage.
$ curl -X POST https://sandbox.ojuri.io/v1/predict \
-H 'X-Api-Key: fdk_9f2c_…' \
-d '{ …, "amount": 2750000, "transaction_type": "TRANSFER",
"ip_is_vpn": true, "ip_country": "RU", "destination_country": "AE" }'{
"decision": "DECLINE",
"decision_source": "PRE_RULE",
"rule_hit": "FATF_HIGH_RISK_CORRIDOR",
"fraud_probability": 0.8412,
"audit_id": "38fb28d2-…",
"latency_ms": 7
}The decisions you ship are auditable, not opaque.
Self-hosted, residency-friendly by construction.
Ojuri runs in your VPC, on your hardware, against your databases. Transaction data does not leave your boundary — not for scoring, not for training, not for investigation. That makes strict residency regimes such as GDPR, NDPR, and CBN tractable. Ojuri is not a compliance certification; it removes the egress that usually makes one hard to obtain.
Every decision is traceable.
Verdict, model_version, threshold, feature snapshot, reason codes with contributions, audit_id, timestamp — every decision RDA emits lands in the audit table before the response returns. Lineage works backwards from a single verdict to the exact feature values the model saw at inference time. Nothing is opaque after the fact.
Reviewer overrides close the label loop.
In Sentinel, an analyst can override any verdict. The override writes back to groundTruthFraud, so the next training run learns from the analyst’s judgement — and the model does not learn from its own past decisions. The longer you run it, the better it judges your traffic.
No vendor on the authorization path.
Open source under MIT. No SaaS dependency, no per-call fees, no rate limits, no vendor outage that takes your authorization offline. The agents you boot are the agents you run; the model registry is your model registry.
From launch to a closed learning loop.
Where we started, what has shipped, and where we are headed — every release tagged and reproducible.
- NextOn the roadmap
Where we are headed
The core loop is proven. Next is making it easier to deploy at scale and to integrate with existing payment flows.
- Canary traffic split by API-key cohort
- Helm chart and Terraform module for production deploys
- TypeScript and Python client SDKs
- v1.5.1August 11, 2026Current
Fixes found by running it
Standing the public sandbox up exercised paths that unit tests do not, and turned up five faults — two of which made a feature look like it worked while doing nothing at all. All are fixed and verified against the running environment.
- API keys are saved. The dashboard showed you a key and quietly discarded it, so every request made with one was rejected
- The API stays reachable after a restart — the proxy kept sending requests to an address the detection agent no longer had
- A server that is merely unreachable no longer reports itself as misconfigured, which sent operators to change a setting that was already correct
- A self-stopping deployment now really does stop itself; the timers were being skipped whenever the stack failed to start
Verified end to end on the live sandbox: sign in, issue a key, score a transaction, and read the decision back with its rule and audit trail.
- v1.5.0August 11, 2026
Run it without installing it
A one-command deployment that puts a complete Ojuri on a single cloud machine, behind HTTPS, and stops itself when nobody is using it. Standing it up surfaced three faults that made services unrunnable rather than merely awkward — each is fixed, and each was verified on the running system rather than in a test.
- A public sandbox you can sign into and POST to, which sleeps when idle and wakes from a button on the page
- The operator dashboard can finally be served from the shipped stack — it had a published image but nothing wired to run it
- Production mode starts: the detection agent refused to boot because a required setting never reached it
- The learning agent runs on current servers again, and investigation follow-ups answer the question asked instead of inventing their own
Follow-up answers went from 248 seconds and four invented questions to 67 seconds and a straight answer — the model had been generating text nobody would read.
- v1.4.0August 9, 2026
Every line reviewed
We reviewed the entire platform line by line and fixed all 45 issues we found, verifying each fix on a running system. The release also adds an optional stronger audit mode: every decision is permanently recorded before the customer gets an answer, so no record is ever lost — even if a server crashes mid-request.
- If the scoring engine fails, transactions go to human review instead of being declined — a customer is never rejected because of an internal outage
- Removing someone’s access or changing their role now takes effect immediately, not at their next login
- Choose how many detection servers to run with a single setting — no config surgery to scale up
- More trustworthy scores and safer operations: calibrated probabilities now reach serving, and the graph service protects itself from being run twice by mistake
Verified under load after the fixes: 2,000 requests, every single one succeeded, and 99 out of 100 answered within 85 ms — down from 295 ms before.
- v1.3.0July 7, 2026
Measured, then hardened
An independent efficacy validation drove this release: correctness gaps between the platform and its own contracts are fixed, and new behavioral rules turn the graph and velocity signals into verdicts the model misses on trusted, authenticated traffic.
- Behavioral rule pack — velocity spikes and fan-out sprays routed to review, guarded so mobile-money agents and payroll stay clean
- Fresh installs now register the shipped model and per-transaction-type thresholds out of the box
- Context-field dropout in training, plus a load-time probe that flags a model keying on integration context instead of behaviour
- Reworked Sentinel rule editor: catalogue-aware variable validation and a searchable, grouped variable picker
The behavioral rules lift velocity-anomaly recall from 0 to 0.80 in the validation harness — with zero added false positives on agent-network, payroll, airtime, and remittance traffic.
- v1.2.0July 2, 2026
The learning loop closes
Fraud outcomes now flow back into the model. Chargebacks and disputes become training labels, the model retrains on verified outcomes, and detection improves per deployment.
- Labels API — chargebacks, disputes, and reviewer overrides feed retraining
- Automatic retraining on verified labels, with temporal train/test splits
- A binding deployment gate: a new model ships only if it genuinely beats the current one
- Live shadow scoring and a REVIEW band that turns uncertainty into labels
Validated in a 128k-transaction benchmark: 34% of fraud caught cold → 98.8% after one label-driven retrain, at a 1.1% false-positive rate.
- v1.1.0June 22, 2026
Hardening and adopter tooling
The first tagged release: durable graph state, richer rule and threshold defaults, and the tooling to bring your own data.
- Durable transaction-graph state that survives restarts
- FATF rule pack, isotonic score calibration, configurable training modes
- Chunked training-data import so adopters can load their own history
- Per-segment threshold defaults and rule visibility in the audit log
- v1.0.0June 7, 2026
The platform launches
Four cooperating agents, decoupled by Kafka: real-time scoring, off-path graph analysis, drift monitoring, and LLM investigations — self-hosted, MIT-licensed, one docker compose up.
- Millisecond ONNX scoring on the authorization path
- Hot-reloaded rules engine, per-segment thresholds, decision audit log
- FIA investigation reports for blocked transactions, on a separate path
- Sentinel operator dashboard: live decisions, audit log, model registry
The numbers on this page — p99 ≈ 6ms server-side, ≈49µs at the model — are reference values measured on a single Apple Silicon developer workstation. They are not SLA targets. Re-measure on your own hardware, with your own feature shape, before relying on them.
Failure mode is graceful. Circuit breakers around Redis and ONNX keep the path degrading instead of failing — predictions still succeed against default features when Redis is down. PAA, MLA, and FIA can each be unavailable without affecting authorization.
See ARCHITECTURE.md for the load-test method and the per-feature performance breakdown.
Bears witness to every transaction.
Self-hosted, MIT-licensed, in your boundary. One repo, one docker compose up, one POST.
Leave an address and you’ll hear about releases, and about paid support if and when it exists. Nothing else, and not often.