Operating

Troubleshooting

The errors you will actually see, what each means, and which are worth retrying.

Moove error codes

Every documented code maps to a typed error carrying its retry semantics. The rule, from the API’s own guidance: only 429 and 5xx are worth retrying. Retrying a 4xx cannot succeed, still counts against the rate limit, and a retry loop against a revoked key looks like an attack.

status · codetyped asretrywhat to do
401 UNAUTHENTICATEDMooveAuthErrorneverNo X-API-Key on a route that needs one. Check the environment.
401 INVALID_API_KEYMooveAuthErrorneverUnknown, revoked or deactivated. Create a new key.
401 EXPIRED_API_KEYMooveAuthErrorneverPast its expiry. Create a new key.
403 INSUFFICIENT_API_SCOPEMooveScopeErrorneverScopes are fixed at creation. Recreate the key with the Receive agent.
404 CANNOT_FIND_PAYMENT_LINKMooveNotFoundErrorneverNo link with that id. A real answer, not an auth failure — the public read does not fall back on it.
409 PAYMENT_LINK_ACCOUNT_NOT_READYMooveAccountNotReadyErrorneverSet a handle and a default wallet. See Moove setup.
422 INVALID_PAYMENT_LINK_AMOUNTMooveAmountErrorneverMore decimal places than the settlement token has (six for USDC).
429 RATE_LIMIT_EXCEEDEDMooveRateLimitErrorbackoffNo Retry-After is sent. The client backs off with jitter and halves its rate.
500 CANNOT_CREATE_PAYMENT_LINKMooveServerErrorbackoffBounded retry, then surface.

Server and store errors

symptomcausefix
Bad Request: Server not initializedA transport per request while asking for session ids: the follow-up request lands on a transport that never saw initialize.Serve MCP statelessly — a fresh server and transport per request, no session id. The reference HTTP entrypoint does this.
SQLITE_BUSYSeveral processes running the schema DDL on one file at once.Construction retries briefly. If it persists, one process should own the file.
AmbiguousCommitErrorA Postgres COMMIT failed at the connection level. It may have applied.Deliberately not retried: retrying could spend the same credit twice. Re-read the balance before acting.
MemoryEntitlementStore holds paid entitlements in memoryThe reference store used outside tests.Use SQLite or Postgres, or acknowledgeEphemeral: true if you really mean it.
paidTool references sku …, which the provider does not sellThe price is not in the provider's list.Register the price before the tool.
Too many requests. Wait NsThe per-handle rate limit on the reference server.Separate from the credit balance. It clears on its own.
[tollbooth] underpaidA settlement below the 10% floor. Nothing was granted.Only a person can resolve it. See underpayment.

The loop does not close

what you seelikely cause
The challenge appears, the user pays, the agent answers from its own knowledge instead of retryingThe one failure mode measured. Copy variant v3 guards against it; an untrustworthy-looking checkout domain makes it worse.
The agent reports URL elicitation was canceledA server is emitting the elicitation shape. Tollbooth never does; if you enabled a renderer by allow-list, remove it.
The retry is challenged again, foreverEither the payment has not settled, or the charge expired (60 minutes). Check the charge's status; a new challenge opens a new link.
A retry says the handle is unknownLapsed after 30 days unused, or presented by a different principal than it was bound to. A fresh challenge is issued rather than a hard failure.
Tollbooth

A paywall layer for MCP servers. An agent calls a paid tool, a human pays, the agent retries. Every number on this page is one we measured, at the precision we measured it.