Docs

Agents

spaceturtle

Callers

Errors and reject codes

What each refusal means, and the one thing to do about it.

A refusal is returned, never thrown. send() resolves with fulfilled: false for anything the network refused: a bad voucher, a route that does not exist, a carriage the route will not accept. Everything the client throws happened before the packet went out, or on chain. A thrown error means you have a configuration or a wallet to fix; a returned refusal means the network answered, and the answer was no.

const answer = await client.send('g.toon.store', { body: 'hello' });
if (!answer.fulfilled) {
  answer.code;             // 'F03', 'F01', 'PAYMENT_REQUIRED', 'TRANSPORT_REQUIRED', …
  answer.message;          // diagnostic text. Never branch on it; branch on the code.
  answer.refusedBy;        // 'destination' | 'path' | 'edge'
  answer.accumulatedCost;  // what the path costs, when the connector stated it
}

Who refused

refusedByWhat it means
destinationThe reject came back sealed. Only the terminating connector holds the secret to seal one, so this is proof the destination itself said no.
pathThe reject arrived in plaintext. That identifies nobody: a hop short of the termination refused, or the termination could not open the wrap.
edgeThe connector you are attached to refused before routing at all: a greeting, or a wrong carriage.

Sealed identifies the destination; unsealed identifies nobody. Do not read a plaintext reject as an accusation against any particular node.

Reject codes

CodeMeansYou do
F00Bad request: the envelope's target escaped the route's handler path (an absolute path, a .. segment, a scheme or an authority).Fix target. It is resolved beneath the handler; '' and '/' both mean the handler itself.
F01A malformed voucher, one that does not verify, or a claim of a retired kind.If it persists, run claim-state and compare the connector's figure with channel status. A figure ahead of yours usually means a lost channel store.
F02No route to that destination, including "no route over this carriage".Check the destination against describe(). If it carries requiredTransport, send over that carriage.
F03The voucher did not advance the connector's watermark by the route's price, or its cumulative amount exceeded the channel's deposit, or the declared amount exceeded the price on a forwarded route.Underpayment: accumulatedCost is the route's price; pay that. Over deposit: top up and send again. Forwarded route: send exactly the price.
F06No voucher was attached, on the BTP carriage. This is the greeting: the route's terms instead of the work.Open or fund a channel, then send with a voucher.
T00The connector hit an internal error. Temporary.Retry with backoff.
T01A peer on the path is unreachable. Temporary.Retry with backoff. The packet is fine; the path currently is not.
T04The packet exceeds the cap a connector will forward to one peer in a single packet. Never carried, never split.The reject's message states the cap. Send a smaller packet; this is the only way the cap is published.
T05Rate limited.Back off. On a probe this is the per-channel probe limit.
R00The packet expired before it arrived.Retry. Persistent expiry means a slow path or a clock well out of step.

accumulatedCost rides beside the packet, only on a REJECT. It is one figure: every hop's flat fee plus the price of the route that terminated, never a breakdown. It is 0 when nothing was traversed and nothing terminated.

Client-side codes

Two code values do not come from the wire. They are refusals the client names when the connector answered with a greeting rather than a packet:

CodeMeansYou do
PAYMENT_REQUIREDThe connector answered the route's terms instead of the work: HTTP 402, or F06 over BTP. answer.terms carries the price and the x402 channels it accepts.Open or fund a channel on one of the offered chains.
TRANSPORT_REQUIREDThe route does not accept the carriage you used. answer.terms.requiredTransport names the one it does.Resend with transport: 'btp', or leave auto against a node that publishes the pin on the route.

HTTP statuses

An ILP-level outcome is always HTTP 200, whether the packet fulfilled or was rejected. A non-2xx is a transport-level failure and never carries a packet body.

StatusMeansYou do
400The body was not a decodable PREPARE, or was oversized.A bug in whatever formed the packet, or an encoder that is not TOON's dialect.
401An ILP-Peer-Id was presented and did not authenticate.Drop the peer id, or fix the credential. Anonymous payers need neither: the voucher identifies you.
402Unpaid request to a priced route, or a request over a carriage the route refuses. The body is the terms document.Pay, or switch carriage.
403A probe from a sender with no channel the connector recognizes, or over the probe rate limit.Open and use a channel first. Probing is free traversal, gated on having paid before.
413The body exceeded the 2 MiB limit.Send less. There is no configuration knob for this on the connector.

Errors the client throws

All of them extend ToonClientError, which carries a stable code string. Branch on the class or on code, never on the message. ConfigError (CONFIG) is the one you will meet first: a missing required field, no facilitator for a Base network with no default, or no x402 channel offered on the chain asked for. NetworkError is an edge that could not be asked; SponsorRefusedError carries a Solana node's own reason for refusing an open. The full table on GitHub lists every class.

Written from toon-client/docs/errors.md.