Developer Guides

    PEP Screening API for PEP/RCA and Sanctions Checks

    One REST call screens PEP/RCA records, sanctions lists and criminal watchlists together. POST /api/v1/search with X-API-Key, poll, then read the pep field.

    Updated

    Use the shared screening endpoint to check PEP/RCA, sanctions and criminal-watchlist sources together. POST /api/v1/search with search_type sanctions_check (€0.39) screens the name against PEP/RCA records, sanctions lists and criminal watchlists in a single pass and returns the three domains in separate fields of the same response, so one integration answers all three questions and you are billed once. Nothing here is synchronous. HTTP 202 hands back a search_id and you poll GET /api/v1/search/{search_id} until status is completed. Authenticate with an X-API-Key header carrying the search:write permission; the key itself is minted in the dashboard, or by POST /api/v1/keys against your account bearer token. Two behaviours to build around. pep is null when nothing matched, where the sanctions fields always carry a formatted block, check the completed job status before interpreting an empty finding. And there is no score in the payload, no SDK and no webhook: a PEP finding arrives as markdown for a reviewer, because *Potential PEP match* is the beginning of a decision rather than the end of one. The PEP/RCA register covers politically exposed persons and their source-documented relatives and close associates (RCA), using public-source data distributed by OpenSanctions. An RCA relationship does not mean the person holds public office. Coverage is a dated snapshot, detailed below; the saved result records what the check found on that day.

    What this workflow covers

    • Keep keys server-side with only the scopes needed by the integration. On a processing timeout, create a manual-review task instead of silently approving onboarding.
    • For batch reconciliation, retain a row-level reference in your own case system alongside the returned result identifiers. Keep submitted identity data, returned sources, reviewer notes and final disposition together; the API result does not itself capture your organization's decision.
    • Schedule re-screening in your own system according to policy and material role or jurisdiction changes. A saved result records the earlier check; it does not monitor later changes.
    • A PEP/RCA finding supplies political-exposure and relationship context; a sanctions finding identifies a designation by a sanctions authority. Review these distinct signals against the same subject instead of treating every politically exposed person or associate as sanctioned.
    • The request body is entity_name (required, 1–500 characters), entity_type (person, company or any), search_type, and the optional country, identifier, website and language fields. Country is optional request context; it is not a Quick Check name-matching filter.
    • The 202 response carries search_id, status, message and estimated_time_seconds, which is 15 for sanctions_check and 30 for full_search. Both figures are constants in the code, not measured or guaranteed latency.
    • The request has no per-domain filter. Quick Check includes PEP/RCA, sanctions and criminal watchlists, returned in separate fields of the same response.
    • The pep response field covers PEP and RCA findings. Review the returned classification and source relationship; an RCA match does not mean that the person holds public office.
    • PEP findings arrive in the pep field of GET /api/v1/search/{search_id}, as markdown headed '## PEP Findings' with the status line *Potential PEP match* and, where the source record carries them, identification, datasets and dates lines plus a source label.
    • When nothing matches, pep is null rather than a no-match block, unlike the sanctions fields. Read null as no PEP/RCA record found in the checked public sources, not as a negative confirmation.
    • PEP attribution is normalised before it leaves the API: aggregator and vendor labels are stripped from the text, so the source line names the public register rather than a data reseller.
    • GET /api/v1/coverage returns a pep domain with available_tables, the table names and latest_updated_at, which is how you evidence what the PEP register held on a given screening date.
    • completed is the status you wait for. processing precedes it, queued precedes that for a deep_research_report, and failures surface as error_queue_create, error_credit_deduction, error_streaming, error_saving_results or error_no_final_payload.
    • Each key carries its tier's limits: free 10/minute and 100/day, standard 60 and 1,000, premium 300 and 10,000, enterprise 1,000 and 100,000. Every response returns X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a 429 adds Retry-After.
    • Every failure uses the same envelope of error, message and status_code. 401 for a missing or invalid X-API-Key, 403 for a key without search:write or search:read, 404 for an unknown search_id, 400 for an unsupported search_type, 429 for rate limits.
    • Send an Idempotency-Key with POST /api/v1/search and reuse it with the same payload for transport retries. An admitted request replays the original response without another search or charge. A different payload returns 409 idempotency_conflict. For 409 request_in_progress_or_uncertain, poll the supplied search_id; do not switch to a new key. Keys contain 16–128 ASCII letters, digits, underscores, dots, colons or hyphens. Without a key, another POST creates a new search. Batch uploads do not support this header.
    • Price per check: €0.39 for sanctions_check and €5.90 for full_search, which adds transliteration, alias expansion and an explainable review of each candidate with false-positive flags. Evidence PDFs are exported from screening history in the portal; /api/v1 has no PDF endpoint.

    Code samples

    Create the check and read your credit balancebash
    curl -X POST https://screenveritai.com/api/v1/search \
      -H "X-API-Key: svai_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
        "entity_name": "Test Person",
        "entity_type": "person",
        "search_type": "sanctions_check",
        "country": "PL"
      }'
    
    # HTTP/1.1 202 Accepted
    # {
    #   "search_id": "6e2b8d05-9f13-4a77-b8c4-0d51e7a3f962",
    #   "status": "processing",
    #   "message": "Search queued successfully. Poll the GET endpoint for results.",
    #   "estimated_time_seconds": 15
    # }
    
    # Allowance and prepaid balance before a run of checks:
    curl https://screenveritai.com/api/v1/account/credits \
      -H "X-API-Key: svai_your_key_here"
    # {
    #   "subscription_tier": "standard",
    #   "credits_used": 0.0,
    #   "credits_limit": null,
    #   "credits_remaining": null,
    #   "credit_pools": {
    #     "sanctions_checks": {"used": 12.0, "limit": 500, "remaining": 488.0},
    #     "full_search": {"used": 1.0, "limit": 25, "remaining": 24.0}
    #   },
    #   "prepaid_balance_cents": 4210,
    #   "prepaid_currency": "eur"
    # }
    GET /api/v1/search/{search_id} — no PEP record, then a potential PEP match. Fictitious examples, abridged.json
    // No PEP record found — pep is null, not a "no match" block.
    {
      "id": "6e2b8d05-9f13-4a77-b8c4-0d51e7a3f962",
      "entity_name": "Test Person",
      "entity_type": "person",
      "status": "completed",
      "search_type": "sanctions_check",
      "pep": null,
      "criminal_watchlists": null,
      "ofac_sanctions": "## OFAC Sanctions Findings\n\n**Status:** *No relevant listing found* ...",
      "created_at": "2026-09-04T11:04:26.501000+00:00",
      "completed_at": "2026-09-04T11:04:41.983000+00:00"
    }
    
    // Potential PEP match in the public source registry.
    {
      "id": "9a4c7f31-08e5-4bd2-9a60-33be1d6c8f57",
      "entity_name": "Second Test Person",
      "entity_type": "person",
      "status": "completed",
      "search_type": "sanctions_check",
      "pep": "## PEP Findings\n\n**Status:** *Potential PEP match*\n\n**Details:**\n- **Matched entity:** SECOND TEST PERSON\n- **Identification:** 1968-02-23 | PL\n- **Dates:** first seen 2018-05-02 | last seen 2026-08-29\n\n**Source:** *Public PEP source registry*",
      "criminal_watchlists": null,
      "created_at": "2026-09-04T11:12:09.114000+00:00",
      "completed_at": "2026-09-04T11:12:25.660000+00:00"
    }
    Poll and split the three domains with requestspython
    import os
    import time
    import requests
    
    BASE = "https://screenveritai.com/api/v1"
    HEADERS = {"X-API-Key": os.environ["SCREENVERITAI_API_KEY"]}
    
    SANCTIONS_FIELDS = (
        "ofac_sanctions", "eu_sanctions", "fr_sanctions", "pl_sanctions",
        "uk_sanctions", "un_sanctions", "canada_sanctions", "australia_sanctions",
        "switzerland_sanctions", "south_africa_sanctions", "new_zealand_sanctions",
    )
    
    
    def screen(entity_name, country=""):
        created = requests.post(
            f"{BASE}/search",
            headers=HEADERS,
            json={
                "entity_name": entity_name,
                "entity_type": "person",
                "search_type": "sanctions_check",
                "country": country,
            },
            timeout=30,
        )
        created.raise_for_status()
        search_id = created.json()["search_id"]
    
        while True:
            response = requests.get(f"{BASE}/search/{search_id}", headers=HEADERS, timeout=30)
            response.raise_for_status()
            result = response.json()
            if result["status"] == "completed":
                return result
            if result["status"].startswith("error"):
                raise RuntimeError(f"search {search_id} ended in {result['status']}")
            time.sleep(3)
    
    
    result = screen("Test Person", country="PL")
    
    # One call, three domains. pep and criminal_watchlists are null when empty;
    # the sanctions fields always carry a block, so check the status marker.
    pep_hit = bool(result["pep"])
    watchlist_hit = bool(result["criminal_watchlists"])
    sanctions_hit = any(
        "**Status:** *Sanctioned*" in (result.get(field) or "")
        for field in SANCTIONS_FIELDS
    )
    
    print(result["id"], {"pep": pep_hit, "watchlist": watchlist_hit, "sanctions": sanctions_hit})
    if pep_hit:
        print(result["pep"])   # markdown block for the reviewer, not a score
    Create a scoped API key (account bearer token, not X-API-Key)bash
    curl -X POST https://screenveritai.com/api/v1/keys \
      -H "Authorization: Bearer <ACCOUNT_BEARER_TOKEN>" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "onboarding-service",
        "permissions": ["search:read", "search:write"],
        "expires_in_days": 90
      }'
    
    # HTTP/1.1 201 Created
    # The api_key value is shown once in this response and never again.
    # {
    #   "id": "1f0a7c62-4d38-4c11-9e77-2b5a90fd6c48",
    #   "name": "onboarding-service",
    #   "key_prefix": "svai_9c41ab",
    #   "permissions": ["search:read", "search:write"],
    #   "rate_limit_tier": "standard",
    #   "rate_limit_per_minute": 60,
    #   "rate_limit_per_day": 1000,
    #   "is_active": true,
    #   "expires_at": "2026-12-03T11:20:44.312000+00:00",
    #   "api_key": "svai_9c41ab..."
    # }
    #
    # Permissions are search:read, search:write, batch:read, batch:write.
    # expires_in_days accepts 1-365, or null for a key that does not expire.
    # A maximum of 10 active keys per account.

    Compliance glossary

    search_id
    The UUID the 202 hands back. It addresses the result at GET /api/v1/search/{search_id} and identifies the stored snapshot that records the PEP/RCA findings returned on the screening date.
    Match
    A public-source PEP record that the name search returned. It appears in the pep field as a '## PEP Findings' section with the status line *Potential PEP match*, plus the identification, datasets and date lines the source record carries.
    Disposition
    The outcome a reviewer records against a candidate: confirmed as the same person, dismissed as a false positive, or escalated for enhanced due diligence. A PEP designation is a risk classification calling for review rather than a prohibition, so the disposition is where the actual decision lives.
    Point-in-time evidence
    The saved findings from a completed check at a particular time. Export and retain the evidence according to your policy; a later check produces a new result.

    Authoritative references

    Questions

    Frequently asked questions

    We are getting null in pep more often than expected. Error or no match?

    No match. The sanctions fields always return a formatted block, pep and criminal_watchlists return null, and that asymmetry catches most integrations exactly once. Write it into your UI as no PEP/RCA record found in the checked public sources, never as a negative confirmation. The register is drawn from public sources, and a person can hold office without appearing in one.

    Can I point staging at a free endpoint while I build?

    No. There is no sandbox, no free test key and no dry-run flag; every POST /api/v1/search bills at the published price. GET /api/v1/health is public for a reachability check, and GET /api/v1/account/credits shows the remaining allowance and prepaid balance. Capture one real completed response as a fixture on day one and run staging against that.

    What is a sane polling interval?

    Every 2–3 seconds. Faster than that just burns rate limit against a job that has not moved. The 202 returns estimated_time_seconds of 15 for sanctions_check and 30 for full_search, both fixed per-type constants in the code rather than measurements, so use them to size a deadline rather than to promise anyone a number. A check that has not reached completed goes to manual review; a timeout is not a pass.

    Can I split this into a PEP call and a sanctions call for two different teams?

    Not on our side, and there is no reason to pay twice for it. The request model has no per-domain or per-list filter, so a single €0.39 check returns pep, criminal_watchlists and the eleven per-jurisdiction sanctions fields in one payload. That is why one integration serves as both a PEP screening API and a sanctions API. Fan the payload out to your teams from your own store rather than issuing two searches for the same name.

    Two spellings of the same name, one hits and one does not. What am I doing wrong?

    Nothing, if you are on sanctions_check. It is the deterministic mode and matches the name as submitted. Transliteration, alias matching and query expansion run in Full Search (search_type: full_search, €5.90), which also writes readable reasoning for each candidate and flags likely false positives. Quick Check matches the submitted name; country is not a matching filter for this product.

    Which language SDKs do you support?

    None, and we would rather say so than ship one we do not maintain. There are no SDKs and no client libraries. It is plain REST with JSON bodies and an X-API-Key header, with interactive documentation at /api/v1/docs, reference docs at /api/v1/redoc and a machine-readable schema at /api/v1/openapi.json that a generator will turn into a client.

    Is PEP data billed separately from sanctions?

    No. It is inside the per-check price: €0.39 for sanctions_check and €5.90 for full_search, each covering PEP/RCA, sanctions and criminal watchlists in one call, with no PEP add-on and no platform fee. Monthly plans include a check allowance that the API spends before the prepaid balance, so a plan changes what a call draws down, not what it returns.

    Start screening in minutes.

    Choose a plan and start screening today.