Method
Two endpoints that share an origin cannot help but agree on details their operators never thought to vary. The method looks for those agreements, measures how many of them hold, and compares the prices of the endpoints that hold them.
Nothing in the method reads a contract, a company registration, or a statement of intent. It reads responses.
What gets captured
Every endpoint is paid for and called. Everything below is recorded on every call, whether or not the current finding uses it, because an observation not taken today cannot be taken later.
- The TLS certificate - the credential a server presents to prove it is the host it claims to be - including who issued it and when it expires, and the JA4 fingerprint, which is a short summary of the exact way the server negotiates an encrypted connection.
- The response headers: what the server calls itself, which version of HTTP it speaks, the order the headers arrived in, how the body was compressed, and any caching or rate-limit headers.
- The network the server sits on, identified by its ASN - the number assigned to the block of addresses an operator or hosting provider runs.
- Time to first byte, recorded separately from total time. The first is how long the server took to start answering; the second is how long the whole answer took.
- Clock skew - how far the server's own clock sits from true time.
- The shape of the body: the exact field names, the order the keys came back in, which fields arrive empty rather than missing, how many decimal places a number carries, how a date is written, and the character encoding.
- The wording of an error, character for character, and whether an error is returned as valid JSON at all.
- Redirect behaviour, and what the endpoint does with a conditional request.
- The payment envelope: the chain, the facilitator, and the denomination the price is quoted in.
How endpoints are grouped
This is method version v1. Every published cluster records the version it was produced under, so a later change to the method does not silently rewrite what was published before it.
- Endpoints are grouped by the capability their operator advertises. Comparison happens inside a group and never across groups.
- Inside a group, every pair of endpoints is scored on how often the pair agrees across a fixed set of ten fields.
- A pair becomes a candidate at 90.00 percent agreement or higher, across at least 30 paired observations, each using a different probe input. A paired observation is one input sent to both endpoints and the two answers compared. The inputs are deliberately varied and deliberately odd, because coincidental agreement on an ordinary input is common and coincidental agreement on thirty strange ones is not.
- A candidate is published only when the error wording matches character for character on at least three different inputs chosen to produce an error. Error-string agreement is necessary rather than merely contributory - an error message is the part of a service nobody rewrites when they rebrand it.
- Confidence is high at 30 or more paired observations with a verbatim error match, moderate at 15 to 29, andlow below 15. Low-confidence groups are stored and never published.
- Inside a published group, the cheapest observed member is the one nearest the origin. The distance between the cheapest and the most expensive is the spread, stated as a multiple. Every price carries the date it was observed.
The ten fields
The discriminating set a pair is scored on:
| Field | What it holds |
|---|---|
| error_string | the wording of an error, character for character |
| key_order_hash | the order the keys came back in |
| field_paths_hash | the full set of field names and their nesting |
| header_order | the order the response headers arrived in |
| server_header | what the server calls itself |
| tls_issuer | who issued the certificate the server presented |
| ja4_fingerprint | the exact shape of the encrypted handshake |
| timestamp_format | how the response writes a date |
| float_precision_max | how many decimal places a number carries |
| null_vs_omitted | which fields come back empty rather than missing |
The thresholds are starting values
The 90.00 percent agreement threshold and the 30-observation minimum were chosen by hand before the first run. They are tuned against real data and the tuned values are recorded here with the date they changed. Confidence in the current values: low.
What this cannot see
- Two operators independently using the same upstream vendor will cluster. That is a shared dependency, and nothing in the method distinguishes it from any other reason two responses agree.
- Two operators running the same open-source template will cluster.
- An operator who deliberately randomises key order and error wording will not cluster, and Crosspeel will not detect that they are avoiding detection.
- Nothing here observes ownership, contracts, or intent. The findings are about responses, not companies.
Rotation, cadence, and publication lag
Publishing how the probes are scheduled is not a leak. An operator who knows a probe is coming, but not when, and knows a finding is held back, but not for how long, is deterred by the same facts that would otherwise be a weakness.
- The wallet that pays for probes is rotated between runs. A single address that appears shortly before every publication is trivially blocked.
- Probe runs are not on a fixed cadence. The interval is randomised inside the scheduling window.
- A finding is published at least seven days behind the observations that support it.
- Everything is stored whether or not it is published. An endpoint whose field order changes within days of a publication has been observed changing it, and that observation is kept too.
Version history
| Version | What changed | Date |
|---|---|---|
| v1 | First version. Exact matching over the ten discriminating fields, with verbatim error-string agreement required before a group is published. | 2026-09-04 |