GitHub

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 envelopes

One 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

CodeStatusMeaning
path.finder.budget_exhausted429Reciprocity budget spent — not a rate limit
path.finder.key_unresolvable404Nothing reachable, or not visible to you
path.finder.holder_unavailable503Listed, 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.

On this page