API reference
RESOLVER
Step 2 of reachability — what a destination accepts. Public, answered by the holder, and the only complete path that needs no network.
Answers what does this destination accept. Served by the member holding the account, peer to peer.
No credential required. This is deliberate and load-bearing: it is the complete path from key to capability that requires membership of nothing, and every claim about the protocol's openness rests on it.
GET /resolver/{key}
GET /api/path/v1/resolver/path:4a91c2f7e8d3
Path-Version: 2026-10-04.genesis{key} is an opaque PATH address. Call it at the holder's base URL — from a
SONAR answer, or one you already knew.
Response
{
"kind": "resolver_answer",
"address": "path:4a91c2f7e8d3",
"requester": null,
"standing": "holder",
"valid": true,
"grammar": 2,
"accepts": [
{
"rail": "blockchain",
"chain": { "slug": "base", "caip2": "eip155:8453", "evm": { "chain_id": 8453 } },
"asset_type": "token",
"asset_code": "USDC",
"standard": "erc20",
"contract": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
},
{ "rail": "book", "asset_type": "fiat", "asset_code": "USD" }
],
"limits": {},
"commitment": "8f29c41a…",
"commitment_version": 3,
"protocol_version": "0.2.0",
"signed_at": "2026-09-09T10:00:00Z",
"kid": "op_member_b_2026_01",
"signature": "…"
}What it never returns
| Returned | Never returned |
|---|---|
| What can be sent | The holder's name |
| Applicable limits | Their institution's customer record |
| A commitment | Their KYC tier or documents |
| A signature | Their balance or history |
| Any other identifier of theirs |
A routing question gets a routing answer. Anything more turns a lookup into a disclosure — and the whole privacy model rests on that not happening. Checking the name before sending is a separate call: the payee check.
standing
holder or gateway, inside the signature. Read it before the terms.
holder — the terms are the destination's own, and commitment is its own pledge. Every answer
this implementation emits is a holder's.
gateway — the responder can reach the destination without holding it. The terms, the limits and
any fee are the intermediary's, and commitment is null because a receiver who does not know
it is being addressed has pledged nothing. A caller that ignores standing reads a middleman's
price believing it read the receiver's.
Reserved here: answering as a gateway needs a declaration in
gateway and a lookup path against non-customer keys,
and PATH-FINDER.Gateway is not claimed. A gateway is never reachable through
SONAR.
The commitment
A digest over {grammar, address, accepts, limits}, pledged when the holder set those terms and quoted
here unchanged. The answering path does not compute it and cannot choose it per caller.
commitment is null when the address has no standing intent: nothing was ever pledged, and the
accepts above were inferred from the holder's raw receive targets. That is a real state, not an
error — but it is not a guarantee either, and a payer that wants one should treat it as such.
GET /commitments/{key}
The same digest, on its own. Unauthenticated, cacheable, identical for every caller.
GET /api/path/v1/commitments/path:4a91c2f7e8d3{
"type": "path.address.commitment",
"address": "path:4a91c2f7e8d3",
"issuer": "member-b",
"commitment": "8f29c41a…",
"version": 3,
"committed_at": "2026-08-02T09:14:00Z",
"kid": "op_member_b_2026_01",
"signature": "…"
}Fetching this separately is the entire mechanism, and skipping it makes the check worthless. Recomputing the digest over the terms you received and matching it against the digest inside the same answer catches a bug and nothing else: whoever chose the terms chose that digest too, so they agree by construction even when both are false. The comparison that bites is against this endpoint, whose value was pledged by a separate act and is served identically to people with no stake in your transaction.
import { checkCommitment } from '@pathprotocol/sdk';
const answer = await path.resolver(address, holder);
const published = await path.addressCommitment(address, holder);
const check = checkCommitment({
address,
accepts: answer.accepts,
limits: answer.limits,
grammar: answer.grammar,
answerCommitment: answer.commitment,
publishedCommitment: published.commitment,
});
if (check.status === 'mismatch') throw new Error('path.address.commitment_mismatch');Treat a mismatch as a potentially compromised resolver, not as a transient error to retry.
What this does not cover, stated plainly so nobody builds on a guarantee that is not here: an operator compromised deeply enough to rewrite the stored terms and the published digest and the answer, consistently, for everyone. Catching that requires an append-only log with outside observers, so that lying to one payer means forking a history other people are watching. It is not in v0.1.
What it does cover is narrower and still worth having: a read path that has been compromised or has simply drifted can no longer alter terms silently, and an operator cannot quote one set of terms to you and another to the rest of the world.
Errors
| Code | Status | Meaning |
|---|---|---|
path.address.revoked | 410 | The address was revoked |
path.core.not_found | 404 | No such address |
revoked and not_found are distinct here, unlike SONAR's negatives. The payer needs the
difference: one means ask for a current address, the other means this destination is unknown.
Verify before acting
import { verifyAgainstIssuer } from '@pathprotocol/sdk';
const answer = await path.resolver('path:4a91c2f7e8d3', 'https://api.member-b.com');
const { valid } = await verifyAgainstIssuer(answer, 'https://api.member-b.com');
if (!valid) throw new Error('Refusing to send against an unverified answer');Worth doing even over TLS. TLS proves you reached the right server; it says nothing about the statement it handed you, and it leaves nothing behind for the argument six months later.
Standing intent beats capability
What comes back reflects the holder's standing intent where one is set, not merely what its infrastructure supports. A member may hold a USDC wallet and still decline USDC this month — the receiver's policy wins, and the answer reflects the policy.