3pap Intelligence API
Integrate CV analysis, sanctions screening, and risk intelligence into any HR or procurement system. Our REST API works with any programming language and tech stack.
http://checker.3pap.africa/api/v1
Quick Start
- Get an API Key — Sign up, subscribe to a plan with API access, and generate a token from your Account page
- Set the Authorization header —
Authorization: Bearer 3pap_your_token - Make your first call — Try the health check endpoint below
curl http://checker.3pap.africa/api/v1/health
API Overview
| Category | Endpoints | Scope Required |
|---|---|---|
| CV Analysis | 11 endpoints — upload, batch, list, detail, career recommendation, skills gap, CV improvement, career coach, opportunity matches, report, delete | cv_analysis |
| Opportunity Matching | 6 endpoints — list, detail, apply, application tracking, candidate ranking, profile matches | cv_analysis |
| Talent Marketplace | 7 endpoints — search talent, detail, shortlist, invitations, and contact logging | cv_analysis |
| Consultant Database | 5 endpoints — search experts, create profiles, add project experience, and manage availability | cv_analysis |
| Sanctions Screening | 6 endpoints — screen, batch, entity, cross-ref, sources, stats | sanctions_search |
| PEP Screening | 7 endpoints — single-name check, batch/file checks, screening history, reports, sources, and monitored names | pep_check or sanctions_search |
| Vendor Risk | Vendor profile, screening, risk refresh, and risk score endpoints | vendor_risk |
| Webhooks | Create endpoints, test deliveries, and inspect webhook logs | webhooks |
| API Billing | Plan limits, billable usage, token requests, and active API access | usage_billing |
| Utility | Health check and usage stats | None / Any |
Authentication
All API requests (except /health) require a Bearer token in the Authorization header.
Authorization: Bearer 3pap_your_api_token_here
Getting Your API Token
- Self-service: Go to your Account page and click "Generate API Key" (requires a plan with API access)
- Admin-issued: Your organization's admin can generate tokens from the admin panel with specific scopes
Token Scopes
| Scope | Grants Access To |
|---|---|
cv_analysis | CV Analysis and Opportunity Matching endpoints (upload, profiles, reports, matching, applications) |
sanctions_search | All Sanctions Screening endpoints (screen, batch, entities, cross-ref, sources, stats) |
pep_check | PEP screening endpoints (single check, batch checks, screening history, PEP reports, source transparency, and monitoring list) |
vendor_risk | Vendor registration, vendor screening aliases, and risk score endpoints |
talent_search | Talent marketplace search, candidate detail, shortlists, invitations, and contact logging |
webhooks | Webhook endpoint management, delivery testing, and delivery history |
usage_billing | Subscription, API billing, usage limits, and token request metrics |
reputation_check | Reputation profile checks, result history, score trend, and scan metadata |
CV Analysis API
Upload CVs/resumes, extract structured data using NLP, predict job roles, and get comprehensive career analysis. Every CV is stored on the platform for future reference.
| Method | Endpoint | Description |
|---|---|---|
| POST | /cv/analyze | Upload and analyze a single CV |
| POST | /cv/batch | Upload and analyze multiple CVs (up to 10) |
| GET | /cv/profiles | List analyzed profiles |
| GET | /cv/profiles/:id | Get full profile with career analysis |
| GET | /cv/profiles/:id/career-recommendation | Get career paths, gaps, and next steps |
| GET | /cv/profiles/:id/skills-gap | Compare skills against a target role |
| GET | /cv/profiles/:id/cv-improvement | Score CV quality and generate improvement guidance |
| GET / POST | /cv/profiles/:id/career-coach | Ask career questions and get coaching guidance |
| GET | /cv/profiles/:id/opportunity-matches | Rank open jobs, internships, consultancies, and projects for one profile |
| GET | /cv/profiles/:id/report | Comprehensive report (career + sanctions + reputation) |
| DELETE | /cv/profiles/:id | Delete a profile |
/api/v1/cv/analyze
Upload and analyze a single CV file. The file is parsed using NLP to extract contact info, skills, experience, education, certifications, and job role predictions. A copy is stored on the platform.
Request
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
cv_file | File | Yes | PDF, DOCX, PNG, JPG, TIFF, or WEBP file (max 10MB) |
linkedin_url | String | No | Candidate's LinkedIn profile URL |
department | String | No | Department name (for your internal tagging) |
position | String | No | Position applied for |
reference_id | String | No | Your internal reference ID (e.g. applicant ID in your HR system) |
auto_sanctions_check | String | No | Set to "true" to automatically screen the candidate's companies against sanctions databases |
Response 201 Created
{
"success": true,
"profile": {
"id": 42,
"name": "John Doe",
"email": "john.doe@example.com",
"phone": "+1-555-0123",
"location": "New York, USA",
"linkedin_url": "https://linkedin.com/in/johndoe",
"summary": "Senior software engineer with 8+ years experience...",
"file_name": "john_doe_resume.pdf",
"created_at": "2026-02-18T10:30:00",
"is_sanctioned": false,
"reputation_score": null,
"skills": [
{"name": "Python", "category": "technical"},
{"name": "JavaScript", "category": "technical"},
{"name": "Leadership", "category": "soft"},
{"name": "AWS", "category": "technical"}
],
"experiences": [
{
"title": "Senior Software Engineer",
"company": "Tech Corp International",
"period": "Jan 2020 - Present",
"description": "Led a team of 8 engineers building cloud infrastructure..."
},
{
"title": "Software Engineer",
"company": "StartUp Inc",
"period": "Mar 2017 - Dec 2019",
"description": "Full-stack development of customer-facing web applications..."
}
],
"educations": [
{
"institution": "MIT",
"degree": "Bachelor of Science",
"field": "Computer Science",
"year": "2016"
}
],
"certifications": [
{"name": "AWS Solutions Architect", "issuer": "Amazon", "year": "2022"}
],
"social_links": [
{"platform": "github", "url": "https://github.com/johndoe", "username": "johndoe"},
{"platform": "linkedin", "url": "https://linkedin.com/in/johndoe", "username": "johndoe"}
],
"predictions": [
{"role_name": "Software Engineer", "confidence": 92.5},
{"role_name": "Full Stack Developer", "confidence": 78.3},
{"role_name": "Backend Developer", "confidence": 65.1}
],
"metadata": {
"department": "Engineering",
"position": "Senior Engineer",
"reference_id": "APP-2026-0042",
"api_submitted": true,
"submitted_by": "hr_system_user"
}
},
"sanctions_screening": {
"matches": 0,
"companies_checked": 2,
"profile_name_checked": true
}
}
/api/v1/cv/batch
Upload and analyze multiple CV files in a single request (up to 10 files). Each file is processed independently — failures in one file don't affect others. Ideal for bulk onboarding or recruitment pipelines.
Request
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
cv_files | File[] | Yes | One or more PDF, DOCX, PNG, JPG, TIFF, or WEBP files (max 10) |
auto_sanctions_check | String | No | Set to "true" to auto-screen each CV |
Response 201 Created
{
"success": true,
"total": 3,
"processed": 2,
"errors": 1,
"results": [
{
"file_name": "john_doe.pdf",
"success": true,
"profile": { "id": 42, "name": "John Doe", ... }
},
{
"file_name": "jane_smith.docx",
"success": true,
"profile": { "id": 43, "name": "Jane Smith", ... }
},
{
"file_name": "corrupted.pdf",
"success": false,
"error": "The uploaded file contains too little text to analyze.",
"code": "ANALYSIS_FAILED"
}
]
}
/api/v1/cv/profiles
List all analyzed CV profiles with filtering, search, and pagination.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | Integer | 1 | Page number |
per_page | Integer | 20 | Results per page (max 100) |
search | String | Filter by name, email, or location | |
sanctioned | String | "true" or "false" to filter by sanctions flag | |
sort | String | newest | "newest", "oldest", or "name" |
Response 200
{
"success": true,
"page": 1,
"per_page": 20,
"total": 156,
"pages": 8,
"profiles": [
{
"id": 42,
"name": "John Doe",
"email": "john@example.com",
"location": "New York, USA",
"file_name": "john_doe_resume.pdf",
"created_at": "2026-02-18T10:30:00",
"is_sanctioned": false,
"skills_count": 12,
"experience_count": 4,
"top_prediction": "Software Engineer"
}
]
}
/api/v1/cv/profiles/:id
Retrieve a full profile by ID including career trajectory analysis and career recommendation data.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | Integer | Profile ID |
Response 200
{
"success": true,
"profile": {
"id": 42,
"name": "John Doe",
"skills": [...],
"experiences": [...],
"educations": [...],
"certifications": [...],
"predictions": [...],
"career_analysis": {
"job_count": 4,
"average_tenure_months": 28.5,
"median_tenure_months": 24.0,
"total_career_span_months": 96,
"company_count": 3,
"promotion_count": 2,
"lateral_count": 1,
"demotion_count": 0,
"gap_count": 1,
"growth_velocity": 72,
"loyalty_score": 68,
"health_score": 78,
"trajectory_type": "climber",
"career_stage": {
"stage": "established",
"label": "Established",
"description": "Proven track record with significant domain expertise",
"years": 8.0
},
"flight_risk": {
"level": "moderate",
"score": 45,
"factors": ["Strong average tenure (>36 months)"]
},
"tenure_trend": {
"direction": "increasing",
"description": "Tenure is increasing over time",
"change_pct": 35
},
"role_consistency": {
"consistency_pct": 85,
"primary_domain": "Engineering"
}
},
"career_recommendation": {
"career_match_percentage": 82,
"best_path": {
"title": "Data & AI",
"industry": "Technology / Data"
},
"recommended_next_steps": [...]
},
"skills_gap_analysis": {
"target_role": "Data Scientist",
"readiness_score": 68,
"readiness_label": "Nearly Ready"
},
"cv_improvement": {
"cv_quality_score": 76,
"cv_quality_label": "Good",
"ats_readiness_score": 81
},
"career_coach": {
"target_role": "Data Scientist",
"answer": "Your best coaching focus right now is Data Scientist...",
"monthly_progress_review": {
"progress_score": 74,
"label": "Improving"
}
},
"opportunity_matches": [
{
"opportunity": {"id": 9, "title": "Data Analyst"},
"match": {"score": 84, "label": "Strong Match"}
}
]
}
}
/api/v1/cv/profiles/:id/career-recommendation
Generate the career recommendation module output for a parsed CV profile, including best career paths, match percentage, skill strengths, gaps, level mapping, progression path, and next steps.
Response 200
{
"success": true,
"profile_id": 42,
"career_recommendation": {
"career_match_percentage": 82,
"best_path": {
"title": "Data & AI",
"industry": "Technology / Data",
"matched_core_skills": ["python", "sql", "data analysis"],
"missing_core_skills": ["Machine Learning", "Statistics"]
},
"skill_strength": {
"overall_score": 78,
"label": "Strong"
},
"weakness_gap_analysis": {
"critical_skill_gaps": ["Machine Learning", "Statistics"],
"profile_gaps": []
},
"level_mapping": {
"current_label": "Mid Level",
"recommended_level": "mid"
},
"recommended_next_steps": [...]
}
}
/api/v1/cv/profiles/:id/skills-gap
Compare a parsed CV profile against a target job role and return missing technical skills, missing soft skills, readiness score, ranked skill importance, courses, certifications, and an improvement roadmap.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
target_role | String | Top predicted role | Role to compare against, e.g. "Data Scientist" |
Response 200
{
"success": true,
"profile_id": 42,
"skills_gap_analysis": {
"target_role": "Data Scientist",
"readiness_score": 68,
"readiness_label": "Nearly Ready",
"missing_technical_skills": [
{"skill": "Machine Learning", "category": "Technical"}
],
"missing_soft_skills": [
{"skill": "Presentation", "category": "Soft"}
],
"recommended_certifications": [...],
"recommended_courses": [...],
"important_skills": [...],
"improvement_roadmap": [...]
}
}
/api/v1/cv/profiles/:id/cv-improvement
Score a parsed CV for quality, ATS readiness, grammar and formatting, section completeness, keyword coverage, and practical improvement guidance. The response also includes rewritten experience bullet suggestions, a generated professional summary, template recommendation, and downloadable improved CV text.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
target_role | String | Top predicted role | Role used for keyword optimization and template recommendations |
Response 200
{
"success": true,
"profile_id": 42,
"cv_improvement": {
"target_role": "Data Scientist",
"cv_quality_score": 76,
"cv_quality_label": "Good",
"ats_readiness_score": 81,
"grammar_formatting": {
"score": 74,
"checks": [...]
},
"missing_section_alerts": [...],
"keyword_optimization": {
"coverage_percentage": 68,
"matched_keywords": ["Python", "SQL"],
"recommended_keywords": ["Machine Learning", "Statistics"]
},
"experience_rewriting_suggestions": [...],
"professional_summary_generator": {
"generated_summary": "John Doe is an experienced professional..."
},
"skills_improvement_suggestions": [...],
"cv_template_recommendation": {
"name": "ATS Standard Template",
"sections": [...]
},
"download": {
"filename": "improved-cv-john-doe.txt",
"mimetype": "text/plain"
}
}
}
/api/v1/cv/profiles/:id/career-coach
Ask profile-aware career questions and receive interactive coaching guidance. The coach can explain career options, recommend learning paths, prepare interview guidance, suggest job titles and industries, provide broad salary research guidance, draft cover letters, prepare LinkedIn profile content, and produce a monthly progress review.
Query Parameters / JSON Body
| Parameter | Type | Default | Description |
|---|---|---|---|
question | String | User's career question, such as "How should I prepare for interviews?" | |
target_role | String | Top predicted role | Target role used for coaching context |
focus | String | general | general, career_options, learning_path, interview, job_titles, industries, salary, cover_letter, linkedin, or monthly_review |
Response 200
{
"success": true,
"profile_id": 42,
"career_coach": {
"target_role": "Data Scientist",
"question": "How should I prepare for interviews?",
"answer": "Start with this pitch...",
"career_options": [...],
"learning_paths": [...],
"interview_preparation": {
"elevator_pitch": "...",
"likely_questions": [...]
},
"job_title_suggestions": [...],
"industry_suggestions": [...],
"salary_guidance": {
"estimated_range": "$75,000 - $120,000",
"market_note": "Use this as a broad benchmark only..."
},
"cover_letter": {
"draft": "Dear Hiring Manager..."
},
"linkedin_profile": {
"headline": "Data Scientist | Python | SQL"
},
"monthly_progress_review": {
"progress_score": 74,
"label": "Improving"
},
"next_actions": [...]
}
}
Opportunity Matching API
List opportunities, score fit against parsed CV profiles, submit applications, track application status, and rank candidates for admin/employer workflows.
| Method | Endpoint | Description |
|---|---|---|
| GET | /opportunities | List open opportunities with filters and optional profile match |
| GET | /opportunities/:id | Get one opportunity with optional profile match |
| POST | /opportunities/:id/apply | Submit an application using a stored CV profile |
| GET | /opportunity-applications | Track submitted applications and interview invitations |
| GET | /opportunities/:id/candidates | Admin-only candidate ranking for an opportunity |
| GET | /cv/profiles/:id/opportunity-matches | Rank opportunities for one profile |
List Opportunities
curl -H "Authorization: Bearer 3pap_your_token" \
"http://checker.3pap.africa/api/v1/opportunities?profile_id=42&type=job&work_mode=remote"
Application Request
{
"profile_id": 42,
"cover_letter": "I am interested in this role because..."
}
Response 200
{
"success": true,
"opportunities": [
{
"id": 9,
"title": "Data Analyst",
"type": "job",
"organization": "3PAP Africa",
"required_skills": ["Python", "SQL", "Power BI"],
"salary_display": "USD 2,000 - 3,500",
"match": {
"score": 84,
"label": "Strong Match",
"matched_skills": ["Python", "SQL"],
"missing_skills": ["Power BI"],
"reasons": [...]
}
}
]
}
Talent Marketplace API
Search marketplace-visible candidates, score fit with AI, manage shortlists, record invitations, and log contact activity for employer and organization workflows.
| Method | Endpoint | Description |
|---|---|---|
| GET | /talent | Search and AI-rank talent by skill, role, country, education, experience, availability, and verification |
| GET | /talent/:profile_id | Get one candidate profile with match explanation and marketplace metadata |
| GET | /talent/shortlist | List your shortlisted candidates |
| POST | /talent/:profile_id/shortlist | Add, restore, or archive a candidate shortlist record |
| GET | /talent/invitations | List your talent invitations |
| POST | /talent/:profile_id/invite | Record a candidate invitation, optionally linked to an opportunity |
| POST | /talent/:profile_id/contact | Log email, phone, LinkedIn, or other contact activity |
Search Talent
curl -H "Authorization: Bearer 3pap_your_token" \
"http://checker.3pap.africa/api/v1/talent?skill=Python,SQL&country=Ghana&min_score=65"
Shortlist Request
{
"action": "shortlist",
"notes": "Strong fit for donor reporting analyst role"
}
Response 200
{
"success": true,
"talent": [
{
"profile": {
"id": 42,
"name": "Amina Mensah",
"country": "Ghana",
"availability_label": "Available",
"verified_consultant": true
},
"match": {
"score": 86,
"label": "Excellent Fit",
"matched_skills": ["Python", "SQL"],
"experience_years": 6.5,
"reasons": [...]
},
"shortlisted": false
}
]
}
Consultant Database API
Build and query a curated expert database for donor, NGO, AU, and government projects, including expertise, donor-funded experience, country experience, rates, verification, and availability.
| Method | Endpoint | Description |
|---|---|---|
| GET | /consultants | Search and AI-rank consultants by category, expertise, country, donor, rate, availability, and verification |
| POST | /consultants | Create a consultant profile |
| GET | /consultants/:id | Get one consultant profile with ranking details |
| POST | /consultants/:id/projects | Add donor, client, country, and project experience |
| POST | /consultants/:id/availability | Add availability calendar slots |
Search Consultants
curl -H "Authorization: Bearer 3pap_your_token" \
"http://checker.3pap.africa/api/v1/consultants?category=procurement_specialist&country=Ghana&donor=World%20Bank"
Create Consultant
{
"name": "Amina Mensah",
"category": "me_expert",
"areas_of_expertise": ["Results frameworks", "Data quality assessment"],
"country_experience": ["Ghana", "Kenya"],
"donor_experience": "World Bank and EU-funded monitoring assignments",
"daily_rate": 650,
"currency": "USD",
"availability_status": "available"
}
/api/v1/cv/profiles/:id/report
Get a comprehensive JSON report combining profile data, career analysis, career recommendations, sanctions matches, and summary scores. Designed for HR systems to display full candidate reports.
Response 200
{
"success": true,
"report": {
"id": 42,
"name": "John Doe",
"skills": [...],
"experiences": [...],
"educations": [...],
"certifications": [...],
"social_links": [...],
"predictions": [...],
"career_analysis": { ... },
"career_recommendation": { ... },
"skills_gap_analysis": { ... },
"cv_improvement": { ... },
"career_coach": { ... },
"opportunity_matches": [
{
"opportunity": {
"id": 9,
"title": "Data Analyst",
"type": "job"
},
"match": {
"score": 84,
"label": "Strong Match"
}
}
],
"sanctions_matches": [
{
"entity_id": 156,
"entity_name": "Tech Corp International",
"match_type": "fuzzy",
"confidence": 0.82,
"matched_field": "company",
"matched_value": "Tech Corp International",
"entity_dataset": "World Bank",
"entity_country": "Nigeria",
"entity_is_active": true,
"checked_at": "2026-02-18T11:00:00"
}
],
"report_summary": {
"total_skills": 12,
"technical_skills": 8,
"soft_skills": 4,
"total_experience": 4,
"total_education": 2,
"total_certifications": 3,
"sanctions_flag": false,
"sanctions_matches_count": 0,
"reputation_score": null,
"top_predicted_role": "Software Engineer",
"top_prediction_confidence": 92.5
}
}
}
/api/v1/cv/profiles/:id
Permanently delete a profile and all associated data (skills, experiences, educations, certifications, predictions, sanctions matches).
Response 200
{
"success": true,
"message": "Profile \"John Doe\" (ID: 42) has been deleted."
}
Sanctions Screening API
Screen companies and individuals against 114 international sanctions and debarment databases using NLP-powered fuzzy matching. Covers development banks, government sanctions, and law enforcement lists across 8 regions.
| Method | Endpoint | Description |
|---|---|---|
| POST | /sanctions/screen | Screen a single entity |
| POST | /sanctions/batch | Batch screen multiple entities (up to 50) |
| GET | /sanctions/entity/:id | Full entity detail |
| POST | /sanctions/cross-reference/:profile_id | Cross-reference CV profile |
| GET | /sanctions/sources | List all data sources |
| GET | /sanctions/stats | Database statistics |
/api/v1/sanctions/screen
Screen a single company or individual name against all sanctions databases. Uses NLP-powered fuzzy matching to catch spelling variations and aliases. Returns a risk assessment level.
Request Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Entity name to screen (company or person) |
country | String | No | Filter results by country |
dataset | String | No | Filter by data source (e.g. "World Bank") |
type | String | No | "person" or "company" to filter entity type |
max_results | Integer | No | Max results to return (default 20, max 100) |
Response 200
{
"success": true,
"query": "Acme Corporation",
"total_matches": 2,
"risk_level": "high",
"results": [
{
"id": 156,
"source_id": "Q-123456",
"name": "Acme Corporation Ltd",
"aliases": ["ACME Corp", "Acme Group"],
"country": "Nigeria",
"address": "123 Main Street, Lagos",
"program": "Fraud and Corruption",
"sanction_type": "Debarment",
"dataset": "World Bank",
"entity_type": "LegalEntity",
"is_active": true,
"start_date": "2020-01-15",
"end_date": "2025-01-15",
"source_url": "https://...",
"match_score": 0.956,
"match_type": "exact"
}
]
}
Risk Levels
| Level | Meaning |
|---|---|
critical | Active sanction with exact match (score ≥ 90%) |
high | Active sanction with fuzzy match (score ≥ 78%) |
medium | Active sanction with partial match |
low | Only expired or weak matches |
clear | No matches found |
/api/v1/sanctions/batch
Screen multiple entities in a single request (up to 50). Ideal for vendor lists, supplier databases, or employee rosters. Each entity consumes 1 sanctions search credit.
Request Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
entities | Array | Yes | Array of entities to screen (max 50) |
entities[].name | String | Yes | Entity name |
entities[].country | String | No | Filter by country |
{
"entities": [
{"name": "Acme Corporation", "country": "Nigeria"},
{"name": "Global Ventures Ltd"},
{"name": "John Smith", "country": "United Kingdom"}
]
}
Response 200
{
"success": true,
"total_screened": 3,
"total_flagged": 1,
"results": [
{
"name": "Acme Corporation",
"success": true,
"total_matches": 2,
"is_flagged": true,
"risk_level": "high",
"matches": [...]
},
{
"name": "Global Ventures Ltd",
"success": true,
"total_matches": 0,
"is_flagged": false,
"risk_level": "clear",
"matches": []
},
{
"name": "John Smith",
"success": true,
"total_matches": 0,
"is_flagged": false,
"risk_level": "clear",
"matches": []
}
]
}
/api/v1/sanctions/entity/:id
Get full details of a sanctions entity including all aliases, linked CV profiles, and source information.
Response 200
{
"success": true,
"entity": {
"id": 156,
"source_id": "Q-123456",
"name": "Acme Corporation Ltd",
"aliases": ["ACME Corp", "Acme Group", "Acme Holdings"],
"country": "Nigeria",
"address": "123 Main Street, Lagos, Nigeria",
"program": "Fraud and Corruption",
"sanction_type": "Debarment",
"dataset": "World Bank",
"entity_type": "LegalEntity",
"topics": "debarment",
"funding_organizations": "IBRD",
"is_active": true,
"start_date": "2020-01-15",
"end_date": "2025-01-15",
"source_url": "https://...",
"first_seen": "2024-06-01",
"last_seen": "2026-02-15",
"fetched_at": "2026-02-15T08:00:00",
"linked_profiles": [
{
"profile_id": 42,
"profile_name": "John Doe",
"match_type": "fuzzy",
"confidence": 0.82,
"matched_field": "company",
"matched_value": "Acme Corp",
"checked_at": "2026-02-18T11:00:00"
}
],
"source_info": {
"slug": "worldbank_debarred",
"name": "World Bank",
"short": "WB",
"color": "#1a73e8",
"category": "Development Banks"
}
}
}
/api/v1/sanctions/cross-reference/:profile_id
Cross-reference a CV profile's company names and personal name against the sanctions database. Checks all employers listed in the candidate's work experience. This is automatically run when using auto_sanctions_check=true during CV upload.
Response 200
{
"success": true,
"profile_id": 42,
"profile_name": "John Doe",
"is_sanctioned": false,
"companies_checked": 3,
"total_matches": 0,
"matches": []
}
/api/v1/sanctions/sources
List all available sanctions data sources with entity counts. Currently covering 114 sources across 8 regions.
Response 200
{
"success": true,
"total_sources": 114,
"sources": [
{"slug": "worldbank_debarred", "name": "World Bank", "short": "WB", "category": "Development Banks", "color": "#1a73e8", "entity_count": 1234},
{"slug": "us_ofac_sdn", "name": "US OFAC SDN", "short": "OFAC", "category": "Americas", "color": "#0d47a1", "entity_count": 8500},
{"slug": "eu_fsf", "name": "EU Financial Sanctions", "short": "EU", "category": "Europe", "color": "#003399", "entity_count": 3200},
...
]
}
/api/v1/sanctions/stats
Get database-wide sanctions statistics including entity counts, active/expired breakdown, top countries, and last sync time.
Response 200
{
"success": true,
"statistics": {
"total_entities": 25000,
"active_debarments": 18500,
"expired": 6500,
"by_dataset": {
"World Bank": 1234,
"US OFAC SDN": 8500,
"EU Financial Sanctions": 3200
},
"top_countries": {
"United States": 3500,
"Russia": 2800,
"Iran": 1900
},
"matched_profiles": 12,
"last_sync": "2026-02-18T08:00:00",
"total_sources": 114
}
}
PEP Screening API
Run PEPChecker-style checks for politically exposed persons, related people, sanctions exposure, saved history, reports, and monitoring.
| Method | Endpoint | Description |
|---|---|---|
| POST | /pep/check | Check one name with name or firstName/lastName, DOB range, related people, and optional sanctions |
| GET | /check | Compatibility alias for simple firstName/lastName PEP and sanctions checks |
| POST | /pep/batch | Check up to 50 names from JSON or a CSV/text file upload |
| GET | /pep/screenings | List saved PEP screening history |
| GET | /pep/screenings/:id/report?format=pdf | Download HTML or PDF audit reports |
| GET | /pep/sources | View PEP and sanctions source coverage |
| GET | /pep/monitoring | List monitored PEP names |
/api/v1/pep/check
Authenticate with Authorization: Bearer or an api-key header.
curl -X POST "http://checker.3pap.africa/api/v1/pep/check" \
-H "api-key: 3pap_your_token" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jane",
"lastName": "Doe",
"country": "Ghana",
"dobStart": "1970-01-01",
"dobEnd": "1985-12-31",
"includeSanctions": true,
"monitor": true
}'
/api/v1/pep/batch
Submit JSON names or upload a CSV/text file with columns such as name,country,subject_type.
/api/v1/pep/monitoring
Returns active monitored names. New monitored names can be created from POST /pep/check by sending "monitor": true.
Reputation Intelligence API
Run web reputation checks for analyzed profiles and retrieve score trends, categorized results, and scan history.
| Method | Endpoint | Description |
|---|---|---|
| GET | /reputation/profiles/:id | Read score, trend, result breakdown, recent results, and recent scans |
| POST | /reputation/profiles/:id/check | Run a manual reputation check for an accessible profile |
| GET | /reputation/profiles/:id/scans | List scan history with optional result details |
curl -X POST "http://checker.3pap.africa/api/v1/reputation/profiles/42/check" \
-H "Authorization: Bearer 3pap_your_token"
Vendor Risk API
Create and inspect vendor profiles, run procurement screening, and retrieve current risk status. Vendor endpoints accept either vendor_risk or legacy sanctions_search scope.
| Method | Endpoint | Description |
|---|---|---|
| GET | /vendors | List vendors with search, status, country, category, and risk filters |
| POST | /vendors | Create a vendor company profile |
| GET | /vendor-risk/:id | Read a vendor profile and current risk fields |
| POST | /vendor-risk/:id/refresh | Run sanctions and procurement screening for one vendor |
| GET | /risk-scores | List trust and risk scores for vendors, consultants, people, and companies |
curl -X POST "http://checker.3pap.africa/api/v1/vendor-risk/42/refresh" \
-H "Authorization: Bearer 3pap_your_token"
Webhooks API
Subscribe external systems to platform events. Delivery payloads are signed with X-3PAP-Signature using HMAC-SHA256 and the endpoint secret.
| Method | Endpoint | Description |
|---|---|---|
| GET | /webhooks/events | List supported webhook events |
| GET | /webhooks | List webhook endpoints |
| POST | /webhooks | Create a webhook endpoint |
| POST | /webhooks/:id/test | Send a signed test delivery |
| GET | /webhooks/deliveries | Inspect delivery status, attempts, and response codes |
{
"name": "Production CRM",
"url": "https://example.com/webhooks/3pap",
"event_types": ["cv.analysis.completed", "vendor.risk.updated"]
}
API Billing
Use billing endpoints to reconcile subscription access, token traffic, and monthly plan limits inside your own system.
/api/v1/billing
Returns the current plan, billable usage counters, active tokens, and API request totals. Requires usage_billing scope.
{
"success": true,
"billing": {
"period": "2026-06",
"plan": {"name": "Enterprise", "api_access": true},
"tokens": {"total": 3, "active": 2, "requests_count": 1840},
"billable_usage": {
"cv_analysis": {"used": 45, "limit": 500, "remaining": 455},
"vendor_screening": {"used": 12, "limit": 1000, "remaining": 988}
}
}
}
Utility Endpoints
/api/v1/health
No Auth
Health check endpoint. No authentication required. Use this to verify API availability and check service status.
{
"status": "ok",
"version": "v1",
"services": {
"database": "connected",
"sanctions_sources": 114,
"sanctions_entities": 25000,
"cv_profiles": 156
}
}
/api/v1/usage
Check your current API usage and remaining monthly quota.
{
"success": true,
"user": "hr_admin",
"plan": "Enterprise",
"token": {
"name": "Production Key",
"prefix": "3pap_a1b2c3...",
"scopes": ["cv_analysis", "sanctions_search"],
"requests_count": 1542,
"created_at": "2026-01-15T08:00:00",
"last_used_at": "2026-02-18T14:23:00"
},
"usage": {
"cv_analysis": {"used": 45, "limit": 500, "remaining": 455},
"sanctions_search": {"used": 120, "limit": 1000, "remaining": 880},
"reputation_check": {"used": 0, "limit": 100, "remaining": 100}
}
}
Code Examples
Complete integration examples in 8 programming languages. Select your language to see all examples.
Python (requests)
import requests
API_BASE = "http://checker.3pap.africa/api/v1"
API_TOKEN = "3pap_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}
# ─── 1. Analyze a CV ───────────────────────────────────────────
with open("resume.pdf", "rb") as f:
resp = requests.post(
f"{API_BASE}/cv/analyze",
headers=headers,
files={"cv_file": ("resume.pdf", f, "application/pdf")},
data={
"department": "Engineering",
"position": "Senior Developer",
"reference_id": "APP-2026-0042",
"auto_sanctions_check": "true",
},
)
result = resp.json()
profile = result["profile"]
print(f"Profile ID: {profile['id']}")
print(f"Name: {profile['name']}")
print(f"Skills: {[s['name'] for s in profile['skills']]}")
print(f"Top Role: {profile['predictions'][0]['role_name']}")
print(f"Sanctioned: {profile['is_sanctioned']}")
# ─── 2. Get Full Report ───────────────────────────────────────
resp = requests.get(
f"{API_BASE}/cv/profiles/{profile['id']}/report",
headers=headers,
)
report = resp.json()["report"]
summary = report["report_summary"]
print(f"Technical Skills: {summary['technical_skills']}")
print(f"Career Health: {report['career_analysis']['health_score']}")
# ─── 3. Screen a Company ──────────────────────────────────────
resp = requests.post(
f"{API_BASE}/sanctions/screen",
headers=headers,
json={"name": "Acme Corporation", "country": "Nigeria"},
)
screening = resp.json()
print(f"Risk Level: {screening['risk_level']}")
print(f"Matches: {screening['total_matches']}")
# ─── 4. Batch Screen Vendors ──────────────────────────────────
resp = requests.post(
f"{API_BASE}/sanctions/batch",
headers=headers,
json={
"entities": [
{"name": "Vendor A Ltd", "country": "South Africa"},
{"name": "Supplier B Inc", "country": "Ghana"},
{"name": "Contractor C", "country": "Kenya"},
]
},
)
batch = resp.json()
print(f"Screened: {batch['total_screened']}, Flagged: {batch['total_flagged']}")
for r in batch["results"]:
status = "FLAGGED" if r["is_flagged"] else "CLEAR"
print(f" {r['name']}: {status} ({r['risk_level']})")
# ─── 5. Check Usage ───────────────────────────────────────────
resp = requests.get(f"{API_BASE}/usage", headers=headers)
usage = resp.json()["usage"]
for action, data in usage.items():
print(f"{action}: {data['used']}/{data['limit']} ({data['remaining']} remaining)")
PHP (cURL)
<?php
$API_BASE = "http://checker.3pap.africa/api/v1";
$API_TOKEN = "3pap_your_token_here";
function apiRequest($method, $url, $data = null, $isFile = false) {
global $API_BASE, $API_TOKEN;
$ch = curl_init($API_BASE . $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . $API_TOKEN,
]);
if ($method === "POST") {
curl_setopt($ch, CURLOPT_POST, true);
if ($isFile) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
} else {
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . $API_TOKEN,
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
} elseif ($method === "DELETE") {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ["code" => $httpCode, "body" => json_decode($response, true)];
}
// ─── 1. Analyze a CV ───────────────────────────────────────────
$cvFile = new CURLFile("/path/to/resume.pdf", "application/pdf", "resume.pdf");
$result = apiRequest("POST", "/cv/analyze", [
"cv_file" => $cvFile,
"department" => "Engineering",
"position" => "Senior Developer",
"reference_id" => "APP-2026-0042",
"auto_sanctions_check" => "true",
], true);
$profile = $result["body"]["profile"];
echo "Profile ID: " . $profile["id"] . "\n";
echo "Name: " . $profile["name"] . "\n";
echo "Sanctioned: " . ($profile["is_sanctioned"] ? "YES" : "NO") . "\n";
// ─── 2. Get Full Report ───────────────────────────────────────
$report = apiRequest("GET", "/cv/profiles/" . $profile["id"] . "/report");
$summary = $report["body"]["report"]["report_summary"];
echo "Skills: " . $summary["total_skills"] . "\n";
// ─── 3. Screen a Company ──────────────────────────────────────
$screening = apiRequest("POST", "/sanctions/screen", [
"name" => "Acme Corporation",
"country" => "Nigeria",
]);
echo "Risk Level: " . $screening["body"]["risk_level"] . "\n";
// ─── 4. Batch Screen ─────────────────────────────────────────
$batch = apiRequest("POST", "/sanctions/batch", [
"entities" => [
["name" => "Vendor A Ltd", "country" => "South Africa"],
["name" => "Supplier B Inc", "country" => "Ghana"],
],
]);
echo "Flagged: " . $batch["body"]["total_flagged"] . "\n";
// ─── 5. Check Usage ──────────────────────────────────────────
$usage = apiRequest("GET", "/usage");
foreach ($usage["body"]["usage"] as $action => $data) {
echo "$action: {$data['used']}/{$data['limit']}\n";
}
?>
JavaScript / Node.js
const fs = require("fs");
const FormData = require("form-data");
const API_BASE = "http://checker.3pap.africa/api/v1";
const API_TOKEN = "3pap_your_token_here";
const headers = { Authorization: `Bearer ${API_TOKEN}` };
async function apiRequest(method, path, body = null, isForm = false) {
const url = `${API_BASE}${path}`;
const opts = { method, headers: { ...headers } };
if (body && isForm) {
opts.body = body;
Object.assign(opts.headers, body.getHeaders());
} else if (body) {
opts.headers["Content-Type"] = "application/json";
opts.body = JSON.stringify(body);
}
const resp = await fetch(url, opts);
return resp.json();
}
// ─── 1. Analyze a CV ───────────────────────────────────────────
const form = new FormData();
form.append("cv_file", fs.createReadStream("resume.pdf"));
form.append("department", "Engineering");
form.append("position", "Senior Developer");
form.append("reference_id", "APP-2026-0042");
form.append("auto_sanctions_check", "true");
const analyzeResult = await apiRequest("POST", "/cv/analyze", form, true);
const profile = analyzeResult.profile;
console.log(`Profile ID: ${profile.id}`);
console.log(`Name: ${profile.name}`);
console.log(`Skills: ${profile.skills.map((s) => s.name).join(", ")}`);
console.log(`Sanctioned: ${profile.is_sanctioned}`);
// ─── 2. Get Full Report ───────────────────────────────────────
const report = await apiRequest("GET", `/cv/profiles/${profile.id}/report`);
console.log(`Health Score: ${report.report.career_analysis?.health_score}`);
// ─── 3. Screen a Company ──────────────────────────────────────
const screening = await apiRequest("POST", "/sanctions/screen", {
name: "Acme Corporation",
country: "Nigeria",
});
console.log(`Risk: ${screening.risk_level}, Matches: ${screening.total_matches}`);
// ─── 4. Batch Screen ─────────────────────────────────────────
const batch = await apiRequest("POST", "/sanctions/batch", {
entities: [
{ name: "Vendor A Ltd", country: "South Africa" },
{ name: "Supplier B Inc", country: "Ghana" },
],
});
console.log(`Screened: ${batch.total_screened}, Flagged: ${batch.total_flagged}`);
// ─── 5. Check Usage ──────────────────────────────────────────
const usage = await apiRequest("GET", "/usage");
Object.entries(usage.usage).forEach(([action, data]) => {
console.log(`${action}: ${data.used}/${data.limit}`);
});
C# (.NET HttpClient)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
class ThreePapClient
{
private readonly HttpClient _client;
private const string ApiBase = "http://checker.3pap.africa/api/v1";
public ThreePapClient(string apiToken)
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", apiToken);
}
// ─── 1. Analyze a CV ───────────────────────────────────────
public async Task<JsonElement> AnalyzeCV(string filePath,
string department = "", string position = "", string referenceId = "")
{
using var form = new MultipartFormDataContent();
var fileStream = File.OpenRead(filePath);
form.Add(new StreamContent(fileStream), "cv_file",
Path.GetFileName(filePath));
if (!string.IsNullOrEmpty(department))
form.Add(new StringContent(department), "department");
if (!string.IsNullOrEmpty(position))
form.Add(new StringContent(position), "position");
if (!string.IsNullOrEmpty(referenceId))
form.Add(new StringContent(referenceId), "reference_id");
form.Add(new StringContent("true"), "auto_sanctions_check");
var resp = await _client.PostAsync($"{ApiBase}/cv/analyze", form);
var json = await resp.Content.ReadAsStringAsync();
return JsonDocument.Parse(json).RootElement;
}
// ─── 2. Get Full Report ───────────────────────────────────
public async Task<JsonElement> GetReport(int profileId)
{
var resp = await _client.GetAsync(
$"{ApiBase}/cv/profiles/{profileId}/report");
var json = await resp.Content.ReadAsStringAsync();
return JsonDocument.Parse(json).RootElement;
}
// ─── 3. Screen Entity ─────────────────────────────────────
public async Task<JsonElement> ScreenEntity(string name,
string country = "")
{
var body = new { name, country };
var content = new StringContent(
JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
var resp = await _client.PostAsync(
$"{ApiBase}/sanctions/screen", content);
var json = await resp.Content.ReadAsStringAsync();
return JsonDocument.Parse(json).RootElement;
}
// ─── 4. Batch Screen ──────────────────────────────────────
public async Task<JsonElement> BatchScreen(
List<Dictionary<string, string>> entities)
{
var body = new { entities };
var content = new StringContent(
JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
var resp = await _client.PostAsync(
$"{ApiBase}/sanctions/batch", content);
var json = await resp.Content.ReadAsStringAsync();
return JsonDocument.Parse(json).RootElement;
}
// ─── 5. Check Usage ───────────────────────────────────────
public async Task<JsonElement> GetUsage()
{
var resp = await _client.GetAsync($"{ApiBase}/usage");
var json = await resp.Content.ReadAsStringAsync();
return JsonDocument.Parse(json).RootElement;
}
}
// Usage:
var client = new ThreePapClient("3pap_your_token_here");
var result = await client.AnalyzeCV("resume.pdf",
department: "Engineering", position: "Senior Dev");
Console.WriteLine($"Profile: {result.GetProperty("profile").GetProperty("name")}");
var screening = await client.ScreenEntity("Acme Corp", "Nigeria");
Console.WriteLine($"Risk: {screening.GetProperty("risk_level")}");
Java (HttpClient - Java 11+)
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
import com.google.gson.*;
public class ThreePapClient {
private static final String API_BASE = "http://checker.3pap.africa/api/v1";
private final HttpClient client;
private final String token;
public ThreePapClient(String apiToken) {
this.client = HttpClient.newHttpClient();
this.token = apiToken;
}
// ─── 1. Analyze a CV ───────────────────────────────────────
public JsonObject analyzeCV(String filePath) throws Exception {
String boundary = "----FormBoundary" + System.currentTimeMillis();
byte[] fileBytes = Files.readAllBytes(Path.of(filePath));
String fileName = Path.of(filePath).getFileName().toString();
String body = "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"cv_file\"; "
+ "filename=\"" + fileName + "\"\r\n"
+ "Content-Type: application/pdf\r\n\r\n";
String end = "\r\n--" + boundary
+ "\r\nContent-Disposition: form-data; "
+ "name=\"auto_sanctions_check\"\r\n\r\ntrue\r\n"
+ "--" + boundary + "--\r\n";
byte[] bodyBytes = concat(body.getBytes(), fileBytes, end.getBytes());
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/cv/analyze"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(bodyBytes))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
return JsonParser.parseString(resp.body()).getAsJsonObject();
}
// ─── 2. Screen Entity ─────────────────────────────────────
public JsonObject screenEntity(String name, String country)
throws Exception {
JsonObject body = new JsonObject();
body.addProperty("name", name);
if (country != null) body.addProperty("country", country);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/sanctions/screen"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
return JsonParser.parseString(resp.body()).getAsJsonObject();
}
// ─── 3. Batch Screen ──────────────────────────────────────
public JsonObject batchScreen(String[][] entities) throws Exception {
JsonArray arr = new JsonArray();
for (String[] e : entities) {
JsonObject obj = new JsonObject();
obj.addProperty("name", e[0]);
if (e.length > 1) obj.addProperty("country", e[1]);
arr.add(obj);
}
JsonObject body = new JsonObject();
body.add("entities", arr);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/sanctions/batch"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
return JsonParser.parseString(resp.body()).getAsJsonObject();
}
private static byte[] concat(byte[]... arrays) {
int len = 0;
for (byte[] a : arrays) len += a.length;
byte[] result = new byte[len];
int pos = 0;
for (byte[] a : arrays) {
System.arraycopy(a, 0, result, pos, a.length);
pos += a.length;
}
return result;
}
// Usage
public static void main(String[] args) throws Exception {
var api = new ThreePapClient("3pap_your_token_here");
var result = api.analyzeCV("resume.pdf");
System.out.println("Name: " + result
.getAsJsonObject("profile").get("name"));
var screen = api.screenEntity("Acme Corp", "Nigeria");
System.out.println("Risk: " + screen.get("risk_level"));
}
}
React (with Hooks)
// ─── api.js - API Client ──────────────────────────────────────
const API_BASE = "http://checker.3pap.africa/api/v1";
export async function apiRequest(method, path, body, isFormData = false) {
const token = localStorage.getItem("threepap_api_token");
const headers = { Authorization: `Bearer ${token}` };
const opts = { method, headers };
if (body && isFormData) {
opts.body = body; // FormData sets its own Content-Type
} else if (body) {
headers["Content-Type"] = "application/json";
opts.body = JSON.stringify(body);
}
const resp = await fetch(`${API_BASE}${path}`, opts);
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || "API request failed");
}
return resp.json();
}
// ─── CVUploader.jsx - Upload & Analyze CV ─────────────────────
import { useState } from "react";
import { apiRequest } from "./api";
export function CVUploader({ onAnalyzed }) {
const [uploading, setUploading] = useState(false);
const [error, setError] = useState(null);
const handleUpload = async (e) => {
e.preventDefault();
setUploading(true);
setError(null);
const formData = new FormData(e.target);
formData.append("auto_sanctions_check", "true");
try {
const result = await apiRequest(
"POST", "/cv/analyze", formData, true
);
onAnalyzed(result.profile);
} catch (err) {
setError(err.message);
} finally {
setUploading(false);
}
};
return (
<form onSubmit={handleUpload}>
<input type="file" name="cv_file" accept=".pdf,.docx,.png,.jpg,.jpeg,.tif,.tiff,.webp"
required />
<input type="text" name="department"
placeholder="Department" />
<input type="text" name="position"
placeholder="Position" />
<input type="text" name="reference_id"
placeholder="Internal Reference ID" />
<button type="submit" disabled={uploading}>
{uploading ? "Analyzing..." : "Upload & Analyze"}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
// ─── SanctionsChecker.jsx - Screen Companies ──────────────────
export function SanctionsChecker() {
const [name, setName] = useState("");
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const handleScreen = async () => {
setLoading(true);
try {
const data = await apiRequest("POST", "/sanctions/screen",
{ name });
setResult(data);
} catch (err) {
alert(err.message);
} finally {
setLoading(false);
}
};
return (
<div>
<input value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Company or person name" />
<button onClick={handleScreen} disabled={loading}>
{loading ? "Screening..." : "Screen"}
</button>
{result && (
<div className={`risk-${result.risk_level}`}>
<h3>Risk: {result.risk_level.toUpperCase()}</h3>
<p>{result.total_matches} matches found</p>
{result.results.map((entity) => (
<div key={entity.id}>
<strong>{entity.name}</strong>
<span>{entity.dataset} - {entity.country}</span>
<span>Score: {Math.round(entity.match_score*100)}%</span>
</div>
))}
</div>
)}
</div>
);
}
Go (net/http)
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
const (
apiBase = "http://checker.3pap.africa/api/v1"
apiToken = "3pap_your_token_here"
)
func apiGet(path string) (map[string]interface{}, error) {
req, _ := http.NewRequest("GET", apiBase+path, nil)
req.Header.Set("Authorization", "Bearer "+apiToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
func apiPostJSON(path string, data interface{}) (map[string]interface{}, error) {
body, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", apiBase+path,
bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+apiToken)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
// ─── Analyze CV ──────────────────────────────────────────────
func analyzeCV(filePath string) (map[string]interface{}, error) {
file, _ := os.Open(filePath)
defer file.Close()
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
part, _ := writer.CreateFormFile("cv_file", filePath)
io.Copy(part, file)
writer.WriteField("auto_sanctions_check", "true")
writer.Close()
req, _ := http.NewRequest("POST", apiBase+"/cv/analyze", &buf)
req.Header.Set("Authorization", "Bearer "+apiToken)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
func main() {
// Analyze CV
result, _ := analyzeCV("resume.pdf")
profile := result["profile"].(map[string]interface{})
fmt.Printf("Name: %s\n", profile["name"])
// Screen entity
screening, _ := apiPostJSON("/sanctions/screen", map[string]string{
"name": "Acme Corporation",
"country": "Nigeria",
})
fmt.Printf("Risk: %s\n", screening["risk_level"])
// Check usage
usage, _ := apiGet("/usage")
fmt.Printf("Plan: %s\n", usage["plan"])
}
Ruby (Net::HTTP)
require 'net/http'
require 'json'
require 'uri'
API_BASE = "http://checker.3pap.africa/api/v1"
API_TOKEN = "3pap_your_token_here"
def api_get(path)
uri = URI("#{API_BASE}#{path}")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{API_TOKEN}"
resp = Net::HTTP.start(uri.hostname, uri.port,
use_ssl: uri.scheme == "https") { |h| h.request(req) }
JSON.parse(resp.body)
end
def api_post_json(path, data)
uri = URI("#{API_BASE}#{path}")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{API_TOKEN}"
req["Content-Type"] = "application/json"
req.body = data.to_json
resp = Net::HTTP.start(uri.hostname, uri.port,
use_ssl: uri.scheme == "https") { |h| h.request(req) }
JSON.parse(resp.body)
end
# ─── 1. Analyze a CV ───────────────────────────────────────────
uri = URI("#{API_BASE}/cv/analyze")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{API_TOKEN}"
form_data = [
["cv_file", File.open("resume.pdf"), {
filename: "resume.pdf",
content_type: "application/pdf"
}],
["department", "Engineering"],
["auto_sanctions_check", "true"],
]
req.set_form(form_data, "multipart/form-data")
resp = Net::HTTP.start(uri.hostname, uri.port,
use_ssl: uri.scheme == "https") { |h| h.request(req) }
result = JSON.parse(resp.body)
profile = result["profile"]
puts "Name: #{profile['name']}"
puts "Skills: #{profile['skills'].map { |s| s['name'] }.join(', ')}"
# ─── 2. Screen Entity ─────────────────────────────────────────
screening = api_post_json("/sanctions/screen", {
name: "Acme Corporation",
country: "Nigeria",
})
puts "Risk: #{screening['risk_level']}"
puts "Matches: #{screening['total_matches']}"
# ─── 3. Batch Screen ──────────────────────────────────────────
batch = api_post_json("/sanctions/batch", {
entities: [
{ name: "Vendor A Ltd", country: "South Africa" },
{ name: "Supplier B Inc", country: "Ghana" },
],
})
puts "Flagged: #{batch['total_flagged']}/#{batch['total_screened']}"
# ─── 4. Check Usage ───────────────────────────────────────────
usage = api_get("/usage")
usage["usage"].each do |action, data|
puts "#{action}: #{data['used']}/#{data['limit']}"
end
Integration Guides
HR System Integration
Integrate CV screening into your HR/ATS (Applicant Tracking System) workflow. The typical flow:
- A copy of every CV is stored on 3pap for audit and compliance
- Use
reference_idto link profiles to your internal applicant IDs - Use
auto_sanctions_check=trueto automatically screen candidates - The
/cv/profiles/:id/reportendpoint provides everything needed for a full candidate report
Procurement / Vendor Screening Integration
Screen vendors, suppliers, and contractors against international sanctions lists before onboarding or contract renewal.
POST /sanctions/batch to screen your entire vendor list at once (up to 50 per request). Run this weekly or monthly to catch newly added sanctions.
Rate Limits & Usage
API usage is governed by your subscription plan. Each action type has a monthly limit that resets on the 1st of each month.
| Action | Triggered By | Limit Source |
|---|---|---|
| CV Analysis | POST /cv/analyze, POST /cv/batch (per file) | Plan's cv_analyses_per_month |
| Sanctions Search | POST /sanctions/screen, POST /sanctions/batch (per entity), POST /sanctions/cross-reference, GET /sanctions/search | Plan's sanctions_searches_per_month |
| Vendor Screening | POST /vendors/:id/screen, POST /vendor-risk/:id/refresh | Plan's vendor_screening_limit |
| Report Download | Report endpoints across CV, compliance, risk, verification, and intelligence modules | Plan's report_download_limit |
| Reputation Check | POST /reputation/profiles/:id/check | Plan's reputation_checks_per_month |
GET /usage.
{
"error": "Monthly sanctions search limit reached (1000/1000).",
"code": "LIMIT_REACHED",
"usage": {"used": 1000, "limit": 1000, "remaining": 0}
}
Error Handling
All errors return a consistent JSON format with an HTTP status code, error message, and machine-readable error code.
{
"error": "Human-readable error message",
"code": "ERROR_CODE"
}
Error Codes Reference
| HTTP | Code | Description |
|---|---|---|
| 400 | MISSING_FILE | No CV file provided in upload request |
| 400 | INVALID_FILE | File is not a valid PDF, DOCX, PNG, JPG, TIFF, or WEBP |
| 400 | MISSING_FIELD | Required field missing in JSON body |
| 400 | MISSING_QUERY | No search query provided (legacy endpoint) |
| 400 | BATCH_LIMIT_EXCEEDED | Too many items in batch request (max 10 CVs, 50 entities) |
| 401 | AUTH_REQUIRED | Missing or malformed Authorization header |
| 401 | INVALID_TOKEN | Token not found in database |
| 401 | TOKEN_REVOKED | Token has been deactivated |
| 401 | TOKEN_EXPIRED | Token has passed its expiry date |
| 403 | INSUFFICIENT_SCOPE | Token lacks the required scope for this endpoint |
| 403 | NO_API_ACCESS | Subscription plan does not include API access |
| 403 | NO_PLAN | No active subscription plan |
| 404 | NOT_FOUND | Requested profile or entity does not exist |
| 429 | LIMIT_REACHED | Monthly usage quota exceeded |
| 500 | ANALYSIS_FAILED | CV processing failed (corrupt file, too little text, etc.) |