Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,978 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

yente

yente is an open source data match-making API. The service provides several HTTP endpoints to search, retrieve or match FollowTheMoney entities, including people, companies or vessels that are subject to international sanctions.

The yente API is built to provide access to OpenSanctions data, and can also be used to search and match other data, such as company registries or custom watchlists.

While yente is the open source core code base for the OpenSanctions API, it can also be run on-premises as a KYC appliance so that no customer data leaves your infrastructure.

Self-hosted Deployment (SmartComply)

This deployment runs yente on a single server using Docker Compose with a multi-replica architecture and external cron-based reindexing. All infrastructure configuration is self-contained in this repository.

Architecture Overview

                         ┌──────────────┐
                         │   Traefik    │
                         │  (reverse    │
                         │   proxy)     │
                         └──────┬───────┘
                                │
                    ┌───────────┴───────────┐
                    │                       │
            ┌───────▼───────┐     ┌─────────▼───────┐
            │   app         │     │   app           │
            │  (replica 1)  │     │  (replica 2)    │
            │  YENTE_AUTO_  │     │  YENTE_AUTO_    │
            │  REINDEX=false│     │  REINDEX=false  │
            └───────┬───────┘     └────────┬────────┘
                    │                      │
                    └──────────┬───────────┘
                               │
                    ┌──────────▼──────────┐
                    │  index              │
                    │  (Elasticsearch)    │
                    │  port 9200          │
                    └─────────────────────┘
                               ▲
                    (depends_on: healthy)
                               │
                    ┌──────────┴──────────┐
                    │  cron-scheduler     │
                    │  panubo/cron:1.4    │
                    │  reads crontab.txt  │
                    └──────────┬──────────┘
                               │
                               │ docker compose run --rm
                               │
                    ┌──────────▼──────────┐
                    │  reindexer          │
                    │  (one-shot job)     │
                    │  yente reindex      │
                    └─────────────────────┘
Service Image Replicas Purpose
traefik traefik:v3 1 TLS termination, routing to app
index elasticsearch:8.19.13 1 Search index, 8GB JVM heap
app yente:5.3.0 2 API serving /search, /match
reindexer yente:5.3.0 1 (profile) Runs yente reindex, exits
cron-scheduler panubo/cron:1.4 1 Schedules reindexer via cron

Why This Setup

Multi-replica app (replicas: 2): Two yente instances behind a load balancer provide resilience. If one fails, the other continues serving. Under normal operation, requests are distributed across both.

External cron instead of auto-reindex: With multiple app replicas, YENTE_AUTO_REINDEX=true causes all replicas to clash, each trying to reindex simultaneously. Setting YENTE_AUTO_REINDEX=false disables the built-in scheduler, and the cron-scheduler container triggers reindexing externally — the standard pattern recommended by the yente project for multi-instance deployments.

Zero-downtime reindexing: Reindex is non-blocking because Elasticsearch uses index aliases. While reindexer builds a new index, the existing index continues serving API requests via the alias. The alias is atomically swapped once the new index is ready — no gap in service.

panubo/cron: Chosen over alternatives because it's actively maintained since 2016, runs jobs as non-root with configurable GID, logs to stdout/stderr (integrated with docker compose logs), and supports dynamic crontab reloading without restart.

Configuration

Environment variables are set in .env:

Variable Value Purpose
YENTE_INDEX_TYPE elasticsearch Search backend type
YENTE_INDEX_URL http://index:9200 Internal ES connection
YENTE_MANIFEST /app/manifests/commercial.yml Dataset manifest
OPENSANCTIONS_DELIVERY_TOKEN <redacted> OpenSanctions data access token
YENTE_AUTO_REINDEX false Disabled — reindex via cron

Schedule

Reindexing runs twice daily at 00:00 and 12:00 UTC, defined in crontab.txt:

0 0,12 * * * cd /app && docker compose run --rm reindexer >> /proc/1/fd/1 2>&1

This schedule is version-controlled. To change the frequency, edit crontab.txt and restart the scheduler:

docker compose restart cron-scheduler

Deployment

# Start everything including cron
docker compose up -d

# Verify all services are healthy
docker compose ps

# Manually trigger a reindex (for immediate updates)
docker compose --profile reindex run --rm reindexer

# View real-time logs
docker compose logs -f cron-scheduler
docker compose logs -f reindexer

# Restart cron with updated schedule
docker compose restart cron-scheduler

Pros & Cons

Pro Con
Zero-downtime reindex — ES aliases enable atomic index swap Additional container — cron-scheduler adds minimal overhead (~5MB)
Resilient — 2 app replicas handle failure gracefully Docker socket mount — cron container requires socket access to orchestrate reindexer
External cron survives app restarts — reindex schedule is independent of app lifecycle UTC-only scheduling — cron container runs in UTC; DST/timezone shifts not supported
Schedule is version-controlledcrontab.txt lives in repo No built-in alerting — reindex failures are only visible via logs
Portable — all config lives in docker-compose.yml and .env Pre-initial index required — fresh deployment is unavailable until first reindex completes
Efficient — delta/incremental updates by default (YENTE_DELTA_UPDATES=true) Shared ES resource contention — heavy reindex load may slightly elevate API latency during the ~5–15min window

Troubleshooting

Reindex failing:

# Check the reindexer exit code and logs
docker compose --profile reindex run --rm reindexer
docker compose logs reindexer

Cron not firing:

# Verify the container is running
docker compose ps cron-scheduler

# Check scheduler logs
docker compose logs cron-scheduler

# Force a reload of the crontab
docker compose exec cron-scheduler sh -c "kill -HUP \$(cat /tmp/go-crond.pid)"

Elasticsearch not healthy:

# Check ES health directly
docker compose exec index curl -s http://localhost:9200/_cluster/health

# View ES logs
docker compose logs index

API returning stale data:

# Force an immediate reindex
docker compose --profile reindex run --rm reindexer --force

# Verify the alias points to the latest index
docker compose exec index curl -s http://localhost:9200/_cat/aliases/yente

Development

yente is implemented in asynchronous, typed Python using the FastAPI framework. We're happy to see any bug fixes, improvements or extensions from the community. To set up a local development environment, use uv:

git clone https://github.com/opensanctions/yente.git
cd yente
# Install runtime and development dependencies
uv sync
# Install pre-commit hooks with useful checks
prek install
# Activate the virtual environment
source .venv/bin/activate

This will install a broad range of dependencies, including numpy, scikit-learn and pyicu, which are binary packages that may require a local build environment. For pyicu in particular, refer to the package documentation.

Running the server

Once you've set the YENTE_INDEX_URL environment variable to point to a running instance of ElasticSearch or OpenSearch, you can run the web server like this:

yente serve

Releasing

bump2version --verbose minor # or patch
git push && git push --tags

Debugging using OpenTelemetry Tracing

You'll need a local piece of software to receive and display traces. Jaeger is widely used and has an easy-to-use Docker image. Paste the command to start the all-in-one container from the Jaeger documentation and find the web interface at http://localhost:16686.

Then, just run yente like this:

opentelemetry-instrument --exporter_otlp_traces_endpoint="http://localhost:4317" yente serve

License and Support

yente is licensed according to the MIT license terms documented in LICENSE. Using the service in a commercial context may require a data license for OpenSanctions data.

About

API for OpenSanctions with support for entity search and bulk matching of data collections. Supports Reconciliation API spec.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages