API reference
FINDER
Both reachability steps in one call — and why the second one is not fetched for you.
Requires a member credential.
POST /finder
{
"identifier_type": "phone",
"identifier": "+221771234567"
}Response
{
"step1": {
"kind": "sonar_answer",
"found": true,
"member": "member-b",
"endpoint": "https://api.member-b.com",
"kid": "net_example_2026_01",
"signature": "…"
},
"step2": {
"follow": "https://api.member-b.com/api/path/v1/resolver",
"composed_by": "client"
}
}Why step2 is a pointer, not an answer
The endpoint runs step 1 and tells you where step 2 lives. It does not fetch it.
A network that fetched step 2 would see the destination's capability on top of every search: which rails a competitor supports, which assets, what limits, and how they change over time. That is not routing information — it is a competitor's product roadmap, delivered daily.
So the composition happens on your side. The SDK does it in one call:
const reach = await path.finder({ identifierType: 'phone', identifier: '+221771234567' });
reach.found; // true
reach.member; // "member-b"
reach.accepts; // [{ asset: "USDC", chain: "base" }, …]
reach.proofs; // { sonar, resolver } — both signed envelopesOne extra round trip. What it buys is that no single party sees both halves.
A network may offer to relay step 2, and must declare it in its discovery document if it does.
Read finder there before assuming either behaviour.
Keep the proofs
reach.proofs holds both signed answers. Store them alongside whatever you do next.
When a payment lands somewhere unexpected, the question is what you were told before you sent. A signed pair answers it; a log line does not.
Skipping step 1
When you already know the holder — the relationship profile — do not call this endpoint at all.
Call RESOLVER directly, with no credential:
const answer = await path.resolver('path:4a91c2f7e8d3', 'https://api.member-b.com');That path requires membership of no network. It is the complete route from key to capability available to anyone, and it is what makes the open ring checkable rather than asserted.
Errors
| Code | Status | Meaning |
|---|---|---|
path.finder.budget_exhausted | 429 | Reciprocity budget spent — not a rate limit |
path.finder.key_unresolvable | 404 | Nothing reachable, or not visible to you |
path.finder.holder_unavailable | 503 | Listed, but not answering — retryable |
holder_unavailable is worth handling separately from key_unresolvable. Someone does hold this
key; their endpoint is down. Retrying is reasonable, and telling a user "temporarily unreachable" is
accurate where "not found" would not be.