OSINT^:OSINT

Companies, Ownership & Finance

Corporate registries, filings and beneficial-ownership data for working out who actually controls a company.

intermediateupdated 2026-10-03opencorporates · edgar · edgartools · aleph

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

  1. 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.
  2. Get the registration number and use it thereafter. Company names are reused across jurisdictions, reused within them after dissolution, and changed deliberately.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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

SourceCoverage
OpenCorporatesThe largest open company database, ~200 jurisdictions. Best starting point for “does this company exist and where”.
Companies HouseUK and Gibraltar. Free, complete filing history including scanned documents, and a clean API. The best-documented major registry.
SEC EDGARAll US public-company filings. Full-text searchable back to 2001 and genuinely free.
OCCRP AlephRegistries, leaks and court records in one index, with a shared entity model. The tool for cross-border work.
OpenSanctionsConsolidated sanctions lists, PEP data and persons of interest, as bulk data and an API.
ICIJ Offshore LeaksPanama / Paradise / Pandora Papers entities, officers and intermediaries.
Open OwnershipBeneficial-ownership data, where it is published at all.
BRISConsolidated 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.

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.

SourceWhat it adds
BRIS / e-JusticeOne search box over most EU registers. Coverage per country varies from full filings to name-and-number only.
North DataEU registers with relationship graphs and German trade-register notices, which often name shareholders the registry does not.
Open Ownership RegisterBeneficial-ownership declarations consolidated across the countries that publish them.
RuPEPPolitically exposed persons in Russia, Belarus, Kazakhstan, Kyrgyzstan, Georgia and Moldova, with family and associate links.
SanctionsExplorerHistorical OFAC, UN and EU designations, including delisted ones — which is what you need for a structure that predates a current list.
ImportYetiUS customs sea-shipment records: who ships to whom, which evidences a trading relationship no registry records.
Wikipedia’s register listThe 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

ToolWhat it doesCost
527 ExplorerProPublica’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
BlockExplorerFollowing a bitcoin trail or following a bitcoin account?free
Companies HouseSearch 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
EDGARDatabase of corporate filings for the USfree
EtherscanAn 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 MapDatabase of sanctions imposed by the European Union and the United Nationsfree
Global Suppliers OnlineA site dedicated to connect suppliers and buyers of goods from all over the world.partly free
ICIJ Offshore Leaks DatabaseFind 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
ImportGeniusCommercial supplier of trade data for 23 countries. Paid service but journalists can ask for free access.paid
ImportYetiSearch 60 million US customs sea shipment records, find company suppliers.free
LittleSisConnects the dots between influential / wealthy individuals in (mostly US) politics and business.free
LumenA research project collecting and publishing legal takedown notices for online content transparencyfree
North DataSearch for people and companies in EU corporate and trade registers + visualize relationshipspartly free
OpenSecretsData on campaign finance, lobbying, and spending in U.S. politicsfree
OSINT Tools MapAn interactive map serving as a curated archive of country-specific OSINT resources, including business registries, court records, and cadastral maps…free
RuPEPOnline database of politically exposed persons in Russia, Belarus, Kyrgyzstan, Kazakhstan, Georgia and Moldova.free
SanctionsExplorerA comprehensive database of current and historical OFAC/UN/EU sanctionsfree
UN Comtrade DatabaseUnited Nations free database of global trade.free
Wikipedia list of company registersA list of official business registers around the world.free
China-related resourcesResources for research on companies in China.—
OpenSanctionsOpen-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.

ToolWhat it does
Arkham IntelA blockchain intelligence and analytics platform that de-anonymizes cryptocurrency transactions.
Bid DetailA procurement intelligence platform that makes government contract and tender data searchable.
BitboA Bitcoin data dashboard that tracks price, market, mining and on-chain metrics.
Bitcoin TalkA long-running online discussion forum focused on Bitcoin, cryptocurrencies, blockchain technology and related projects.
Bitcoin WhoswhoCryptocurrency intelligence tool used to investigate Bitcoin addresses and identify reported scams.
Block ExplorerA blockchain search engine that lets you inspect cryptocurrency transactions, wallet addresses, blocks, and network activity.
DuneA blockchain analytics platform that lets you explore and analyse data from cryptocurrencies and decentralised applications.
GuideStarA nonprofit intelligence platform that provides detailed information on registered charities and nonprofit organisations…
TheBigBrotherAll-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.

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.

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.