OrbitDocs

Errors

HTTP status codes returned by the Merchant API.

Every error response body has this shape:

{ "error": "Invalid input on merchant_id: merchant_id must be a valid UUID" }
StatusWhenRetry?
400The request failed validation: a field is missing, has the wrong type or the wrong formatNo. Fix the request
401merchant_secret does not match the plan owner's wallet (/trigger-pull)No
404Plan, subscription or merchant not foundNo
500Server misconfiguration, database error, or the Soroban simulation or submission failed. The message is passed throughDepends on the message
502The pull transaction failed on-chain (/trigger-pull)Depends on the cause. See contract errors

Validation errors

Every request is validated before it reaches the database. A 400 reports the first field that failed, in this format:

Invalid input on <field>: <reason>
FieldRuleReason
merchant_id, plan_id, subscription_idMust be a UUID<field> must be a valid UUID
id (path)Must be a UUIDid parameter must be a valid UUID
nameMust not be empty or only spacesname cannot be empty
usdc_amountMust be a number greater than 0usdc_amount must be greater than 0
interval_secondsMust be a whole number greater than 0interval_seconds must be a positive integer
customer_wallet_addressMust be a Stellar public key (G...)must be a valid Stellar Ed25519 public address
merchant_secretMust not be emptymerchant_secret is required

A field that is missing or has the wrong type gets a generic reason instead:

{ "error": "Invalid input on name: Invalid input: expected string, received undefined" }

usdc_amount and interval_seconds also accept numeric strings such as "2592000". A value that is not a number returns expected number, received NaN, and a fractional interval_seconds returns expected int, received number.

/trigger-pull has one more 400 that does not use this format. It is returned when merchant_secret is not a valid Stellar secret key:

{ "error": "Invalid merchant_secret: not a valid Stellar secret key" }

Not found

MessageReturned by
Plan not foundGET /plans/{id}, and POST /subscriptions when plan_id does not exist
Subscription not foundPOST /trigger-pull
Merchant not foundPOST /trigger-pull when the plan's merchant no longer exists

A malformed ID is a 400, not a 404. You only get a 404 for a well-formed UUID that matches nothing.

Server errors

MessageCauseFix
Supabase client not initializedSUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY is not set on the API serverSet both and restart the server
ORBIT_CONTRACT_ID not set in .envORBIT_CONTRACT_ID is not set (/trigger-pull)Set it to the deployed contract ID
Any other messagePassed through from the database or from SorobanSee below for /trigger-pull

Recording the same wallet on the same plan twice also returns a 500, with the database's unique constraint message.

Soroban failures on /trigger-pull

A 500 from /trigger-pull carries the simulation error. Match it against the contract errors:

Message containsAction
Too early to pull fundsRetry after the interval
Vault does not existThe subscriber has not run create_vault
allowance or balance errorMark the subscription past_due and notify the subscriber

A 502 means the transaction was submitted and then failed on-chain. The body includes the txHash, and the API marks the subscription past_due:

{ "error": "Transaction failed on-chain", "txHash": "6f4a8b...19e0" }

A 202 is not an error. The transaction was submitted but not confirmed before the poll timeout, so the outcome is unknown. Poll getTransaction(txHash) before you mark the cycle as paid or retry the pull.

On this page