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.

Base URL http://checker.3pap.africa/api/v1

Quick Start

  1. Get an API Key — Sign up, subscribe to a plan with API access, and generate a token from your Account page
  2. Set the Authorization headerAuthorization: Bearer 3pap_your_token
  3. Make your first call — Try the health check endpoint below
Quick Test (cURL)
curl http://checker.3pap.africa/api/v1/health

API Overview

CategoryEndpointsScope Required
CV Analysis6 endpoints — upload, batch, list, detail, report, deletecv_analysis
Sanctions Screening6 endpoints — screen, batch, entity, cross-ref, sources, statssanctions_search
Utility2 endpoints — health check, usage statsNone / Any

Authentication

All API requests (except /health) require a Bearer token in the Authorization header.

HTTP Header
Authorization: Bearer 3pap_your_api_token_here

Getting Your API Token

  1. Self-service: Go to your Account page and click "Generate API Key" (requires a plan with API access)
  2. Admin-issued: Your organization's admin can generate tokens from the admin panel with specific scopes
Security: Tokens are shown only once when created. Store them securely in environment variables or a secrets manager. Never expose tokens in client-side code, Git repositories, or logs.

Token Scopes

ScopeGrants Access To
cv_analysisAll CV Analysis endpoints (upload, batch, profiles, reports, delete)
sanctions_searchAll Sanctions Screening endpoints (screen, batch, entities, cross-ref, sources, stats)
reputation_checkReputation 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.

MethodEndpointDescription
POST/cv/analyzeUpload and analyze a single CV
POST/cv/batchUpload and analyze multiple CVs (up to 10)
GET/cv/profilesList analyzed profiles
GET/cv/profiles/:idGet full profile with career analysis
GET/cv/profiles/:id/reportComprehensive report (career + sanctions + reputation)
DELETE/cv/profiles/:idDelete a profile
POST /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

FieldTypeRequiredDescription
cv_fileFileYesPDF or DOCX file (max 10MB)
linkedin_urlStringNoCandidate's LinkedIn profile URL
departmentStringNoDepartment name (for your internal tagging)
positionStringNoPosition applied for
reference_idStringNoYour internal reference ID (e.g. applicant ID in your HR system)
auto_sanctions_checkStringNoSet to "true" to automatically screen the candidate's companies against sanctions databases

Response 201 Created

JSON Response
{
  "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
  }
}
POST /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

FieldTypeRequiredDescription
cv_filesFile[]YesOne or more PDF/DOCX files (max 10)
auto_sanctions_checkStringNoSet to "true" to auto-screen each CV

Response 201 Created

JSON Response
{
  "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"
    }
  ]
}
GET /api/v1/cv/profiles

List all analyzed CV profiles with filtering, search, and pagination.

Query Parameters

ParameterTypeDefaultDescription
pageInteger1Page number
per_pageInteger20Results per page (max 100)
searchStringFilter by name, email, or location
sanctionedString"true" or "false" to filter by sanctions flag
sortStringnewest"newest", "oldest", or "name"

Response 200

JSON Response
{
  "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"
    }
  ]
}
GET /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

ParameterTypeDescription
idIntegerProfile ID

Response 200

JSON Response (abbreviated)
{
  "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"
      }
    }
  }
}
GET /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

JSON Response (key fields)
{
  "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
    }
  }
}
DELETE /api/v1/cv/profiles/:id

Permanently delete a profile and all associated data (skills, experiences, educations, certifications, predictions, sanctions matches).

Response 200

JSON Response
{
  "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.

MethodEndpointDescription
POST/sanctions/screenScreen a single entity
POST/sanctions/batchBatch screen multiple entities (up to 50)
GET/sanctions/entity/:idFull entity detail
POST/sanctions/cross-reference/:profile_idCross-reference CV profile
GET/sanctions/sourcesList all data sources
GET/sanctions/statsDatabase statistics
POST /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)

FieldTypeRequiredDescription
nameStringYesEntity name to screen (company or person)
countryStringNoFilter results by country
datasetStringNoFilter by data source (e.g. "World Bank")
typeStringNo"person" or "company" to filter entity type
max_resultsIntegerNoMax results to return (default 20, max 100)

Response 200

JSON Response
{
  "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

LevelMeaning
criticalActive sanction with exact match (score ≥ 90%)
highActive sanction with fuzzy match (score ≥ 78%)
mediumActive sanction with partial match
lowOnly expired or weak matches
clearNo matches found
POST /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)

FieldTypeRequiredDescription
entitiesArrayYesArray of entities to screen (max 50)
entities[].nameStringYesEntity name
entities[].countryStringNoFilter by country
Request Example
{
  "entities": [
    {"name": "Acme Corporation", "country": "Nigeria"},
    {"name": "Global Ventures Ltd"},
    {"name": "John Smith", "country": "United Kingdom"}
  ]
}

Response 200

JSON Response
{
  "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": []
    }
  ]
}
GET /api/v1/sanctions/entity/:id

Get full details of a sanctions entity including all aliases, linked CV profiles, and source information.

Response 200

JSON Response
{
  "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"
    }
  }
}
POST /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

JSON Response
{
  "success": true,
  "profile_id": 42,
  "profile_name": "John Doe",
  "is_sanctioned": false,
  "companies_checked": 3,
  "total_matches": 0,
  "matches": []
}
GET /api/v1/sanctions/sources

List all available sanctions data sources with entity counts. Currently covering 114 sources across 8 regions.

Response 200

JSON Response (abbreviated)
{
  "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},
    ...
  ]
}
GET /api/v1/sanctions/stats

Get database-wide sanctions statistics including entity counts, active/expired breakdown, top countries, and last sync time.

Response 200

JSON Response
{
  "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

GET /api/v1/health No Auth

Health check endpoint. No authentication required. Use this to verify API availability and check service status.

Response
{
  "status": "ok",
  "version": "v1",
  "services": {
    "database": "connected",
    "sanctions_sources": 114,
    "sanctions_entities": 25000,
    "cv_profiles": 156
  }
}
GET /api/v1/usage

Check your current API usage and remaining monthly quota.

Response
{
  "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)

Full Integration Example
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)

Full Integration Example
<?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

Full Integration Example
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)

Full Integration Example
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+)

Full Integration Example
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)

React Integration Components
// ─── 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)

Full Integration Example
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)

Full Integration Example
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:

1
Candidate Applies Candidate uploads CV through your HR portal
2
Forward to 3pap API POST /cv/analyze with the CV file + auto_sanctions_check=true
3
Receive Analysis API returns parsed profile data, role predictions, and sanctions screening results
4
Display in Your System Show extracted data, skills match, and risk flags in your HR dashboard
5
Get Detailed Report GET /cv/profiles/:id/report for career analysis, tenure trends, and flight risk assessment
Key Points:
  • A copy of every CV is stored on 3pap for audit and compliance
  • Use reference_id to link profiles to your internal applicant IDs
  • Use auto_sanctions_check=true to automatically screen candidates
  • The /cv/profiles/:id/report endpoint 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.

1
New Vendor Application Vendor submits registration or contract renewal in your procurement system
2
Screen via API POST /sanctions/screen with vendor name and country
3
Check Risk Level API returns risk_level: "clear", "low", "medium", "high", or "critical"
4
Decision Logic "clear"/"low" = auto-approve | "medium" = manual review | "high"/"critical" = block
Batch Screening: Use 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.

ActionTriggered ByLimit Source
CV AnalysisPOST /cv/analyze, POST /cv/batch (per file)Plan's cv_analyses_per_month
Sanctions SearchPOST /sanctions/screen, POST /sanctions/batch (per entity), POST /sanctions/cross-reference, GET /sanctions/searchPlan's sanctions_searches_per_month
Reputation CheckComing soonPlan's reputation_checks_per_month
429 Too Many Requests: When you exceed your limit, the API returns a 429 status with usage details. Check your quota anytime with GET /usage.
429 Response Example
{
  "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 Response Format
{
  "error": "Human-readable error message",
  "code": "ERROR_CODE"
}

Error Codes Reference

HTTPCodeDescription
400MISSING_FILENo CV file provided in upload request
400INVALID_FILEFile is not a valid PDF or DOCX
400MISSING_FIELDRequired field missing in JSON body
400MISSING_QUERYNo search query provided (legacy endpoint)
400BATCH_LIMIT_EXCEEDEDToo many items in batch request (max 10 CVs, 50 entities)
401AUTH_REQUIREDMissing or malformed Authorization header
401INVALID_TOKENToken not found in database
401TOKEN_REVOKEDToken has been deactivated
401TOKEN_EXPIREDToken has passed its expiry date
403INSUFFICIENT_SCOPEToken lacks the required scope for this endpoint
403NO_API_ACCESSSubscription plan does not include API access
403NO_PLANNo active subscription plan
404NOT_FOUNDRequested profile or entity does not exist
429LIMIT_REACHEDMonthly usage quota exceeded
500ANALYSIS_FAILEDCV processing failed (corrupt file, too little text, etc.)