GitHub
PATH SETTLEMENT

API reference

Receipts

Issuing, reading and verifying settlement receipts — the one format that makes a mobile-money transfer as provable as an on-chain one.

Issuing needs a member credential. Reading and verifying do not, and that is the point: a receipt is only useful if a third party can check it.


POST /receipts

{
  "issued_by": "payee",
  "request": "7fk2m9pq3vx8",
  "via": { "rail": "book", "asset_type": "fiat", "asset_code": "USD" },
  "received": { "value": "5000", "asset_type": "fiat", "asset_code": "USD" },
  "rail_references": [{ "type": "book_entry", "value": "LE-8891-2231" }]
}
{
  "type": "path.receipt",
  "reference": "rcpt_9a41c8f2b731",
  "issuer": "example-member",
  "issued_by": "payee",
  "payer": { "member": "member-b" },
  "payee": { "member": "example-member" },
  "request": "7fk2m9pq3vx8",
  "instruction": null,
  "via": { "rail": "book", "asset_type": "fiat", "asset_code": "USD" },
  "sent": null,
  "received": { "value": "5000", "asset_type": "fiat", "asset_code": "USD" },
  "fx": null,
  "fees": [],
  "rail_references": [{ "type": "book_entry", "value": "LE-8891-2231" }],
  "attestations": [],
  "status": "settled",
  "reason_code": null,
  "supersedes": null,
  "route": null,
  "grammar": 2,
  "protocol_version": "0.2.0",
  "signed_at": "2026-09-09T10:04:12Z",
  "kid": "op_example_2026_01",
  "signature": "…"
}
FieldNotes
issued_bypayer or payee. A payee receipt may state settled. A payer receipt is pending or failed
requestOptional — a spontaneous payment has no request. The public reference from the link, never an internal id
instructionThe public reference of the payment instruction, when one was used
viaThe capability used
sent / receivedAmounts. received is required on a settled payee receipt
rail_referencesTyped by the rail. Required on settled. A pending receipt may omit them — the rail has not named the transaction yet
statuspending or settled. Absent means settled on a payee receipt
supersedesnull at issuance. Set on every transition to the digest of the envelope it replaces
routeAlways null. PIP-0005 is the only shape it may take

The reserved field

route is present on every receipt. PIP-0005 specifies the only shape it may take (legs, providers, eta_seconds). Implementations MUST emit null until a later PIP names who may fill it.


POST /receipts/{reference}/transitions

Issuer's credential. Moves the receipt and returns a new signed envelope.

{ "to": "failed", "reason_code": "rail_unavailable" }
{
  "type": "path.receipt",
  "reference": "rcpt_9a41c8f2b731",
  "status": "failed",
  "reason_code": "rail_unavailable",
  "supersedes": "4c1e77b9…",
  "via": { "rail": "pix", "asset_type": "fiat", "asset_code": "BRL" },
  "rail_references": [],
  "route": null,
  "grammar": 2,
  "protocol_version": "0.2.0",
  "signed_at": "2026-09-21T10:06:44Z",
  "kid": "op_example_2026_01",
  "signature": "…"
}
FieldNotes
tosettled, failed or reversed. pending → settled | failed, settled → reversed, and nothing else — anything else answers path.settlement.transition_invalid
reason_codeRequired on failed. Closed list: rail_unavailable, rail_rejected, destination_unknown, destination_closed, limit_exceeded, compliance_blocked, insufficient_funds, expired
reasonFree text, never normative. A reader switches on reason_code
rail_referencesSupplied here when the rail only names the transaction once it has gone through

The envelope already handed out stays as signed. A transition returns a new statement. supersedes is the digest of the previous envelope. Both remain readable and both remain valid.

Re-stating the state a receipt already holds returns the current envelope unchanged: a duplicate rail callback is ordinary and must not append a second statement.


GET /receipts/{reference}

Public. A supplier, an auditor, a customs officer, the payer's own institution.

Putting this behind a credential would mean the only people who can verify a settlement are the two who already agree about it.

Returns the current envelope — the most recent statement in the chain.


GET /receipts/{reference}/transitions

Public. Every statement ever signed about this settlement, oldest first.

{
  "reference": "rcpt_9a41c8f2b731",
  "status": "failed",
  "transitions": [
    {
      "from": null,
      "to": "pending",
      "reason_code": null,
      "occurred_at": "2026-09-21T10:04:12Z",
      "envelope": { "…": "signed at issuance" }
    },
    {
      "from": "pending",
      "to": "failed",
      "reason_code": "rail_unavailable",
      "occurred_at": "2026-09-21T10:06:44Z",
      "envelope": { "…": "signed at the transition" }
    }
  ]
}

Each envelope verifies on its own. A reader holding only the current one can check a signature and still not know whether the money ever moved, or moved and came back — which is most of what a third party came to find out.


POST /receipts/verify

{
  "envelope": { "type": "path.receipt", "…": "…" },
  "issuer_public_key": "d75a980182b10ab7…"
}
{ "valid": true }
{ "valid": false, "reason": "Signature does not verify against the supplied key" }

You supply the key. This endpoint does not fetch it for you, deliberately: an operator vouching for another operator's key rebuilds the trust hierarchy the protocol avoids, and would mean compromising one operator compromises statements about others.

Fetch it from the issuer's own discovery document. The SDK does both steps:

import { verifyAgainstIssuer } from '@pathprotocol/sdk';

const { valid, reason } = await verifyAgainstIssuer(receipt, 'https://api.other-member.com');

Verifying offline

A receipt verifies with nothing but the envelope and the issuer's public key — no API call at all, once you hold the key.

That is what makes a receipt presentable as a QR code, scanned by someone with no account anywhere, and it is the concrete form of finality is a verifiable state, not a promise.


Reconciliation

RESERVED — the shape below is specified and no endpoint writes it. PATH-SETTLE.Reconciliation is a separate profile for exactly this reason, and this implementation does not declare it. What follows describes where it is going, not what it does.

Three views of one event, which must agree:

{
  "receipt": "rcpt_9a41c8f2b731",
  "observations": [
    { "source": "provider",  "observed": "5000", "observed_at": "2026-09-09T10:04:12Z" },
    { "source": "statement", "observed": "4975", "observed_at": "2026-09-10T02:00:00Z", "discrepancy": "25" }
  ]
}

Discrepancies are recorded, not silently corrected. A system that quietly reconciles differences away is one where nobody notices the fee that was never disclosed, or the rail that rounds in one direction.

On this page