Agent Verifier reference
Everything on this page is generated from the code that answers your request, so it cannot drift from what you actually receive. The same contract in machine-readable form is at /openapi.json.
How to call it
GET https://fp.packet.guru/api/v1/connection with no body, no parameters and no credential. Send the ordinary request, signed the way you sign requests to anyone else. A signed request carries three standard headers:
Signature-Agent: the origin publishing your keys, quoted per RFC 9651, as"https://example.com"Signature-Input: the RFC 9421 parameters. Thetagmust be"web-bot-auth", or the signature is not read as an agent signature at all.Signature: the signature itself.
Without them you still get an answer: plain facts about the address you came from are read for every caller, signed or not.
What comes back
A block marked varies is described rather than enumerated: its members depend on what you sent. Fields that do not apply to a request are absent rather than null. An absent key reads the same as a field that does not apply, and a null only invites guessing.
| Field | Values | Meaning |
|---|---|---|
| geo | varies | Country, region, city and timezone of your address. |
| geo.city | stringnull | City the address is placed in. |
| geo.country | stringnull | ISO 3166-1 alpha-2 country code of the address. |
| geo.region | stringnull | First-level administrative division. |
| geo.timezone | stringnull | IANA timezone name for that place. |
| id | string | Identifier for this verdict, safe to quote in a support request. |
| identity | varies | Who you were established to be, and on what evidence. Empty of judgement: the outcome of checking a signature you sent against a directory you published. |
| identity.bot | stringnull | The specific bot within that operator, when the evidence distinguishes one. |
| identity.method | string | How the name was established: `signature` when your own signature verified, `range` when your address sat in a list its operator publishes. |
| identity.operator | stringnull | The name established for you, when one could be. Null when nothing named you. |
| identity.trusted | boolean | Whether the established name is one this verifier holds a trust anchor for. A verified signature from a domain nobody knows is a verified signature from a domain nobody knows. |
| ip | varies | Plain facts about your own address: network, operator, reputation of the address itself. |
| ip.asn | integernull | Autonomous system number of the network. |
| ip.blacklisted | boolean | Whether the address appears on the public abuse feeds this service tracks. |
| ip.hostname | stringnull | Reverse DNS name of the address, when it has one. |
| ip.org | stringnull | The organisation that network is registered to. |
| ip.origin | string | What kind of network the address belongs to: residential, datacenter, mobile, and so on. |
| ip.riskLevel | string | The same figure as a word: low, medium or high. |
| ip.riskScore | integer | Reputation of the ADDRESS, 0 to 100, higher meaning worse. A datacenter address scores in the middle by nature: it is a statement about where you are, never about who you are or what you did. |
| meta | varies | Envelope: API version, how long the verdict took, which axes contributed. |
| meta.apiVersion | string | Version of this API. `v1` today. |
| meta.axes | array | Which sources contributed to this answer. An axis absent here contributed nothing, which is why a field can be missing without anything being wrong. |
| meta.queryTimeMs | integernull | How long the answer took to build, in milliseconds. |
| mode | direct | You called this endpoint yourself, so the answer describes your own request. |
| signals.agent.card | varies | Your own published card read back to you, plus the one promise a single live request can settle: the User-Agent you said you would send. |
| signals.agent.keyDirectoryBinding | binding-proven · binding-key-proven · binding-not-provided · binding-failed · binding-not-checked | Whether your directory proved it published the key set that was read. binding-key-proven means the response signature verified but did not cover the body, so possession of the key is proven and the key set is not. |
| signals.agent.knownBot | matched · none | The second, independent check, and the one that has nothing to do with your signature: some operators publish the address ranges their crawlers use, and those lists are read here nightly. matched means the address this request came from is inside such a published range. none is the ordinary answer and is not a fault: it means no operator publishes a list containing this address, which is true of every address that is not a large crawler, and true of most agents. |
| signals.agent.notes | array | Observations about your setup that are not faults. |
| signals.agent.refutedSignatures | array | Other signatures on the same request whose named directory does not list the key they used. You can confirm each against the published file yourself. |
| signals.agent.signature | varies | Whether a Web Bot Auth signature was present and how it came out. |
| signals.agent.webBotAuthDetail | varies | The specific fault, and the sentence saying what to change. This is the heart of the free answer. Besides reason and fix it may carry three more members. clockDriftSec is how far the signing clock sits from this server, in seconds. directoryStatus is the HTTP status the named key directory answered with, where 403 or 429 is almost always a firewall in front of it refusing this verifier rather than anything about the keys. fromCachedDirectory appears only when true and means the answer rests on a stored copy that could not be refreshed, so a key reported as absent may simply have been published after that copy was taken. |
| signals.automation.clientHints | string | State of the Client Hints headers you sent. |
| signals.automation.uaClient | string | What your User-Agent string alone says you are. A string, not a judgement. |
| signals.transport.provenance | measured · unavailable | measured = your request arrived directly and was read first-hand. unavailable = it did not, and no finding in this answer rests on it. |
| success | boolean | Always true on a 200. Errors carry success:false and an error object. |
All 36 faults, and how to fix each
When a signature does not hold, the answer names one of these in signals.agent.webBotAuthDetail.reason and carries the remedy beside it. The whole list is published on purpose. These are the standard's own refusals, and a report that cannot be acted on without reading somebody else's documentation is not much of a report.
Two things the table cannot show. The sentence in an answer is sometimes more specific than the row: where the situation allows it the remedy names the case rather than the family, so a directory answering a redirect is told about redirects, and a signature base assembled in a known-wrong shape is told which line to change. The row is the general answer for that fault. And bad-tag never arrives as a reason at all. A signature tagged for another profile was not addressed to this check, so the answer reports signature.present: false and carries the same advice as a note.
The outcome column is the standard's own verdict for that fault. invalid means a proof was presented and does not hold.unverified means there was not enough to decide on, which is a statement about reach rather than a finding against the caller, and never a deduction.
The signature itself
The signature was read, and something about it does not hold: the bytes, the parameters, or what it covers.
| Reason | Outcome | What to change |
|---|---|---|
| bad-signature | invalid | The signature does not verify against the published key. The base is a likelier cause than the key: covering "signature-agent" puts the whole field into it, covering "signature-agent";key="sig1" puts that member value alone. Rebuild the base, then check the key is the one your directory publishes. |
| bad-tag | invalid | Set the Signature-Input tag parameter to "web-bot-auth". Without it the signature is not read as a bot-auth signature. |
| canonicalization-mismatch | invalid | The signature is sound, the base it was built over is not the one rebuilt here: a value formatted differently, identifiers written without quotes, a whole field where the covered member belongs, or a trailing newline. The fix beside this reason names which. |
| clock-ahead | invalid | Your server's clock is ahead: the signature is dated in the future. Enable time synchronisation (NTP). |
| component-unsupported | unverified | The signature covers a component this verifier does not rebuild. Supported: @method, @authority, @path, @target-uri, signature-agent, content-digest. Nothing is wrong with your signature. It simply was not checked here. |
| component-unsupported-parameter | unverified | A covered component carries a parameter this verifier does not support. Only the key parameter on signature-agent is understood. |
| content-digest-mismatch | invalid | The Content-Digest you signed does not match the body that arrived. Recompute the digest over the exact bytes you send. |
| expired | invalid | The signature expired before the request arrived. Set expires with more headroom. A minute is usually enough. |
| missing-component | invalid | The signature covers a component that this request does not carry, so the signature base cannot be rebuilt. Send every component you sign. |
| missing-fields | invalid | Signature-Input is missing required parameters. Include created, expires, keyid and tag. |
| signature-alg-unsupported | unverified | The signature names an algorithm this verifier does not implement. Ed25519 is the only one checked here, so nothing was decided about yours. Sign with Ed25519 to be verifiable at this endpoint. |
| signature-input-unreadable | invalid | Signature-Input could not be parsed. It must be a dictionary member such as sig1=("@authority");created=1;expires=2;keyid="…";tag="web-bot-auth". |
| signature-value-unreadable | invalid | The Signature header has no member matching the Signature-Input label, or its value is not base64 between colons. |
| target-not-covered | invalid | The signature covers no request target, so it is valid against any endpoint and replayable elsewhere as your identity. Cover "@authority" or "@target-uri". The draft requires one of them. |
The Signature-Agent header
The header that says where your keys are published could not be read, or does not point at one usable place.
| Reason | Outcome | What to change |
|---|---|---|
| signature-agent-absent | unverified | No Signature-Agent header. The current draft requires one on a signed request, and without it nothing says where your public key is published. |
| signature-agent-ambiguous-member | invalid | Signature-Agent carries several members and none can be matched to this signature. Add a key parameter to the covered signature-agent component, or use one member named after the signature label. |
| signature-agent-member-missing | invalid | The signature covers a Signature-Agent member that the header does not contain. The key parameter must name a member that is actually sent. |
| signature-agent-mixed-forms | invalid | Signature-Agent mixes the bare-string and dictionary forms in one header. Send one form only. |
| signature-agent-not-https | invalid | Signature-Agent must point at an https address. Key directories are not read over plain http. |
| signature-agent-not-origin | invalid | For the directory type the value must be a bare origin such as "https://example.com", with no path, query or fragment. Use type=jwks_uri to point at a file. |
| signature-agent-port-unsupported | unverified | Publish the key directory on the default HTTPS port and name it without one, as "https://example.com". A Signature-Agent carrying another port is not fetched. |
| signature-agent-unparsable | invalid | Signature-Agent is not readable. Send a dictionary member such as sig1="https://example.com". |
| signature-agent-unsupported-type | unverified | The Signature-Agent type parameter names a discovery mechanism this verifier does not support. Use directory, jwks_uri or cimd. |
Your key directory
The address you named was fetched, and no usable key directory came back from it.
| Reason | Outcome | What to change |
|---|---|---|
| directory-bad-status | unverified | Your key directory answered a status other than 200, in directoryStatus here. 403 or 429 is a bot rule refusing this verifier: exempt /.well-known/http-message-signatures-directory itself, which serves public keys to a plain GET, rather than a User-Agent anyone can copy. 5xx is your origin failing. 3xx means the keys are elsewhere than your Signature-Agent says. |
| directory-host-unknown | unverified | The host in Signature-Agent does not exist in DNS, so no key directory can be published there. Check the address you send. |
| directory-no-keys | unverified | Your key directory holds no usable Ed25519 key. Check the kty, crv and x fields. |
| directory-not-published | unverified | The host exists but publishes no key directory at the well-known path. Publish it at /.well-known/http-message-signatures-directory. |
| directory-too-large | unverified | Your key directory is larger than this verifier will read. Publish only the keys currently in use. |
| directory-too-many-keys | unverified | Your key directory holds more keys than this verifier will search. Remove keys that are no longer in use. |
| directory-unreachable | unverified | Nothing answered at your key directory address: a name that does not resolve, a TLS failure or a timeout. Check the well-known path from outside its own network. It has to answer 200 directly over HTTPS on the default port, since a redirect is not followed. |
| directory-unreadable | unverified | The directory body does not parse. It must be JSON containing a keys array. |
| directory-wrong-media-type | unverified | Your key directory is served with the wrong Content-Type. Use application/http-message-signatures-directory+json. |
The key you signed with
The directory was read, and the specific key your signature names is not usable from it.
| Reason | Outcome | What to change |
|---|---|---|
| key-before-activation | invalid | This key nbf is in the future, so the key is not active yet. |
| key-not-found | unverified | The public key for this signature could not be obtained from the address you published it at. |
| key-not-in-directory | unverified | No key with this keyid is in your directory. Make sure keyid matches either the key thumbprint or its kid. |
| key-past-expiry | invalid | This key exp is already in the past. Publish a replacement key. |
What this does not claim
- A verified signature is identity, not trust. It proves control of the domain whose keys were published. Anyone can publish keys at their own domain, so a valid signature from a name nobody knows stays a valid signature from a name nobody knows.
- A directory that could not be reached costs nothing. An unreachable directory is a gap on this side, not a fault on yours, and the answer says so rather than counting it against the signature.
- Nothing unseen is reported as seen. When the request did not arrive directly,
provenancesaysunavailable, and no finding in that answer rests on something that was never read.
The standards behind this
For machines
- API catalog Every API here, each with its description and its documentation. Standard address, RFC 9727.
- openapi.json The response contract, machine-readable. The same source the reference page is built from.
- MCP server card What an MCP registry reads about this server: address, transport, and the tool it exposes.
- packet.guru key directory The public keys this site signs its own outbound requests with. A working example of the file you publish.
- packet.guru agent card What the fetcher on this site promises about itself, including the exact User-Agent it sends.
Read next
MCP server Endpoint, client config, and the one tool it exposes.