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 Analysis11 endpoints — upload, batch, list, detail, career recommendation, skills gap, CV improvement, career coach, opportunity matches, report, deletecv_analysis
Opportunity Matching6 endpoints — list, detail, apply, application tracking, candidate ranking, profile matchescv_analysis
Talent Marketplace7 endpoints — search talent, detail, shortlist, invitations, and contact loggingcv_analysis
Consultant Database5 endpoints — search experts, create profiles, add project experience, and manage availabilitycv_analysis
Sanctions Screening6 endpoints — screen, batch, entity, cross-ref, sources, statssanctions_search
PEP Screening7 endpoints — single-name check, batch/file checks, screening history, reports, sources, and monitored namespep_check or sanctions_search
Vendor RiskVendor profile, screening, risk refresh, and risk score endpointsvendor_risk
WebhooksCreate endpoints, test deliveries, and inspect webhook logswebhooks
API BillingPlan limits, billable usage, token requests, and active API accessusage_billing
UtilityHealth check and 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_analysisCV Analysis and Opportunity Matching endpoints (upload, profiles, reports, matching, applications)
sanctions_searchAll Sanctions Screening endpoints (screen, batch, entities, cross-ref, sources, stats)
pep_checkPEP screening endpoints (single check, batch checks, screening history, PEP reports, source transparency, and monitoring list)
vendor_riskVendor registration, vendor screening aliases, and risk score endpoints
talent_searchTalent marketplace search, candidate detail, shortlists, invitations, and contact logging
webhooksWebhook endpoint management, delivery testing, and delivery history
usage_billingSubscription, API billing, usage limits, and token request metrics
reputation_checkReputation 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.

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/career-recommendationGet career paths, gaps, and next steps
GET/cv/profiles/:id/skills-gapCompare skills against a target role
GET/cv/profiles/:id/cv-improvementScore CV quality and generate improvement guidance
GET / POST/cv/profiles/:id/career-coachAsk career questions and get coaching guidance
GET/cv/profiles/:id/opportunity-matchesRank open jobs, internships, consultancies, and projects for one profile
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, DOCX, PNG, JPG, TIFF, or WEBP 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, PNG, JPG, TIFF, or WEBP 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 and career recommendation data.

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"
      }
    },
    "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"}
      }
    ]
  }
}
GET /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

JSON Response (key fields)
{
  "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": [...]
  }
}
GET /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

ParameterTypeDefaultDescription
target_roleStringTop predicted roleRole to compare against, e.g. "Data Scientist"

Response 200

JSON Response (key fields)
{
  "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": [...]
  }
}
GET /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

ParameterTypeDefaultDescription
target_roleStringTop predicted roleRole used for keyword optimization and template recommendations

Response 200

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

ParameterTypeDefaultDescription
questionStringUser's career question, such as "How should I prepare for interviews?"
target_roleStringTop predicted roleTarget role used for coaching context
focusStringgeneralgeneral, career_options, learning_path, interview, job_titles, industries, salary, cover_letter, linkedin, or monthly_review

Response 200

JSON Response (key fields)
{
  "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.

MethodEndpointDescription
GET/opportunitiesList open opportunities with filters and optional profile match
GET/opportunities/:idGet one opportunity with optional profile match
POST/opportunities/:id/applySubmit an application using a stored CV profile
GET/opportunity-applicationsTrack submitted applications and interview invitations
GET/opportunities/:id/candidatesAdmin-only candidate ranking for an opportunity
GET/cv/profiles/:id/opportunity-matchesRank opportunities for one profile

List Opportunities

cURL
curl -H "Authorization: Bearer 3pap_your_token" \
  "http://checker.3pap.africa/api/v1/opportunities?profile_id=42&type=job&work_mode=remote"

Application Request

JSON Body
{
  "profile_id": 42,
  "cover_letter": "I am interested in this role because..."
}

Response 200

Opportunity Match (key fields)
{
  "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.

MethodEndpointDescription
GET/talentSearch and AI-rank talent by skill, role, country, education, experience, availability, and verification
GET/talent/:profile_idGet one candidate profile with match explanation and marketplace metadata
GET/talent/shortlistList your shortlisted candidates
POST/talent/:profile_id/shortlistAdd, restore, or archive a candidate shortlist record
GET/talent/invitationsList your talent invitations
POST/talent/:profile_id/inviteRecord a candidate invitation, optionally linked to an opportunity
POST/talent/:profile_id/contactLog email, phone, LinkedIn, or other contact activity

Search Talent

cURL
curl -H "Authorization: Bearer 3pap_your_token" \
  "http://checker.3pap.africa/api/v1/talent?skill=Python,SQL&country=Ghana&min_score=65"

Shortlist Request

JSON Body
{
  "action": "shortlist",
  "notes": "Strong fit for donor reporting analyst role"
}

Response 200

Talent Match (key fields)
{
  "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.

MethodEndpointDescription
GET/consultantsSearch and AI-rank consultants by category, expertise, country, donor, rate, availability, and verification
POST/consultantsCreate a consultant profile
GET/consultants/:idGet one consultant profile with ranking details
POST/consultants/:id/projectsAdd donor, client, country, and project experience
POST/consultants/:id/availabilityAdd availability calendar slots

Search Consultants

cURL
curl -H "Authorization: Bearer 3pap_your_token" \
  "http://checker.3pap.africa/api/v1/consultants?category=procurement_specialist&country=Ghana&donor=World%20Bank"

Create Consultant

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

JSON Response (key fields)
{
  "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
    }
  }
}
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
  }
}

PEP Screening API

Run PEPChecker-style checks for politically exposed persons, related people, sanctions exposure, saved history, reports, and monitoring.

MethodEndpointDescription
POST/pep/checkCheck one name with name or firstName/lastName, DOB range, related people, and optional sanctions
GET/checkCompatibility alias for simple firstName/lastName PEP and sanctions checks
POST/pep/batchCheck up to 50 names from JSON or a CSV/text file upload
GET/pep/screeningsList saved PEP screening history
GET/pep/screenings/:id/report?format=pdfDownload HTML or PDF audit reports
GET/pep/sourcesView PEP and sanctions source coverage
GET/pep/monitoringList monitored PEP names
POST /api/v1/pep/check

Authenticate with Authorization: Bearer or an api-key header.

Request
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
  }'
POST /api/v1/pep/batch

Submit JSON names or upload a CSV/text file with columns such as name,country,subject_type.

GET /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.

MethodEndpointDescription
GET/reputation/profiles/:idRead score, trend, result breakdown, recent results, and recent scans
POST/reputation/profiles/:id/checkRun a manual reputation check for an accessible profile
GET/reputation/profiles/:id/scansList scan history with optional result details
Run Reputation Check
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.

MethodEndpointDescription
GET/vendorsList vendors with search, status, country, category, and risk filters
POST/vendorsCreate a vendor company profile
GET/vendor-risk/:idRead a vendor profile and current risk fields
POST/vendor-risk/:id/refreshRun sanctions and procurement screening for one vendor
GET/risk-scoresList trust and risk scores for vendors, consultants, people, and companies
Refresh Vendor Risk
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.

MethodEndpointDescription
GET/webhooks/eventsList supported webhook events
GET/webhooksList webhook endpoints
POST/webhooksCreate a webhook endpoint
POST/webhooks/:id/testSend a signed test delivery
GET/webhooks/deliveriesInspect delivery status, attempts, and response codes
Create Webhook
{
  "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.

GET /api/v1/billing

Returns the current plan, billable usage counters, active tokens, and API request totals. Requires usage_billing scope.

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

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,.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)

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
Vendor ScreeningPOST /vendors/:id/screen, POST /vendor-risk/:id/refreshPlan's vendor_screening_limit
Report DownloadReport endpoints across CV, compliance, risk, verification, and intelligence modulesPlan's report_download_limit
Reputation CheckPOST /reputation/profiles/:id/checkPlan'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, DOCX, PNG, JPG, TIFF, or WEBP
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.)