paytrace
Attribute websites to the legal entities that get paid for them.
25 collectors for attribution-graph,
built on one fact: an operator can hide registrant, hosting, DNS and email — but
to be paid, a real legal entity must be named to an ad system, and sellers.json
publishes that name.
pip install paytrace
The chain
scraper-site.example/ads.txt
└─ pubmatic.com, 156423, DIRECT
└─ pubmatic.com/sellers.json
└─ name: "Example Media Holdings Ltd"
├─ GLEIF → LEI, address, ownership tree
├─ SEC EDGAR → CIK, officers, former names
├─ Companies House → directors, beneficial owners
└─ RDAP / crt.sh → back to infrastructure
Every hop is free, keyless, and either statutory or self-published.
Commands
paytrace run --case case.yaml --index paytrace.sqlite --out ./out
paytrace portfolio scraper-site.example --index paytrace.sqlite --registrants
paytrace registries --jurisdiction IN
paytrace-index build --domains tranco-top-1m.txt --db paytrace.sqlite
paytrace-index sellers --db paytrace.sqlite
paytrace-index lookup --db paytrace.sqlite --seller pubmatic.com/156423
Runnable examples:
python examples/end_to_end_domain.py # full chain, offline
python examples/reference_collector.py # annotated collector template
python demo/run_demo.py # prompt injection demo (below)
Collectors
| Name | Source | Auth |
|---|---|---|
ads_txt_owner |
/ads.txt v1.1 incl. OWNERDOMAIN |
none |
sellers_json |
{adsystem}/sellers.json |
none |
analytics_ids |
AdSense, GA4, UA, GTM, Pixel, Sentry, Yandex | none |
wayback |
Internet Archive CDX | none |
gleif |
GLEIF LEI index | none |
sec_edgar |
EDGAR full-text + submissions | none¹ |
companies_house_uk |
UK Companies House | free key |
opencorporates |
OpenCorporates v0.4 | key |
uspto_trademark |
USPTO Open Data | free key |
imprint |
/impressum, /legal, /terms |
none |
opensanctions_yente |
self-hosted yente | none |
rdap |
IANA RDAP bootstrap | none |
crtsh |
Certificate Transparency | none |
internetdb |
internetdb.shodan.io |
none |
mnemonic_pdns |
mnemonic passive DNS | none |
favicon_mmh3 |
local mmh3 | none |
code_host_org |
GitHub/GitLab orgs | free token |
package_registry |
npm, PyPI, crates.io | none |
nyc_acris |
NYC ACRIS | none |
uk_overseas_property |
HM Land Registry OCOD | bulk |
¹ SEC requires a declared User-Agent. Set contact_email in the case file.
Registry catalog — 30 registries across ~20 jurisdictions, with un-automatable ones marked so you know where to search by hand:
paytrace registries --jurisdiction IN
paytrace registries --yields beneficial_owner
paytrace registries --coverage
Planted identifiers
High-selectivity identifiers are also high-forgeability identifiers. Pasting a
competitor's ca-pub- into your page source costs nothing.
adversarial.py runs four checks before a shared ID becomes a finding:
| Check | Question |
|---|---|
| Reciprocity | Does sellers.json name this domain back? |
| Load-bearing | Is the loader present, or is the ID inert text? |
| Temporal depth | Does it have archived history? |
| Asymmetry | Is a tiny site carrying a major property's ID? |
Failures demote, never delete — a planted identifier is evidence of someone manufacturing an attribution.
Further reading
The method in depth — sellers.json is not always at the domain root, Expansion from a name, The pivot: the answer is on a sibling, not the seed, The pivot loop — the seed is not the answer, The sibling pivot, Egress and geography, Company registers are not uniform, Source coverage, Mandated disclosures — where the name actually is, Fingerprinting: IPs, hashes, SimHash, DIRECT does not mean direct, Build the corpus first, Replacing paid APIs, Portfolio expansion, Ingest from other tools, Agent + prompt injection demo, Person-scoped collectors — is in docs/GUIDE.md.
Security
- SSRF guard.
adsystemcomes from the target'sads.txt, so169.254.169.254, 1, DIRECTwould reach cloud metadata. Every URL is validated before connecting: scheme, userinfo, port, and DNS resolution against private/reserved ranges. Redirects re-checked per hop. - ReDoS-bounded regexes, response size caps, parameterised SQL.
banditandpip-auditin CI. Tests intests/test_security.py.
Deploying
DEPLOYMENT.md. Run investigations outside the repo:
mkdir -p ~/cases/CASE-001 && cd ~/cases/CASE-001
paytrace run --case case.yaml --index ~/corpus/paytrace.sqlite --out .
Known limitations
METHODOLOGY_AUDIT.md is an adversarial read of this toolkit, written as if by
a reviewer with no stake in it. Read it before relying on a number.
The governing limitation: calibration is unvalidated. Every probability is a defensible ordering, not a measured frequency. And of nine identified failure modes, four bias toward overconfidence and none bias low — an asymmetry that is a direct consequence of having nothing fitted to catch it.
Status
See CHANGELOG.md. Pre-release; API unstable. is_confidential: 1 suppresses the seller name (you keep
seller_type). EDGAR's full-text endpoint is undocumented — pin a contract test.
Author
Tushar Karumudi — github.com/tusharkarumudi
License
Copyright 2026 Tushar Karumudi.
Licensed under the Apache License, Version 2.0. See LICENSE.
Cite via CITATION.cff.
Metadata
Release files for paytrace 2.0.7
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| paytrace-2.0.7.tar.gz | 234.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| paytrace-2.0.7-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 408.9 kB
Release files / paytrace-2.0.7.tar.gz
| Download URL | paytrace-2.0.7.tar.gz |
|---|---|
| Size | 234.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c5307b0bec8346df6253dd8c19fcc9bc5bcc0461f39845cb4491f9a9a7905f16
|
|
BLAKE2b-256 checksum How to use checksums |
c05b06a77719401b0aaa9bde15f742339d29391437e4503f79a460f0805c40f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency logRelease files / paytrace-2.0.7-py3-none-any.whl
| Download URL | paytrace-2.0.7-py3-none-any.whl |
|---|---|
| Size | 174.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d58747a6bb9ec42700e8c0c89e67b949a989816a2a8491b01d392a1ec8f253d5
|
|
BLAKE2b-256 checksum How to use checksums |
34abad5b09b2ff35b0c7fbfbf4864ae056ed4af5743b3f077ec0289059ed4219
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency log