Errors

Common errors returned by the Magma API and how to handle them, including auth failures, payment errors, and order cancellation reasons.

How to read errors from the Magma API, and what to do about the ones you're most likely to hit.

Error shape

The Magma API is GraphQL. Errors come back in the standard errors array on the response:

{
  "errors": [
    {
      "message": "Unauthorized token",
      "extensions": { "code": "UNAUTHORIZED" }
    }
  ],
  "data": null
}

The message is the user-facing description. Some errors include a machine-readable code in extensions. Surface message in your UI and key your retry logic off the code when present.

A 200 OK HTTP status doesn't mean the operation succeeded. GraphQL returns 200 even on partial failures. Always check errors before reading data.


Authentication errors

MessageWhen it happensWhat to do
Unauthorized tokenAPI key or session key missing, expired, or revoked.Mint a new key at account.amboss.tech/settings/api-keys. For session keys, the original liquidity.buy response is the only place the key was returned - create a new session.
ForbiddenThe key is valid but doesn't own the resource you're trying to read or mutate (e.g. another account's offer).Check that the key is for the right Amboss account.

See Authentication for the credential matrix.


Validation errors

MessageWhen it happensWhat to do
Invalid IDA UUID-shaped argument (order_id, offer_id, …) is malformed or not a UUID at all.Validate IDs before sending; never embed user input in an ID field without checks.
connection_uri must be in <pubkey>@<host>:<port> formatThe connection_uri you passed to liquidity.buy isn't in the expected shape.Use lncli getinfo (LND) or lightning-cli getinfo (CLN) and pick a public socket. Tor (*.onion:9735) works.
Min amount violatedusd_cents below 500 (the $5 minimum), or outside an offer's min_amount/max_amount.Bump the amount, or query market.liquidity.liquidity_per_usd to size your order against the offer's bounds.
GraphQL argument errorsRequired field missing, wrong type, or enum value not allowed.Compare your variables against the schema. The errors array points at the offending field.

Payment errors (MarketOrderPaymentStatus)

Every order carries a payment_status alongside its status. The non-success values are terminal - once you see one of these, the payment side of the order is dead.

payment_statusMeaningRecovery
PENDING_PAYMENTInvoice issued, waiting for the buyer to pay.No action - keep polling. The Lightning invoice has its own expiry; if you wait past it you'll roll to one of the failure states below.
SUCCESSFUL_PAYMENTBuyer paid the HODL invoice.None - proceed to channel opening.
HODL_INVOICE_TIMEOUTBuyer didn't pay before the seller's HODL invoice expired.The order is dead. Create a new one. Ensure the seller's invoice expiry is at least 48 hours when you provide it.
SELLER_INVOICE_EXPIREDThe seller's invoice expired before payment landed.Same as above - new order, longer expiry.
PAYMENT_FAILEDThe buyer's payment attempt failed at the wallet or routing layer.Check the buyer's logs and retry from the buyer side. The order is still open if the invoice hasn't expired.
PAYMENT_REJECTED_BY_DESTINATIONThe seller's node rejected the HTLC.Almost always a seller-side problem (peer connectivity, channel state). Cancel and re-order from a different offer.
INVALID_PAYMENT_SECRETPreimage mismatch - the HTLC didn't carry the expected secret.Treat as a payment failure. Create a new order.

Order cancellation reasons

Pass an OrderCancellationReason when calling market.order.cancel. The choice is the audit trail for why the order didn't complete:

ReasonUse when
CHANNEL_SIZE_OUT_OF_BOUNDSThe buyer's request fell outside the offer's min_amount / max_amount after pricing.
UNABLE_TO_CONNECT_TO_NODESeller couldn't reach the buyer's node over Lightning to open the channel.
UNABLE_TO_PAYBuyer's wallet couldn't pay the HODL invoice (no routing path, insufficient outbound, etc.).
mutation CancelOrder($input: CancelOrderInput!) {
  market {
    order {
      cancel(input: $input) {
        success
      }
    }
  }
}
{
  "input": {
    "order_id": "ec562479-a4b8-44f4-95b4-150b310832de",
    "cancellation_reason": "UNABLE_TO_CONNECT_TO_NODE"
  }
}

Terminal order statuses (MarketOrderStatus)

Some status values mean the order is permanently failed. Stop polling and surface the error when you see any of these:

StatusCause
SELLER_REJECTEDSeller declined the order.
SELLER_FAILED_TO_REACTSeller didn't accept or reject before timing out.
BUYER_REJECTEDBuyer cancelled before paying.
BUYER_FAILED_TO_PAYBuyer didn't pay the HODL invoice in time.
SELLER_FAILED_TO_OPEN_CHANNELSeller accepted but never broadcast a funding transaction.
SELLER_FAILED_TO_SEND_SWAPSeller couldn't complete the swap leg.
INVALID_CHANNEL_OPENINGA funding transaction was broadcast but didn't match the agreed terms.
ADMIN_CLOSEDManually closed by Amboss support (rare).

See Order Lifecycle for the full state diagram.


Server errors

SymptomLikely causeWhat to do
500 HTTP status with no GraphQL bodyInternal database or processing failure on our side.Retry with exponential backoff. If it persists, open a ticket.
404 from REST (/api/lsp/v1/...) endpointsOrder ID doesn't exist or isn't on the LSP account.Verify the order_id you stored; it must come from a previous create_order on the same type.
Network timeoutYour client gave up before we responded.Retry. The GraphQL endpoint is idempotent for read queries; mutations may not be - use idempotency_key patterns or check the resulting order state before re-issuing.

Retry strategy

  • Read queries (market.*, user.market.orders.get_order, etc.) are safe to retry freely. Exponential backoff, max ~5 attempts.
  • liquidity.buy and liquidity.create_subscription are not idempotent. Don't retry on timeout without first reading user.transactions.transaction_list to confirm whether the previous call already created an order.
  • Seller mutations (accept, reject, add_transaction) only make sense from one status. If a retry hits a state where the operation no longer applies, the API will reject it - re-fetch the order, look at status, and branch accordingly.

Next steps