SDK reference
Crossway
CrosswayClient — resolve destinations on other networks through Path Global, and verify every answer against its connector attestation.
LIVE — @pathprotocol/sdk 0.3.0.
PIP-0014.
Private access. The Crossway API is operated by Path Global. A credential is issued per client and per registry, on request at pathglobal.finance. It is not a PATH member credential, and a member credential does not open Crossway.
new CrosswayClient(options)
import { CrosswayClient } from '@pathprotocol/sdk';
const crossway = new CrosswayClient({
credential: { kid: 'client_2026_01', privateKeyHex: process.env.PATH_GLOBAL_KEY! },
});| Option | Notes |
|---|---|
baseUrl | Defaults to https://api.pathglobal.finance/crossway/v1 (CROSSWAY_BASE_URL) |
credential | { kid, privateKeyHex }, issued by Path Global. Requests are signed as in Authentication |
fetch, timeoutMs | As for PathClient |
A separate client, on purpose. A Crossway answer is a registry's entry relayed by a connector, not a PATH member's signed terms, and the types keep the two apart.
From your server, never from an app. Same reason as for a member credential.
Discovery
const { candidates } = await crossway.discovery({ keyType: 'phone', key: '+5511987654321' });
// [{ registry: "pix.dict", rail: "pix", zone: "BR", operations: [...], granted: true }]Which registry can know the key. No registry is queried: a candidate never means the key exists.
keyType: phone, email, tax, random, alias, iban, vpa.
finder(params)
SONAR then RESOLVER, composed on your side.
const reach = await crossway.finder({
registry: 'pix.dict',
keyType: 'phone',
key: '+5511987654321',
});
reach.found; // true
reach.participant; // { id: "12345678", name: "Example Bank S.A." }
reach.resolution?.rail; // "pix"
reach.resolution?.asset_code; // "BRL"
reach.resolution?.destination; // { key_type: "phone", key: "+55…", account_type: "CACC" }
reach.proofs; // { sonar, resolver }Stops after SONAR when the registry does not hold the key. sonar() and resolver() are available
on their own. Each sends purpose: "payment_preparation" and a fresh nonce.
Budgeted and logged. Look a key up when a payment is being prepared. There is no batch call.
Hand resolution.destination to your provider on the external rail. Crossway does not move money.
payeeCheck(params)
const check = await crossway.payeeCheck({
registry: 'pix.dict',
keyType: 'phone',
key: '+5511987654321',
mode: 'verify',
name: 'Alice Martin',
});
check.result; // "match" | "close_match" | "no_match" | "unavailable"The scheme's own name check, in the two modes of the PATH payee check. Never another identifier.
Verifying
const { valid, reason } = await crossway.verify(reach.resolution!);
if (!valid) throw new Error(`Refusing an unattested destination: ${reason}`);verify fetches Path Global's keys and the attestation the answer names, then checks:
- the answer's signature, under the connector key;
- the attestation's signature, under the root key;
- that the attestation is
active, unexpired, issued to that connector, and covers that registry and that operation.
Without network calls, when you hold the keys and the attestation:
import { verifyCrosswayAnswer } from '@pathprotocol/sdk';
const result = verifyCrosswayAnswer(answer, attestation, keys);Public reads
await crossway.registries(); // the connector register
await crossway.keys(); // root and connector keys
await crossway.attestation('att_9kq3…'); // read at use: revocation is checked hereErrors
PathApiError, with crossway.* codes. crossway.budget.exhausted is not a rate limit — do not
retry slower. See the API reference.