What this covers
Establishing what a company is, who owns it, and what it has told regulators. The pattern that matters: the registry tells you the legal structure, and the legal structure is often designed to obscure who benefits. Getting to the human at the end takes several sources in a particular order.
Method
- Start at the national registry, which is authoritative. Aggregators are convenient but stale, and for anything you will publish the registry page is the citation.
- Get the registration number and use it thereafter. Company names are reused across jurisdictions, reused within them after dissolution, and changed deliberately.
- Pull the filings, not the summary. Annual accounts, director appointments and charges carry addresses, signatures, auditors and related parties that the summary page omits. The scanned PDF often has a signature and a handwritten date the structured data does not.
- Follow officers sideways. One director’s other appointments frequently reveal the group the company actually belongs to — and distinguish a decision-maker from a nominee, because a nominee has dozens or hundreds.
- Screen the names you have against sanctions and politically-exposed-person data before you go further. A hit changes what the rest of the chain means, and it is cheap to check.
- Cross-border means repeating the whole process in each jurisdiction, and each one publishes something different: the UK publishes officers and PSCs free, Delaware publishes almost nothing, the EU publishes through BRIS with per-country gaps, and secrecy jurisdictions publish the existence of the company and nothing else. A chain terminating in one of them is the normal outcome, not a failure.
- Check leak archives where the chain goes dark. OCCRP Aleph and ICIJ Offshore Leaks hold what registries do not, with the caveat that leaked data is a snapshot of one moment.
- Write down what each jurisdiction refused to tell you. “Beneficial owner not published in this jurisdiction” is a finding about the structure, and it is the sentence that makes the analysis defensible rather than incomplete.
Core sources
| Source | Coverage |
|---|---|
| OpenCorporates | The largest open company database, ~200 jurisdictions. Best starting point for “does this company exist and where”. |
| Companies House | UK and Gibraltar. Free, complete filing history including scanned documents, and a clean API. The best-documented major registry. |
| SEC EDGAR | All US public-company filings. Full-text searchable back to 2001 and genuinely free. |
| OCCRP Aleph | Registries, leaks and court records in one index, with a shared entity model. The tool for cross-border work. |
| OpenSanctions | Consolidated sanctions lists, PEP data and persons of interest, as bulk data and an API. |
| ICIJ Offshore Leaks | Panama / Paradise / Pandora Papers entities, officers and intermediaries. |
| Open Ownership | Beneficial-ownership data, where it is published at all. |
| BRIS | Consolidated search across most EU, Iceland, Liechtenstein and Norway registers. |
Key tools
Companies House API
The reference implementation of what a company registry ought to be: free, keyed, documented, and complete back to incorporation including the scanned paper filings. Use it as the model for how much you should expect elsewhere, and be disappointed accordingly.
Register at developer.company-information.service.gov.uk for a free key. Authentication is HTTP basic with the key as the username and an empty password — note the trailing colon.
export CH_KEY='your-key-here'
# find the company and get its number, which is what everything else keys on
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/search/companies?q=example+trading+ltd' \
| jq '.items[] | {title, company_number, company_status, address_snippet}'
# the profile: incorporation date, SIC codes, registered office, accounts due
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/company/12345678' \
| jq '{company_name, date_of_creation, company_status, sic_codes, registered_office_address}'
# officers, including resignation dates — the sideways pivot starts here
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/company/12345678/officers' \
| jq '.items[] | {name, officer_role, appointed_on, resigned_on, links}'
# persons with significant control: the UK's beneficial-ownership disclosure
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/company/12345678/persons-with-significant-control' \
| jq '.items[] | {name, kind, natures_of_control, notified_on, ceased_on}'
# every other appointment this officer holds — nominee detection in one call
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/officers/OFFICER_ID/appointments' \
| jq '{total:.total_results, items:[.items[] | .appointed_to.company_name]}'
# charges and mortgages, which name lenders the company never advertised
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/company/12345678/charges' \
| jq '.items[] | {created_on, status, persons_entitled}'
# the filing history, then fetch the scanned document itself
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/company/12345678/filing-history' \
| jq '.items[] | {date, type, description, doc:.links.document_metadata}'
# search officers by name across all UK companies
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/search/officers?q=jane+smith' \
| jq '.items[] | {title, appointment_count, date_of_birth}'
Rate limit is 600 requests per five minutes per key; exceed it and you get HTTP 429 with a
Retry-After. The document-download host is document-api.company-information.service.gov.uk, not
the API host, and it needs the same basic auth plus an Accept: application/pdf header.
What it does not tell you: a PSC statement is self-declared and unverified, the 25% threshold means
real control can sit legally just underneath disclosure, and date_of_birth is published as month
and year only. “PSC: none” sometimes means genuinely dispersed ownership and sometimes means nobody
filed.
OpenCorporates
The aggregator that normalises ~200 jurisdictions into one schema, which is what makes it the right first call when you do not yet know where a company is registered. Its value is breadth and cross-jurisdiction name matching; its weakness is freshness, so confirm anything important against the registry it came from.
The API is key-gated and mostly paid. Free keys exist for open-data projects that republish under a share-alike licence, and journalists, academics and NGOs can apply for free or discounted access, but the free allowance is small — of the order of 50 requests a day. Commercial plans start in the low thousands of pounds a year. Without a key the API returns HTTP 401; the website search remains free.
export OC_TOKEN='your-api-token'
# name search across every jurisdiction OpenCorporates holds
curl -s -G 'https://api.opencorporates.com/v0.4/companies/search' \
--data-urlencode 'q=example trading' --data-urlencode "api_token=$OC_TOKEN" \
| jq '.results.companies[].company | {name, jurisdiction_code, company_number, current_status}'
# narrow to one jurisdiction once you know it
curl -s -G 'https://api.opencorporates.com/v0.4/companies/search' \
--data-urlencode 'q=example trading' --data-urlencode 'jurisdiction_code=gb' \
--data-urlencode "api_token=$OC_TOKEN" | jq '.results.total_count'
# the canonical record, including the source URL you should cite instead
curl -s -G 'https://api.opencorporates.com/v0.4/companies/gb/12345678' \
--data-urlencode "api_token=$OC_TOKEN" \
| jq '.results.company | {name, incorporation_date, registered_address_in_full, source}'
# officers attached to that company
curl -s -G 'https://api.opencorporates.com/v0.4/companies/gb/12345678' \
--data-urlencode "api_token=$OC_TOKEN" \
| jq '.results.company.officers[].officer | {name, position, start_date, end_date}'
# search officers by name globally — the cross-border nominee sweep
curl -s -G 'https://api.opencorporates.com/v0.4/officers/search' \
--data-urlencode 'q=jane smith' --data-urlencode "api_token=$OC_TOKEN" \
| jq '.results.officers[].officer | {name, jurisdiction_code, company:.company.name}'
# every company at one address, which is how you find a formation agent
curl -s -G 'https://api.opencorporates.com/v0.4/companies/search' \
--data-urlencode 'q=' --data-urlencode 'registered_address=*Tortola*' \
--data-urlencode "api_token=$OC_TOKEN" | jq '.results.total_count'
# check your remaining allowance before a loop burns the day's quota
curl -s -G 'https://api.opencorporates.com/v0.4/account_status' \
--data-urlencode "api_token=$OC_TOKEN" | jq '.results.account_status.usage'
Every company record carries a source object with the registry URL and the date OpenCorporates
scraped it. That date is the one thing you must read: a record last refreshed two years ago will
show a director who resigned eighteen months back as current.
SEC EDGAR full-text search
Every US filing since 2001, searchable by phrase. This is the one free full-text corporate archive of real size, and it is the fastest way to find a private person or entity named in someone else’s filing — a subsidiary list, a related-party note, a beneficial-ownership schedule.
There is no API key. The identification mechanism is the User-Agent header, which must name your
application and a contact address; send a generic one and you get HTTP 403 and a short IP block.
The limit is ten requests per second across all sec.gov hosts.
UA='DAEMON-SEC research you@example.com'
# phrase search across all filings — quotes force an exact phrase
curl -s -A "$UA" -G 'https://efts.sec.gov/LATEST/search-index' \
--data-urlencode 'q="Example Trading Limited"' \
| jq '{total:.hits.total.value, hits:[.hits.hits[] | {name:._source.display_names, date:._source.file_date, id:._id}]}'
# restrict to a form type: beneficial-ownership schedules
curl -s -A "$UA" -G 'https://efts.sec.gov/LATEST/search-index' \
--data-urlencode 'q="Example Trading"' --data-urlencode 'forms=SC 13D' \
| jq '.hits.total.value'
# restrict to a date window — dateRange=custom is required alongside the dates
curl -s -A "$UA" -G 'https://efts.sec.gov/LATEST/search-index' \
--data-urlencode 'q="going concern"' --data-urlencode 'forms=10-K' \
--data-urlencode 'dateRange=custom' \
--data-urlencode 'startdt=2025-01-01' --data-urlencode 'enddt=2025-06-30' \
| jq '.hits.total.value'
# everything one filer said about a term, by CIK
curl -s -A "$UA" -G 'https://efts.sec.gov/LATEST/search-index' \
--data-urlencode 'q="related party"' --data-urlencode 'entityName=0000320193' \
| jq '.hits.total.value'
# page past the first ten results (the corpus caps at 10,000 total)
curl -s -A "$UA" -G 'https://efts.sec.gov/LATEST/search-index' \
--data-urlencode 'q="Example Trading"' --data-urlencode 'from=10' \
| jq '[.hits.hits[]._id]'
# a filer's complete submission index, from the structured-data host
curl -s -A "$UA" 'https://data.sec.gov/submissions/CIK0000320193.json' \
| jq '{name, cik, formerNames, tickers, recent:[.filings.recent.form[0:5]]}'
# resolve a ticker to a CIK, which every other endpoint wants zero-padded
curl -s -A "$UA" 'https://www.sec.gov/files/company_tickers.json' \
| jq -r 'to_entries[] | select(.value.ticker=="AAPL") | .value.cik_str'
# one XBRL concept across a company's whole history
curl -s -A "$UA" \
'https://data.sec.gov/api/xbrl/companyconcept/CIK0000320193/us-gaap/Revenues.json' \
| jq '.units["USD"] | length'
The _id in a hit is accession-number:filename, which reconstructs to a document URL under
https://www.sec.gov/Archives/edgar/data/<cik>/<accession-no-dashes>/<filename>. The result set is
capped at 10,000 and paged ten at a time, so a broad query needs narrowing by form and date rather
than paging.
Full-text coverage starts in 2001. Anything older is in EDGAR but only findable by filer and form, not by phrase — which matters when the structure you are chasing was set up in the 1990s.
edgartools
A Python layer over EDGAR that parses filings into typed objects rather than handing you HTML, and
handles the User-Agent requirement and rate limiting for you. Use it the moment you need more
than a handful of filings, and particularly for Form 4 insider transactions and 13F holdings, where
the raw XML is tedious and the parsing is where the mistakes happen.
pip install edgartools
from edgar import Company, set_identity, get_filings
set_identity("you@example.com") # required; this becomes the User-Agent
c = Company("AAPL")
# the filing index for one form type
c.get_filings(form="10-K").head(5)
# insider transactions as structured objects, not XML
c.get_filings(form="4").latest().obj()
# institutional holdings from a 13F, already parsed into positions
get_filings(form="13F-HR").latest().obj().holdings
# subsidiary and related-party text out of the latest annual report
tenk = c.get_filings(form="10-K").latest().obj()
print(tenk["Item 1"][:2000])
# one XBRL concept as a DataFrame, for a time series
c.get_facts().query().by_concept("Revenues").to_dataframe()
# filings across all companies in a quarter, to sweep a form type
get_filings(year=2025, quarter=1, form="SC 13D")
set_identity is not optional — without it the library refuses to call EDGAR, which is the correct
behaviour. For bulk retrieval of raw documents rather than parsed objects,
sec-edgar-downloader is the simpler tool: Downloader("YourOrg", "you@example.com") then
dl.get("10-K", "AAPL", after="2020-01-01", limit=5).
OpenSanctions
Consolidated sanctions designations, politically-exposed-person lists and persons of interest from several hundred sources, normalised into one entity model. Run every name you extract from a registry through it, because a designation changes the legal meaning of the whole structure.
The API is metered pay-as-you-go, with free keys available on request for journalism, civil-society and academic work. The full dataset is also published for download under a share-alike licence, and the matching service is open source as yente, which you can self-host with no key at all — the right choice when the names you are screening should not leave your machine.
export OS_API_KEY='your-key'
# free-text search across the default collection
curl -s -H "Authorization: ApiKey $OS_API_KEY" -G \
'https://api.opensanctions.org/search/default' --data-urlencode 'q=Ilham Aliyev' \
| jq '.results[] | {id, caption, schema, datasets, topics}'
# restrict to companies rather than people
curl -s -H "Authorization: ApiKey $OS_API_KEY" -G \
'https://api.opensanctions.org/search/default' \
--data-urlencode 'q=Example Trading' --data-urlencode 'schema=Company' \
| jq '.total.value'
# structured matching, which scores candidates instead of string-matching
curl -s -H "Authorization: ApiKey $OS_API_KEY" -X POST \
-H 'Content-Type: application/json' \
'https://api.opensanctions.org/match/default' \
-d '{"queries":{"q1":{"schema":"Person","properties":{"name":["Jane Smith"],"birthDate":["1970"],"nationality":["gb"]}}}}' \
| jq '.responses.q1.results[] | {score, caption, topics}'
# the full entity, including every alias, address and linked entity
curl -s -H "Authorization: ApiKey $OS_API_KEY" \
'https://api.opensanctions.org/entities/NK-ENTITY-ID' | jq '.properties'
# what datasets a hit came from, which is how you cite the designation
curl -s -H "Authorization: ApiKey $OS_API_KEY" \
'https://api.opensanctions.org/datasets' | jq '.datasets[] | {name, title, entity_count}'
# self-hosted yente takes the same paths with no key
curl -s -G 'http://localhost:8000/search/default' --data-urlencode 'q=Jane Smith' | jq '.total'
A match is a match on data, not an identification. Common names produce confident-looking hits on unrelated people, and the sanctions lists themselves contain transliteration variants of one person as separate entries. Always open the entity, read the aliases and birth date, and cite the underlying designation — the OFAC or EU legal act — not the aggregator.
OCCRP Aleph
Registries, leaks, court records, procurement data and gazettes in one index, all mapped onto the
FollowTheMoney entity schema. That shared schema is the point: a Company from a Latvian registry
and a Company from a leaked corporate database have the same property names, so you can ask one
query across both.
API keys come with an Aleph account; some collections need separate permission and investigative
collections are not public. The header format is Authorization: ApiKey <key>, and all paths sit
under /api/2/.
export ALEPH_KEY='your-api-key'
H="Authorization: ApiKey $ALEPH_KEY"
# free-text search across every collection you can see
curl -s -H "$H" -G 'https://aleph.occrp.org/api/2/entities' \
--data-urlencode 'q=Example Trading Limited' \
| jq '.results[] | {id, schema, caption, collection:.collection.label}'
# restrict by entity type — the schema names are the FollowTheMoney ones
curl -s -H "$H" -G 'https://aleph.occrp.org/api/2/entities' \
--data-urlencode 'q=Jane Smith' --data-urlencode 'filter:schema=Person' \
| jq '.total'
# restrict to one collection, once you know which dataset matters
curl -s -H "$H" -G 'https://aleph.occrp.org/api/2/entities' \
--data-urlencode 'q=Example Trading' --data-urlencode 'filter:collection_id=123' \
| jq '.total'
# one entity in full, with every property the schema allows
curl -s -H "$H" 'https://aleph.occrp.org/api/2/entities/ENTITY_ID' | jq '.properties'
# what this entity is connected to and how — ownership, directorship, payment
curl -s -H "$H" -G 'https://aleph.occrp.org/api/2/entities/ENTITY_ID/expand' \
--data-urlencode 'limit=50' \
| jq '.results[] | {property, count, entities:[.entities[].caption]}'
# the entity-model definitions, so you know which properties exist to filter on
curl -s 'https://aleph.occrp.org/api/2/metadata' \
| jq '.schemata | keys | map(select(. | test("Company|Ownership|Person|Directorship")))'
# list the collections your key can reach, with their ids and update dates
curl -s -H "$H" -G 'https://aleph.occrp.org/api/2/collections' --data-urlencode 'limit=50' \
| jq '.results[] | {id, label, category, updated_at}'
The schema is what makes Aleph worth the learning curve. Company and Person are the nodes;
Ownership, Directorship, Membership and Payment are intermediate entities with their own
properties — an Ownership carries a percentage, a start date and an end date, and connects an
owner to an asset. /expand walks those edges, which is how you traverse a chain without reading
every document.
Leaked collections are snapshots. An Ownership from a 2016 leak describes 2016, and saying so is
the difference between a finding and a libel risk.
ICIJ Offshore Leaks
Entities, officers, intermediaries and addresses from the Panama, Paradise, Pandora, Bahamas and Offshore Leaks investigations. Web-only, no API, and worth a walkthrough because the structure of the database is not obvious from the search box.
Search at offshoreleaks.icij.org by company name, person name or address. Read the result in this order.
Entity the offshore company itself: jurisdiction, incorporation and inactivation
dates, and the leak it came from. The jurisdiction plus the dates is what
you take back to that jurisdiction's registry
Officers the people and companies attached, each with a role (shareholder, director,
beneficiary, nominee). "Nominee" here is explicit, which is rarer than it
sounds
Intermediary the law firm or agent that formed it. This is the strongest pivot in the
database — the intermediary's other clients are listed, and a pattern of
one agent forming a cluster of companies is a structure
Addresses search by address, not name, when a name is too common. A residential
address shared by forty companies is the finding
Linked the graph view, which shows the chain between two entities if one exists
Capture the node URL for every entity and officer, the leak name, and the date range the data covers. The URLs are stable and citable.
Two limits to state every time you use it: being in the database is not evidence of wrongdoing — offshore structures are legal and ICIJ says so prominently — and the data is as of the leak, which for Panama means 2015. A company shown as active was active then.
Jurisdiction-by-jurisdiction fallbacks
When the chain crosses a border, what you can get changes completely. These are the sources worth knowing before you conclude a jurisdiction publishes nothing.
| Source | What it adds |
|---|---|
| BRIS / e-Justice | One search box over most EU registers. Coverage per country varies from full filings to name-and-number only. |
| North Data | EU registers with relationship graphs and German trade-register notices, which often name shareholders the registry does not. |
| Open Ownership Register | Beneficial-ownership declarations consolidated across the countries that publish them. |
| RuPEP | Politically exposed persons in Russia, Belarus, Kazakhstan, Kyrgyzstan, Georgia and Moldova, with family and associate links. |
| SanctionsExplorer | Historical OFAC, UN and EU designations, including delisted ones — which is what you need for a structure that predates a current list. |
| ImportYeti | US customs sea-shipment records: who ships to whom, which evidences a trading relationship no registry records. |
| Wikipedia’s register list | The maintained index of official registers worldwide. Start here for a jurisdiction you have not worked before. |
Reading the structure
- Nominee directors appear on dozens or hundreds of companies. A director with 200 appointments is a service provider, not a decision-maker, and the Companies House appointments endpoint tells you which you have in one call.
- Registered-address clustering usually means a formation agent. Search the address, not the name, and the agent’s whole book appears.
- Shareholders that are themselves companies are the chain you have to walk. Keep going until you hit a natural person or a jurisdiction that will not tell you, then record which.
- Charges and mortgages name lenders, which reveals banking relationships the company did not advertise and gives you a regulated counterparty who had to do their own due diligence.
- Dates are the load-bearing part. An ownership that ended before the event you are investigating is not relevant, and a leak snapshot has one date for everything in it.
Tool reference
| Tool | What it does | Cost |
|---|---|---|
| 527 Explorer | ProPublica’s 527 Explorer is a database that allows users to examine the finances of organizations known as 527s in the United States, which can raise… | free |
| BlockExplorer | Following a bitcoin trail or following a bitcoin account? | free |
| Companies House | Search companies and individuals in the United Kingdom and Gibraltar. | free |
| EDGAR Command Line Interface (edgar-tool) | Tool for the retrieval of corporate and financial data from SEC’s EDGAR (Electronic Data Gathering, Analysis, and Retrieval) database. | free |
| EDGAR | Database of corporate filings for the US | free |
| Etherscan | An explorer that allows researchers to track wallets, transactions and more on the Ethereum blockchain. | free |
| EU consolidated corporate registers (BRIS) | Consolidated company registers covering most of the EU, Iceland, Liechtenstein and Norway. | free |
| EU Sanctions Map | Database of sanctions imposed by the European Union and the United Nations | free |
| Global Suppliers Online | A site dedicated to connect suppliers and buyers of goods from all over the world. | partly free |
| ICIJ Offshore Leaks Database | Find out who’s behind more than 810k offshore companies, foundations and trusts from the Panama Papers, the Offshore Leaks, the Bahamas Leaks and the… | free |
| ImportGenius | Commercial supplier of trade data for 23 countries. Paid service but journalists can ask for free access. | paid |
| ImportYeti | Search 60 million US customs sea shipment records, find company suppliers. | free |
| LittleSis | Connects the dots between influential / wealthy individuals in (mostly US) politics and business. | free |
| Lumen | A research project collecting and publishing legal takedown notices for online content transparency | free |
| North Data | Search for people and companies in EU corporate and trade registers + visualize relationships | partly free |
| OpenSecrets | Data on campaign finance, lobbying, and spending in U.S. politics | free |
| OSINT Tools Map | An interactive map serving as a curated archive of country-specific OSINT resources, including business registries, court records, and cadastral maps… | free |
| RuPEP | Online database of politically exposed persons in Russia, Belarus, Kyrgyzstan, Kazakhstan, Georgia and Moldova. | free |
| SanctionsExplorer | A comprehensive database of current and historical OFAC/UN/EU sanctions | free |
| UN Comtrade Database | United Nations free database of global trade. | free |
| Wikipedia list of company registers | A list of official business registers around the world. | free |
| China-related resources | Resources for research on companies in China. | — |
| OpenSanctions | Open-source international database of sanctions data, persons of interest and politically exposed persons. | partly free |
Pitfalls
- Registry data is self-reported and frequently not verified by anyone. A filed address may be fictional.
- “Beneficial owner” is defined differently everywhere, and thresholds (often 25%) let real control sit just underneath the disclosure line.
- Aggregators lag. For anything current, go to the registry itself.
- Name collisions across jurisdictions are constant. Always carry the registration number.
- Dissolved does not mean gone. Historical filings often hold what you need, and some registries purge them after a few years — capture early.
Worked example
One datum: the name “Northgate Minerals Trading” on an invoice. The company, the numbers and the returns are invented; the order of the pivots and what each source can and cannot settle are not.
# 1. where does it exist at all
curl -s -G 'https://api.opencorporates.com/v0.4/companies/search' \
--data-urlencode 'q=Northgate Minerals Trading' --data-urlencode "api_token=$OC_TOKEN" \
| jq '.results.companies[].company | {name, jurisdiction_code, company_number, current_status}'
# two hits: gb/09876543 (active) and vg/1654321 (OpenCorporates record, source dated 2019)
A UK company and a British Virgin Islands company with the same name. The UK one is where the data
is, so start there and carry the number 09876543.
# 2. who is declared to control it
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/company/09876543/persons-with-significant-control' \
| jq '.items[] | {name, kind, natures_of_control}'
# one PSC, kind "corporate-entity-person-with-significant-control",
# name "Northgate Holdings Ltd", country of registration "Virgin Islands, British"
The declared controller is the BVI company, so UK disclosure has handed the chain straight back offshore. That is the expected outcome, and worth stating as one.
# 3. the officers, and whether they are real or nominal
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/company/09876543/officers' \
| jq '.items[] | {name, officer_role, appointed_on, id:.links.officer.appointments}'
# two directors; take each appointments link
curl -s -u "$CH_KEY:" \
'https://api.company-information.service.gov.uk/officers/AbC123.../appointments' | jq '.total_results'
# 147
147 appointments makes that director a formation-agent nominee, not a decision-maker. The other has three, all in the same group — that is the person to follow.
# 4. screen both names and the BVI entity
curl -s -H "Authorization: ApiKey $OS_API_KEY" -G \
'https://api.opensanctions.org/search/default' --data-urlencode 'q=Northgate Holdings' \
| jq '.results[] | {caption, schema, topics, datasets}'
# no designation; one PEP-adjacent hit on a similarly-named Cyprus entity — not the same company
A near-miss on a different entity is exactly the false positive to discard explicitly, in writing, so it does not resurface later as a claim.
# 5. the offshore end, where the registry publishes nothing
curl -s -H "Authorization: ApiKey $ALEPH_KEY" -G 'https://aleph.occrp.org/api/2/entities' \
--data-urlencode 'q=Northgate Holdings' --data-urlencode 'filter:schema=Company' \
| jq '.results[] | {id, caption, collection:.collection.label}'
# a hit in an ICIJ collection; expand it for the ownership edges
curl -s -H "Authorization: ApiKey $ALEPH_KEY" -G \
'https://aleph.occrp.org/api/2/entities/ENTITY_ID/expand' --data-urlencode 'limit=50' \
| jq '.results[] | {property, entities:[.entities[].caption]}'
# Ownership -> a named individual, percentage 100, startDate 2014
Cross-checking that individual in ICIJ Offshore Leaks gives the same person as beneficiary via the same BVI intermediary, with a residential address shared by eleven other companies — a second, independent confirmation of the same edge.
# 6. does the name appear in anyone's US filings
curl -s -A "$UA" -G 'https://efts.sec.gov/LATEST/search-index' \
--data-urlencode 'q="Northgate Minerals Trading"' \
| jq '.hits.hits[] | {name:._source.display_names, date:._source.file_date}'
# one 10-K exhibit, 2023, listing it as a counterparty of a listed miner
What you can assert: invoice name → UK company 09876543 (Companies House, retrieved with date) → corporate PSC in BVI (UK filing, self-declared) → named individual with 100% ownership from 2014 (Aleph, ICIJ collection, snapshot date stated) → confirmed independently in the ICIJ public database via the same intermediary → named as a counterparty in a 2023 SEC exhibit. Six sources, two of them independent of each other on the key link.
What you still cannot say: whether that individual controls it today. The ownership edge is from a leak snapshot, the BVI publishes nothing current, and the UK PSC declaration names the company rather than the person. “Beneficial ownership is not currently published in the British Virgin Islands; the most recent evidenced owner is X as of 2014” is the honest sentence, and it is a stronger finding than an unqualified assertion.
What would falsify it: a later primary filing naming a different beneficial owner, the Aleph and ICIJ records resolving to different entities once registration numbers are compared, or the SEC exhibit referring to a namesake rather than company 09876543. The 2014 ownership edge is the oldest and weakest link; date it explicitly and replace it when a newer primary record appears.
Broader catalogues
More tools
Further tools for this area from the OSINT Newsletter Tools Library (Blockchain/Cryptocurrency OSINT), excluding those already listed above.
| Tool | What it does |
|---|---|
| Arkham Intel | A blockchain intelligence and analytics platform that de-anonymizes cryptocurrency transactions. |
| Bid Detail | A procurement intelligence platform that makes government contract and tender data searchable. |
| Bitbo | A Bitcoin data dashboard that tracks price, market, mining and on-chain metrics. |
| Bitcoin Talk | A long-running online discussion forum focused on Bitcoin, cryptocurrencies, blockchain technology and related projects. |
| Bitcoin Whoswho | Cryptocurrency intelligence tool used to investigate Bitcoin addresses and identify reported scams. |
| Block Explorer | A blockchain search engine that lets you inspect cryptocurrency transactions, wallet addresses, blocks, and network activity. |
| Dune | A blockchain analytics platform that lets you explore and analyse data from cryptocurrencies and decentralised applications. |
| GuideStar | A nonprofit intelligence platform that provides detailed information on registered charities and nonprofit organisations… |
| TheBigBrother | All-in-one OSINT toolkit that helps track usernames across the web, investigate emails and domains, extract metadata, and follow… |
Sources
Both catalogues below are maintained by other people and are considerably larger than this page. Use them as the canonical index; this sheet is a working route through them.
- Bellingcat’s Online Investigation Toolkit — ~340 tools, each with its own review page covering cost, difficulty, requirements and limitations.
- OSINT Newsletter Tools Library — ~280 tools, organised by investigative goal.
Neither publishes a licence, so nothing here is copied from them: tool names, one-line descriptions, cost flags and links are catalogue facts, and the method and commentary are this site’s own. See credits.