P2P trading and all other Bitania services are temporarily disabled until further notice. Only swaps are available for now.
REST API Endpoint

Resolve a swap

POST /swap/emergency

Also available as POST /swap/resolve. Takes a decision on a swap that cannot simply execute. Two situations need you:

  • RATE_CHANGED — a fixed-rate deposit differs from the ordered amount by more than the 0.5 % tolerance (issue.type is LESS or MORE). The status response carries proposed: what the user would get for the actually deposited amount at the current market rate, with the fixed-rate fee unchanged. Choose EXCHANGE or REFUND.
  • REFUNDING with issue.needsRefundAddress = true — the deposit will be sent back (below the pair minimum, more than we can pay out, or the user chose a refund) but no refund address is known. Send REFUND with an address.

Choices:

  • EXCHANGE — continue the swap: the deposited amount is exchanged at the current market rate (proposed.rate) and paid out. The swap moves to EXCHANGING immediately. Only on RATE_CHANGED.
  • REFUND — send the deposit back to the refund address, minus the refund fee (1 % of the deposit; the on-chain fee of the refund is paid by Bitania). The swap is REFUNDING until the refund is sent, then REFUNDED with refund.tx. Pass address if none was given at creation; an address that is already set is not changed through the API.

Deposits below the pair minimum or larger than our available balance never wait for you: they are refunded automatically as soon as a refund address is known, which is why we recommend always passing refundAddress on /swap/create. Partners with auto_execute_on_mismatch enabled never see RATE_CHANGED either: their fixed-rate mismatches execute at the current rate automatically (slippage on the swap shows the difference).

Manual review: a swap in FAILED is being looked at by Bitania operations (for example a payout that failed on-chain); EXCHANGE and REFUND are refused with an "under manual review" message and the swap resolves to DONE or REFUNDED without your action. Keep polling and show the user a neutral "processing" state.

Authentication: HMAC-signed (v2) request — X-API-KEY, X-API-SIGN, X-API-TIMESTAMP, X-API-NONCE headers. See the Swap Aggregator Guide.

Authorization (dual-mode): same as /swap/status — the swap must belong to your key, or you include its token.

Request

Request Body (required)

Content-Type: application/json

{
  id: string, required — Swap id
  choice: string, required, enum: EXCHANGE|REFUND — Action choice
  address: string — Refund address (required for REFUND when no refund address is set; validated for the source chain)
  token: string — Optional per-swap token from /swap/create; authorizes the call from a different API key
}

Response 200

The updated swap: EXCHANGING after EXCHANGE, REFUNDING (issue.choice REFUND, issue.needsRefundAddress false) after REFUND

{
  code: integer, e.g. 0 — 0 = success, 1 = error
  msg: string, e.g. "" — Error message (empty on success)
  data: object — Swap order (returned by create, status, advance and emergency)
    {
      id: string, e.g. "k7m3px9qw2" — Swap id: 10 characters (dry-run swaps: "DR" + 8). Use it for status and emergency calls.
      token: string, e.g. "uYx9j2k3Lm4nOp5qRs6tUw" — Per-swap access token (22 characters). Lets another API key, or a shared link, read the status. Treat it as a secret.
      type: string, enum: float|fixed — Rate type
      status: string, enum: NEW|PENDING|CONFIRMING|RATE_CHANGED|EXCHANGING|SENDING|REFUNDING|DONE|EXPIRED|REFUNDED|FAILED — Current status (see Get swap status)
      time: object — UNIX timestamps in seconds
        {
          reg: integer — Swap creation time
          start: integer|null — When the deposit was first detected
          finish: integer|null — When the swap ended (DONE or REFUNDED)
          update: integer — Last change
          expiration: integer — End of the payment window
          left: integer|null — Seconds left in the payment window; null once a deposit was seen or the swap ended
        }
      from: object — What the user sends
        {
          code: string, e.g. "TRX" — Currency code
          coin: string, e.g. "TRX" — Coin ticker
          network: string, e.g. "TRX" — Chain
          name: string, e.g. "Tron" — Display name
          amount: string, e.g. "1000" — Amount the user is expected to send (string, full precision)
          amountDeposited: string, e.g. "0" — Sum of the confirmed deposits so far
          address: string — Deposit address to show the user
          reqConfirmations: integer, e.g. 1 — Confirmations we wait for before crediting a deposit
          tx: object|null — The deposit transaction, once detected
            {
              id: string — Transaction hash
              amount: string — Deposited amount
              fee: string|null — Network fee (not known for deposits)
              feeCurrency: string|null — Currency of the fee
              timeReg: integer — When we first saw the transaction
              timeBlock: integer|null — When it reached the required confirmations
              confirmations: integer — Current confirmation count
            }
        }
      to: object — What the user receives
        {
          code, coin, network, name — as in from
          amount: string, e.g. "339.15" — Amount we pay out: the quote until the deposit is confirmed, the final amount afterwards
          address: string — Destination address
          tx: object|null — The payout transaction, once sent: id, amount, fee (the network fee we paid), feeCurrency, timeReg, confirmations (refreshed while you poll, until the coin's own threshold), txKey (Monero only: the transaction key that proves the payment to the recipient's address; null elsewhere)
        }
      refund: object
        {
          address: string|null — Refund address (from the create call, or from an emergency REFUND call)
          tx: object|null — The refund transaction, once sent
        }
      issue: object — Why the swap is not simply executing, and what was decided
        {
          type: string|null, enum: LESS|MORE|BELOW_MINIMUM|NO_LIQUIDITY — LESS / MORE: a fixed-rate deposit under / over the ordered amount (RATE_CHANGED). BELOW_MINIMUM: the deposit is under the pair minimum. NO_LIQUIDITY: the payout is more than we hold. Both of the latter are refunded automatically.
          reason: string|null — Human-readable explanation while the swap is RATE_CHANGED, REFUNDING or FAILED
          needsRefundAddress: boolean — true when we want to refund but no refund address is known: send one with POST /swap/emergency (choice REFUND)
          choice: string, enum: NONE|EXCHANGE|REFUND — What was decided: EXCHANGE (executed at the new rate), REFUND (being / been refunded)
        }
      rate: string, e.g. "0.34" — Rate at creation: to-coin per 1 from-coin
      rateActual: string|null — Rate actually applied; set when the swap is DONE
      fee: object — Our service fee: { percent: number, e.g. 0.25 — amount: string, in the to-coin }
      slippage: number|null — Applied rate vs quoted rate, in percent; set when the deposit is credited (0 for a fixed-rate swap honoured as quoted)
      proposed: object|null — While RATE_CHANGED: the re-quote the user accepts with EXCHANGE: { rate, amount }
      dryrun: boolean — true for simulated swaps
      url: string|null — Public status page of the swap on bitania.com (null for dry-run swaps)
    }
}

Response 400

Validation or business error, for example: EXCHANGE is only possible while the swap is RATE_CHANGED, REFUND is only possible while the swap is RATE_CHANGED, or REFUNDING without a refund address, A refund address is required, The refund address is not a valid TRX address, A refund address is already set for this swap; contact support to change it, This swap is under manual review; Bitania support resolves it, Dry-run swaps never need a decision

{
  code: integer, e.g. 1 — Error code (1)
  msg: string, e.g. "Missing required fields: from, to, amount" — Error message
  data: null
}

Response 401

Authentication failed: unknown or disabled key, IP not whitelisted (Invalid credentials), or a bad signature, stale timestamp or reused nonce (Invalid signature)

{
  code: integer, e.g. 1
  msg: string, e.g. "Invalid credentials"
  data: null
}

Response 429

Rate limit of this endpoint exhausted; wait retry_after seconds (also sent as the Retry-After header)

{
  code: integer, e.g. 1
  msg: string, e.g. "Rate limit exceeded. Please retry after 12 seconds."
  data: null
  retry_after: integer, e.g. 12
}