GitHub
TransversePATH FINDER

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

ReturnedNever returned
What can be sentThe holder's name
Applicable limitsTheir institution's customer record
A commitmentTheir KYC tier or documents
A signatureTheir 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

CodeStatusMeaning
path.address.revoked410The address was revoked
path.core.not_found404No 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.

On this page