SDK reference
URIs
parsePayload, parseUri, signUri, verifyUri — HTTPS first, path: as an alias, and the parsing rule that keeps a QR reader from becoming an attack surface.
buildUri(input)
import { buildUri } from '@pathprotocol/sdk';
buildUri({
kind: 'request',
reference: '7fk2m9pq3vx8',
host: 'pay.example.com',
amount: '5000',
currency: 'XOF',
});
// 'https://pay.example.com/p/request/7fk2m9pq3vx8?amount=5000¤cy=XOF'
buildUri({
kind: 'request',
reference: '7fk2m9pq3vx8',
amount: '5000',
currency: 'XOF',
extra: { op: 'burok' },
});
// 'path:request/7fk2m9pq3vx8?amount=5000¤cy=XOF&op=burok'| Field | Notes |
|---|---|
kind | One of the ten interop kinds |
reference | Opaque [A-Za-z0-9_.-]+ — no colon, no path: prefix |
host | When set, emit HTTPS. When omitted, emit the path: alias |
amount, currency, memo | Optional |
accepts | Serialised compactly as asset:chain:rail |
extra | Additional parameters. op= is rejected when host is set |
Keep a signed static pay URI under roughly 300 characters on the HTTPS form. That is the
budget that matters for a printed sticker.
parsePayload(input)
The reading algorithm's step 2. Dispatches https: then path:.
import { parsePayload } from '@pathprotocol/sdk';
const parsed = parsePayload(
'https://pay.example.com/p/request/7fk2m9pq3vx8?amount=5000¤cy=XOF',
);
parsed.form; // 'https'
parsed.host; // 'pay.example.com'
parsed.kind; // 'request'
parsed.reference; // '7fk2m9pq3vx8'
parsed.params; // { amount: '5000', currency: 'XOF' }- Two segments after
/p/, kind in the allowlist → PATH URI. - One segment after
/p/→not_path(inherited payment link). - Two segments, unknown kind →
unknown_kind(never treated as a code). op=on HTTPS →malformed.http:→not_path.
parseUri(input)
Strict path: parser. Anchored at both ends. Does not accept HTTPS — that is parsePayload.
import { parseUri } from '@pathprotocol/sdk';
const parsed = parseUri('path:request/7fk2m9pq3vx8?amount=5000¤cy=XOF');
parsed.kind; // 'request'
parsed.reference; // '7fk2m9pq3vx8'
parsed.form; // 'path'
parsed.params; // { amount: '5000', currency: 'XOF' }It is anchored at both ends
parseUri('harmless text path:pay/abc123 trailing');
// throws InteropParseError { reason: 'not_path' }This is the security-relevant behaviour. A reader that scans for a pattern anywhere in the input and takes the first match can be redirected by appending a crafted suffix to an innocuous string.
Unknown kinds are named
parseUri('path:teleport/abc123');
// throws InteropParseError { reason: 'unknown_kind' }Unknown parameters survive
const parsed = parseUri('path:pay/abc123?amount=10&future_field=keepme');
parsed.params.future_field; // 'keepme'Errors
import { InteropParseError } from '@pathprotocol/sdk';
try {
parsePayload(scanned);
} catch (err) {
if (err instanceof InteropParseError) {
switch (err.reason) {
case 'not_path': // not a PATH URI — try another format
case 'unknown_kind': // PATH, but newer than this reader
case 'malformed': // PATH, and broken
}
}
}not_path is the common case in a general-purpose scanner and should not surface as an error to the
user.
signUri(input, privateKey) / verifyUri(uri, publicKey)
Ed25519, sig in base64url. Canonicalisation: kind + reference + every parameter except
sig and op, keys sorted. Host is not covered, so the same signature verifies on HTTPS and on
the path: alias.
import { signUri, verifyUri } from '@pathprotocol/sdk';
const uri = signUri(
{
kind: 'pay',
reference: '4a91c2f7e8d3',
host: 'pay.example.com',
kid: 'op_example_2026_01',
params: { amount: '5000', currency: 'XOF' },
},
privateKeyHex,
);
verifyUri(uri, publicKeyHex); // trueJSON envelopes still sign with hex (verifyEnvelope). Do not mix the two encodings.
issuerOrigin(uri) / pillarFor(kind)
import { issuerOrigin, pillarFor } from '@pathprotocol/sdk';
issuerOrigin(parsed); // 'https://pay.example.com' or null on the path: alias
pillarFor('request'); // 'REQUEST'
pillarFor('pay'); // 'ADDRESS'
pillarFor('subscribe'); // 'CONNECT'There is no resolveIssuerBase. On HTTPS the host is the routing index. On the alias, the caller
already knows the holder — or looks up a declared hosts[] entry. A host not on that list is not
that issuer.
subscribe returning CONNECT is the one to notice. It looks exactly like a payment link and
creates a standing permission. Handle it as a payment request and you ship without revocation
and without counters.