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 | 6 endpoints — upload, batch, list, detail, report, delete | cv_analysis |
| Sanctions Screening | 6 endpoints — screen, batch, entity, cross-ref, sources, stats | sanctions_search |
| Utility | 2 endpoints — health check, 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 | All CV Analysis endpoints (upload, batch, profiles, reports, delete) |
sanctions_search | All Sanctions Screening endpoints (screen, batch, entities, cross-ref, sources, stats) |
reputation_check | Reputation intelligence endpoints (coming soon) |
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/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 or DOCX 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 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 (tenure, seniority progression, flight risk, health score, etc.).
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"
}
}
}
}
/api/v1/cv/profiles/:id/report
Get a comprehensive JSON report combining profile data, career analysis, 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": { ... },
"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
}
}
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"
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 |
| Reputation Check | Coming soon | 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 or DOCX |
| 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.) |