Entities and the graph
Sentinel models who your customers are, how they connect, and what they do. Decisions, alerts, cases and filings all hang off those three ideas.
Entities
An entity is anything Sentinel keeps a profile for. You create or update one with POST /v1/entities. IDs are yours, so use the IDs your own systems already use.
| Kind | Represents | Typical attributes |
|---|---|---|
customer | A person or business you serve | Name, date of birth, national ID, KYC result, risk tier |
account | A bank or ledger account | Currency, opened date, product |
wallet | A mobile money or e-money wallet | Operator, phone number, tier, opened date |
device | A phone or browser seen on your platform | Fingerprint, OS, first seen |
agent | A cash-in / cash-out agent or merchant | Location, operator, till number |
counterparty | Someone on the other side who isn't your customer | Name, institution, country |
Attributes are free-form JSON. Send what you have now and add fields later without a migration.
Links
A link records that two entities are related, with the first and last time it was seen. Every event with a source.entity_id adds links, and replaying an event changes nothing:
| Relation | From | To | Recorded when |
|---|---|---|---|
paid | The sender | The receiver | The event names both source.entity_id and destination.entity_id |
uses_device | The sender | The device | The event has a device.id |
Reading links back with GET /v1/entities/{id}/links also follows them one step: who paid this entity (paid_by), who used a device (device_user), and anyone else who used one of its devices (shares_device). Each linked entity comes with its open alert, if it has one.
If an event names someone you never registered, Sentinel adds a placeholder entity so the link has two ends. A placeholder's kind is a guess, so it's never shown; register the entity with POST /v1/entities and its real kind and attributes take over.
The entity graph
Together, the links form the entity graph. Patterns often show in connections rather than single transactions. One wallet receiving from 23 senders looks odd. The same wallet sharing a phone with four others and cashing out at an agent who serves all five looks much worse.
The alert panel's Linked entities list is built from the graph today, and the built-in rules count senders and receivers.
Core tables
Every table except the screening lists carries an org_id, so one install can hold several organisations with complete separation.
| Table | Key columns | Notes |
|---|---|---|
entities | org_id, id, kind, attributes (jsonb), created_at, updated_at | Your IDs |
entity_links | src_id, dst_id, relation, first_seen, last_seen | paid and uses_device, filled from events |
events | id, idempotency_key (unique), type, occurred_at, amount_value, currency, rail, source_id, destination_id, payload (jsonb) | source_id and destination_id are the event's source.entity_id and destination.entity_id |
decisions | event_id (unique), decision, risk_score, reasons (jsonb), features (jsonb) | One decision per event |
alerts | decision_id, event_id, source_id, status, priority, occurrences, assigned_to, disposition | One open alert per source |
alert_decisions | alert_id, decision_id | Every decision on an alert |
alert_notes | alert_id, author, body | Notes on alerts |
rules | rule_key, version, expr, weight, subject, status, created_by, approved_by | Every version is kept |
screening_lists, screening_entries | code, version, ref, name, category | Shared by the whole install |
screenings | reference, status, subject, matches, lists | Each result as it was returned |
audit_log | seq, actor, action, target_type, target_id, data, prev_hash, hash | Append-only |
api_keys | name, key_hash (unique), key_prefix, created_by, revoked_at | Hashes only |
Cases, reports and users are part of the product design. Their tables don't exist yet.