Globalscreen Api - Production-ready Sanctions & Pep Screening Service
This is a submission for the Xano AI-Powered Backend Challenge: Production-Ready Public API
What I Built
GlobalScreen API is a production-ready sanctions and PEP (Politically Exposed Persons) screening service that enables third-party applications to perform compliance checks against international watchlists. Think of it as Stripe for compliance - a simple API that solves a complex regulatory problem.
???? Core Features
- Real UN Sanctions Data: Integrated 1,000+ verified entries from the UN Consolidated Sanctions List
- Smart Fuzzy Matching: AI-powered search finds matches even with typos or name variations
- Comprehensive Coverage: Both individuals (727 entries) and entities (273 entries) across 10+ sanctions programs
- Production Security: OAuth 2.0 Bearer token authentication with tiered rate limiting
- Full Audit Trail: Complete logging of all screening requests for compliance reporting
- Developer-Friendly: RESTful design with clear error messages and pagination
???? Real-World Use Cases
- Fintech KYC/AML: Customer onboarding verification for banks and payment processors
- E-commerce: Transaction monitoring for high-risk merchants
- HR & Recruitment: Background checks for politically exposed persons
- Real Estate: Due diligence for property transactions
- Legal/Consulting: Client intake screening
API Documentation
???? Base URL
https://xvln-hqrc-qxwa.n7e.xano.io/api:QC35j52Y
???? Authentication
All endpoints require Bearer token authentication:
# 1. Sign up to create an account
POST /auth/signup
{
"email": "user@example.com",
"name": "John Doe",
"password": "secure-password"
}
# 2. Login to get your Bearer token
POST /auth/login
{
"email": "user@example.com",
"password": "secure-password"
}
# Response includes your token
{
"authToken": "eyJhbGciOiJBMjU2S1ciLC..."
}
# 3. Use token in all API calls
Authorization: Bearer eyJhbGciOiJBMjU2S1ciLC...
???? Rate Limits 1000 per account
Rate limit info is included in response headers:
-
X-RateLimit-Limit: Maximum requests allowed -
X-RateLimit-Remaining: Requests remaining in current window -
X-RateLimit-Reset: Unix timestamp when limit resets
???? Endpoint 1: GET /screening_entity
Search for a specific person or entity by name with fuzzy matching.
Request:
curl -X GET 'https://xvln-hqrc-qxwa.n7e.xano.io/api:QC35j52Y/search' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "ERIC BADEGE",
"entity_type": "Individual",
"nationality": "CD"
}'
Parameters:
-
name(required): Full or partial name to search (2-200 characters) -
entity_type(optional): Filter by "Individual" or "Entity" -
nationality(optional): 2-letter ISO country code
Response:
{
"success": true,
"data": {
"query": "ERIC BADEGE",
"total_matches": 1,
"results": [
{
"id": 1,
"name": "ERIC BADEGE",
"name_normalized": "eric badege",
"entity_type": "Individual",
"program": "UN - DRC",
"nationality": "CD",
"date_of_birth": "1971-01-01",
"address": "Rwanda",
"risk_level": "HIGH",
"status": "ACTIVE",
"match_score": 100,
"entity_number": "CDi.001",
"remarks": "He fled to Rwanda in March 2013...",
"list_date": "2012-12-31"
}
]
},
"meta": {
"timestamp": "2024-12-14T10:30:00Z",
"rate_limit": {
"remaining": 999,
"reset_at": 1734177600
}
}
}
Match Scoring:
- 100: Exact match
- 90: Name starts with search term
- 85: Match found in aliases (AKA field)
- 80: Partial match
???? Endpoint 2: GET /list
Get paginated list of watchlist entries with optional filters.
Request:
curl 'https://xvln-hqrc-qxwa.n7e.xano.io/api:QC35j52Y/screen_entity?page=1&per_page=20&entity_type=Individual&program=Al-Qaida' \
-H 'Authorization: Bearer YOUR_TOKEN'
Query Parameters:
-
name(optional): Filter by name (partial matching) -
page(optional, default: 1): Page number -
per_page(optional, default: 20, max: 100): Results per page -
entity_type(optional): "Individual" or "Entity" -
nationality(optional): 2-letter country code -
program(optional): Sanctions program name -
risk_level(optional): "HIGH", "MEDIUM", "LOW" -
status(optional, default: "ACTIVE"): "ACTIVE" or "INACTIVE"
Response:
{
"success": true,
"data": [
{
"id": 1,
"name": "ERIC BADEGE",
"entity_type": "Individual",
"program": "UN - DRC",
"nationality": "CD",
"risk_level": "HIGH",
"status": "ACTIVE"
},
// ... more entries
],
"meta": {
"page": 1,
"per_page": 20,
"total_results": 1000,
"total_pages": 50,
"has_next": true,
"has_previous": false
}
}
❌ Error Responses
All errors follow a consistent format:
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing Authorization header"
},
"meta": {
"timestamp": "2024-12-14T10:30:00Z"
}
}
Error Codes:
-
400 BAD_REQUEST: Missing or invalid parameters -
401 UNAUTHORIZED: Missing or invalid Bearer token -
403 FORBIDDEN: Token is inactive or insufficient permissions -
429 RATE_LIMIT_EXCEEDED: Too many requests -
500 INTERNAL_SERVER_ERROR: Server error
???? Data Coverage
Sanctions Programs:
- UN - Al-Qaida (341 entries, 34%)
- UN - DPRK (North Korea) (155 entries, 15%)
- UN - Taliban (140 entries, 14%)
- UN - Iran (121 entries, 12%)
- UN - Iraq (76 entries, 8%)
- Plus: DRC, Libya, Somalia, CAR, Haiti
Top Countries:
- Afghanistan (AF): 129 entries
- Iraq (IQ): 80 entries
- Indonesia (ID): 23 entries
- Democratic Republic of Congo (CD): 22 entries
- Tunisia (TN): 21 entries
Demo
???? Live Demo Screenshots
Search for "ERIC" finds "ERIC BADEGE" with 90 match score - demonstrates partial name matching capability.
** Paginated List View**
** Xano Database View**
1,000 verified UN sanctions records in watchlist_entries table, properly normalized and indexed for fast searches.
???? Test Cases
You can test the API with these verified names from the database:
Individuals:
# Exact match test
curl -X POST '.../search' -H 'Authorization: Bearer TOKEN' \
-d '{"name": "ERIC BADEGE"}'
# Partial match test
curl -X POST '.../search' -H 'Authorization: Bearer TOKEN' \
-d '{"name": "GASTON"}'
# Alias match test (searches AKA field)
curl -X POST '.../search' -H 'Authorization: Bearer TOKEN' \
-d '{"name": "AIGLE BLANC"}'
Entities:
# Entity search
curl -X POST '.../search' -H 'Authorization: Bearer TOKEN' \
-d '{"name": "HAWALA", "entity_type": "Entity"}'
Filtered Listing:
# Get Al-Qaida individuals only
curl '.../screen_entity?entity_type=Individual&program=Al-Qaida' \
-H 'Authorization: Bearer TOKEN'
The AI Prompt I Used
I started with Xano's AI assistant using this prompt:
Create a production-ready sanctions screening API called "GlobalScreen" with these requirements:
DATABASE SCHEMA:
Create 4 tables:
1. users (authentication)
- id, email, password, name, company
- rate_limit, requests_count, rate_limit_reset
- created_at, updated_at
2. watchlist_entries (1,000 UN sanctions records)
- id, source, entity_type, name, name_normalized
- aka (aliases), program, nationality, date_of_birth
- place_of_birth, address, position, country
- remarks, list_date, entity_number
- risk_level, status, created_at, updated_at
3. screening_logs (audit trail)
- id, user_id, search_type, search_name
- match_count, created_at, ip_address
4. api_keys (was later removed - using Bearer tokens instead)
API ENDPOINTS:
1. POST /search
- Search by name with fuzzy matching
- Optional filters: entity_type, nationality
- Return match_score for each result
- Limit to 20 top matches
2. GET /screen_entity
- Paginated list of watchlist entries
- Filters: name, entity_type, nationality, program, risk_level, status
- Default: 20 per page, max 100
AUTHENTICATION:
- Bearer token (OAuth 2.0 style)
- Rate limiting by tier (100/1000/10000 per hour)
- Proper error responses (401, 403, 429)
FUZZY MATCHING:
- Search both name_normalized and aka fields
- Case-insensitive partial matching
- Calculate match scores (100=exact, 90=starts with, 80=contains, 85=aka match)
- Sort by match_score DESC
AUDIT LOGGING:
- Log every search request
- Track: user_id, search_name, match_count, timestamp, IP
Please implement with proper validation, error handling, and production-ready code.
This prompt generated about 70% of the backend. The AI created:
- ✅ Database schema with all tables
- ✅ Basic authentication flow
- ✅ Endpoint structure
- ✅ Input validation
- ✅ Error response format
How I Refined the AI-Generated Code
The AI gave me a solid foundation, but I made several critical improvements for production readiness:
???? Fix #1: Fuzzy Matching Logic
BEFORE (AI-Generated):
// AI used exact match with WHERE clause
db.query watchlist_entries {
where = ($db.watchlist_entries.name == $input.name)
return = {type: "list"}
}
AFTER (My Improvement):
// Query all active entries, then filter in memory
db.query watchlist_entries {
return = {type: "list"}
} as $all_entries
// Normalize search term
var $search_term {
value = $input.name|to_lower|trim
}
// Filter with partial matching
foreach ($all_entries) {
each as $entry {
// Check if name_normalized contains search term
conditional {
if ($entry.name_normalized|contains:$search_term) {
// Add to results with match score
}
}
// Also check aka field for aliases
conditional {
if ($entry.aka|to_lower|contains:$search_term) {
// Higher priority for alias matches
}
}
}
}
Why: Xano's AI couldn't handle complex WHERE clauses with partial matching and OR logic. My solution uses in-memory filtering which is more flexible and actually works.
???? Fix #2: Match Score Calculation
BEFORE (AI-Generated):
// No match scoring in AI version
// Just returned raw results
AFTER (My Improvement):
foreach ($raw_results) {
each as $entry {
var $match_score {
value = 80 // default
}
// Exact match
conditional {
if ($entry.name_normalized == $search_term) {
var.update $match_score { value = 100 }
}
}
// Starts with
conditional {
if ($entry.name_normalized|starts_with:$search_term) {
var.update $match_score { value = 90 }
}
}
// Alias match
conditional {
if ($entry.aka|to_lower|contains:$search_term) {
var.update $match_score { value = 85 }
}
}
// Add score to result
var $result_with_score {
value = $entry|set:"match_score":$match_score
}
}
}
// Sort by score descending
var $sorted_results {
value = $results_with_scores|sort:"match_score":"int":true|slice:0:20
}
Why: Fuzzy matching is useless without confidence scores. This helps developers decide if a match is worth flagging.
???? Fix #3: Rate Limiting Implementation
BEFORE (AI-Generated):
// AI created basic check but didn't reset properly
precondition ($user.requests_count < $user.rate_limit) {
error = "Rate limit exceeded"
}
AFTER (My Improvement):
// Get current timestamp
var $current_time { value = now }
// Check if rate limit window has expired
conditional {
if ($user.rate_limit_reset == null || $current_time > $user.rate_limit_reset) {
// Reset counter for new hour
var $new_reset_time {
value = $current_time + 3600000 // +1 hour in milliseconds
}
db.edit user {
field_name = "id"
field_value = $auth.id
data = {
requests_count: 0,
rate_limit_reset: $new_reset_time
}
}
}
}
// Now check limit
precondition ($user.requests_count < $user.rate_limit) {
error_type = "inputerror"
error = "Rate limit exceeded"
}
// Increment counter
db.edit user {
field_name = "id"
field_value = $auth.id
data = { requests_count: ($user.requests_count + 1) }
}
Why: The AI version would never reset the counter, so users would hit the limit once and be locked out forever!
???? Fix #4: Error Response Consistency
BEFORE (AI-Generated):
// AI returned different error formats
return { error: "something went wrong" }
// or
return { message: "error" }
// or
return { code: 400 }
AFTER (My Improvement):
// Standardized error format everywhere
return {
value = {
success: false,
error: {
code: $error_code,
message: $error.message|first_notempty:"Error occurred"
},
meta: {
timestamp: $current_time|format_timestamp:"Y-m-d\TH:i:s\Z":"UTC"
}
}
}
// With proper HTTP status codes
conditional {
if ($error.message|contains:"Rate limit") {
var.update $error_code { value = "RATE_LIMIT_EXCEEDED" }
var.update $http_status { value = 429 }
}
}
Why: Consistent error format makes it easy for developers to handle errors programmatically. The AI mixed different formats.
???? Fix #5: Manual Pagination for Filtered Results
BEFORE (AI-Generated):
// AI tried to use database pagination with WHERE clause
db.query watchlist_entries {
where = $complex_expression
return = {
type: "list",
paging: { page: $input.page, per_page: $input.per_page }
}
}
AFTER (My Improvement):
// Since we filter in memory, paginate manually
var $total_count {
value = $matched_results|count
}
var $total_pages {
value = ($total_count / $input.per_page)|ceil
}
var $offset {
value = ($input.page - 1) * $input.per_page
}
var $paginated_results {
value = $matched_results|slice:$offset:$input.per_page
}
var $meta_data {
value = {
page: $input.page,
per_page: $input.per_page,
total_results: $total_count,
total_pages: $total_pages,
has_next: $input.page < $total_pages,
has_previous: $input.page > 1
}
}
Why: Xano's AI couldn't combine complex WHERE clauses with pagination. Manual pagination works perfectly.
Custom Data Import & Cleaning Function in Xano
**
AI Didn't Do This At All - Built Entirely From Scratch in Xano:
**
The biggest challenge wasn't the API endpoints—it was getting 1,000+ UN sanctions records cleaned, normalized, and imported into Xano. The raw UN data had inconsistent formatting, mixed cases, and missing fields. I built a custom Xano function to process and validate CSV data before import.
???? Summary of Improvements
| Aspect | AI Generated | My Improvements |
|---|---|---|
| Fuzzy Matching | ❌ Exact only | ✅ Partial + aliases |
| Match Scoring | ❌ None | ✅ 80-100 scale |
| Rate Limiting | ⚠️ Broken (no reset) | ✅ Hourly windows |
| Errors | ⚠️ Inconsistent | ✅ Standardized format |
| Pagination | ⚠️ Doesn't work with filters | ✅ Manual pagination |
| Data Import | ❌ Not provided | ✅ Complete pipeline |
| Case Sensitivity | ❌ Fails on "eric" vs "ERIC" | ✅ Normalized search |
| Alias Search | ❌ Name only | ✅ Name + aka fields |
Time Saved by AI: ~40% (basic structure, auth, validation)
Time Spent Fixing: ~60% (all the critical production features)
My Experience with Xano
✅ What I Loved
1. Visual Query Builder
The visual database query builder is fantastic for simple queries. Click, drag, done. Way faster than writing SQL.
2. Built-in Authentication
Xano's auth system with Bearer tokens worked out of the box. No need to implement JWT signing/verification myself.
3. Function Stack Concept
The visual "function stack" makes it easy to see the flow of data through your endpoint. Great for debugging.
4. Instant API Docs
Xano auto-generates API documentation from your endpoints. Saved hours compared to writing Swagger specs.
5. Fast Iteration
Changes deploy instantly. No build/compile step. Made testing and iteration very fast.
???? The Challenges
1. Complex WHERE Clauses Don't Work
// This looks right but throws cryptic errors
where = ($db.table.field includes $var || $db.table.field2 == $var2)
Solution: Query everything, filter in memory with foreach loops. Less efficient but actually works.
2. AI Generates Invalid Syntax
Xano's AI often generated code that looked correct but had subtle syntax errors:
- Using
filterinstead ofreturn - Using
containsin WHERE clauses (not supported) - Missing proper operator precedence
Solution: Learn Xano's actual syntax.
3. Pagination + Filtering = Manual Work
Can't combine database pagination with complex filters. Had to implement manual pagination with slice.
4. Debugging is Hard
Error messages like "Missing var entry: api_key_record" don't tell you which line failed. Had to add lots of debugging output.
5. No Local Development
Everything is in the cloud. No way to run Xano locally or use version control effectively.
???? Tips for Others
1. Start Simple, Then Refine
Let the AI generate a basic version, then manually improve it. Don't expect perfection from AI.
2. Test Every Change
With no local dev environment, test in production. Make small changes and test immediately.
3. Use Foreach Loops for Complex Filtering
If your WHERE clause gets complex, just query all and filter in memory. More reliable.
4. Read the Docs
Xano's syntax is specific. The AI doesn't always know the latest syntax. Check official docs.
5. Keep Functions Small
Break complex logic into multiple small functions. Easier to debug when things break.
???? Final Verdict
Would I use Xano again? Yes, for rapid prototyping and MVPs.
Would I use it for production? Maybe, with reservations:
- ✅ Great for: REST APIs with simple queries
- ⚠️ Okay for: Medium complexity business logic
- ❌ Not for: Complex data transformations, high-performance needs
Best Feature: Speed of development. I built this in 3 days that would've taken 2 weeks with traditional backend.
Biggest Limitation: Complex queries require workarounds. Miss the power of raw SQL.
Overall Experience: 7/10. Powerful but has rough edges. The AI helps but isn't magic - you still need to know what you're doing.
???? Try It Yourself
The API is live and ready to use!
Quick Start:
# 1. Sign up
curl -X POST 'https://xvln-hqrc-qxwa.n7e.xano.io/api:QC35j52Y/auth/signup' \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com", "name": "Your Name", "password": "secure123"}'
# 2. Login
curl -X POST 'https://xvln-hqrc-qxwa.n7e.xano.io/api:QC35j52Y/auth/login' \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com", "password": "secure123"}'
# 3. Search (use the token from login response)
curl -X POST 'https://xvln-hqrc-qxwa.n7e.xano.io/api:QC35j52Y/search' \
-H 'Authorization: Bearer YOUR_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{"name": "ERIC BADEGE"}'
Free tier: 100 requests/hour - plenty for testing!
???? Future Enhancements
If I had more time, I'd add:
- More Data Sources: OFAC, EU sanctions, PEP databases
- Webhooks: Real-time notifications when watchlist updates
- Bulk Screening: Upload CSV of 1,000 names, get results
- Historical Tracking: See when someone was added/removed from lists
- Risk Scoring ML: Machine learning to improve match confidence
- SDKs: Python, JavaScript, PHP client libraries
???? Acknowledgments
- UN Security Council for maintaining the Consolidated Sanctions List
- Xano team for the AI-powered backend platform
- DEV Community for running this challenge
???? Stats
- Lines of Code: ~1,200 (Xano script)
- Development Time: 3 days
- Database Records: 1,000 verified entries
- API Endpoints: 4 (signup, login, search, list)
- Test Coverage: 15+ test cases
- Countries Covered: 50+ nationalities
- Sanctions Programs: 10+ UN programs
Built with ❤️ using Xano, real UN data, and a lot of debugging!
A comprehensive compliance API built with Xano, featuring real UN sanctions data, AI-powered fuzzy matching, and production-grade security - Dataset Download here limit to 1000 entries.
Popular Products
-
Put Me Down Funny Toilet Seat Sticker$33.56$16.78 -
Stainless Steel Tongue Scrapers$33.56$16.78 -
Stylish Blue Light Blocking Glasses$85.56$42.78 -
Adjustable Ankle Tension Rope$53.56$26.78 -
Electronic Bidet Toilet Seat$981.56$490.78