Crypto-to-Fiat Transactions

Pay a receiver's bank account in fiat from stablecoins in your organisation wallet, from lookups to token approval, quote, accept and settlement

A crypto-to-fiat transaction (type: FIAT) pays one or more bank accounts in fiat. You fund it with tokens from your organisation wallet. Skyline quotes a rate, you accept the quote, and Skyline pulls the tokens from your wallet under an allowance you approved.

Read Transactions first for statuses, preconditions, money fields and errors.

Prerequisites

  • An organisation wallet on the network you pay from, holding the token. See Wallets.
  • A receiver with a bank account. See Receivers and Bank Accounts.
  • An API user with the authorized role, 2FA off, in an ACTIVE organisation.

Step 1: Look Up the Ids

CallWhat you take from it
GET /users/meselectedOrganisation.operationalStatus (must be ACTIVE) and selectedOrganisation.wallets (your organisation wallets).
GET /networks/client-visibleA network whose features includes FIAT. Keep its _id, chainId and type.
GET /tokens?tradableInFiat=true&limit=300The token's isoCode (for example USDC). In its ERC20Address array, the entry whose chainId matches the network gives the token contract address and decimals.
GET /trading-pairsConfirm a pair TOKEN/CURRENCY exists for your token and the bank account's currency, with isHidden false.
GET /receivers/me?paymentType=fiatReceivers that have bank accounts, with their bankAccounts. Keep the bank account _id and its currency.isoCode.
GET /transactions/receiver/walletwallet: the address you approve to spend your tokens (EVM). If this call returns 500, retry after a few seconds.

Step 2: Validate on Your Side

Replicate these server rules before you call the API:

  • Reason, GBP payouts: 1 to 18 characters, matching ^[a-zA-Z0-9\s,.\-]{1,18}$.
  • Reason, other currencies: 2 to 140 characters, matching ^[+/?:,.\s()a-zA-Z0-9]{2,140}$. Hyphens are rejected, even though the error message lists them.
  • Amount: a positive decimal string, in the bank account's currency.
  • USD payouts: each USD payment must meet your organisation's minimum. Below it, validate returns 400 Minimum payment amount is $….
  • Payments: at most 30 per transaction.
  • Wallet address: send EVM addresses in lowercase.

Step 3: Create the Transaction

POST /transactions

{
  "type": "FIAT",
  "token": "USDC",
  "network": "6650a1f2c3d4e5f6a7b8c9d0",
  "walletAddress": "0x4b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c",
  "payments": [
    {
      "type": "FIAT",
      "bankAccount": { "id": "6650b2e3d4f5a6b7c8d9e0f1" },
      "currencyIsoCode": "EUR",
      "amount": "25000.00",
      "reason": "Invoice 1042",
      "bankingFeesOption": "OUR"
    }
  ]
}

Transaction fields:

FieldRequiredDescription
typeYesFIAT.
tokenYesThe token isoCode you pay with.
networkYesThe network _id.
walletAddressYesYour organisation wallet on that network. Lowercase for EVM.
paymentsYes1 to 30 payments.

Payment fields:

FieldRequiredDescription
typeYesFIAT.
bankAccount.idYesThe receiver's bank account _id. The receiver is taken from the bank account.
currencyIsoCodeYesThe bank account's currency, for example EUR.
amountYesThe fiat amount the receiver gets, as a decimal string.
reasonYesPayment reference shown to the receiver. See the rules above.
bankingFeesOptionNoOUR (default), SHA or BEN. See Money Fields.
documentS3KeyNoA supporting document. Upload it with POST /file/upload (multipart, field file) and use the returned fileKey. Use PDF, DOC, DOCX, JPG or PNG under 10 MB.
instructionsNoFree-text instructions for the payment.

The response is the transaction in DRAFT, with _id, payments, and first estimates of tokensToSend, totalCommission and totalBankingFee. The estimates are replaced at submit.

To change anything, send the whole body again with PUT /transactions/:id. The payments array is replaced.

Step 4: Validate

POST /transactions/:id/validate returns 200 with an empty body when the transaction is valid. It checks the payments, the network and the USD minimum. On failure it returns 400 with the reason.

Step 5: Check the Allowance

POST /transactions/:id/check-allowance

{
  "expectedAllowance": "25392",
  "amountAllowed": "0",
  "hasAllowance": false,
  "needsReset": false
}
FieldMeaning
expectedAllowanceThe amount to approve, in token units. When your current allowance is short, it is the estimated total plus a small margin, rounded up to a whole token.
amountAllowedThe current allowance from your wallet to the spender.
hasAllowancetrue when the current allowance is enough. Skip to step 7.
needsResettrue when the token requires the allowance to be set to zero before a new value.

The sending wallet must be an organisation wallet. Otherwise this call returns 400 Wallet with address … not found in organisation.

Step 6: Approve the Token (EVM)

From your organisation wallet, call the standard ERC-20 approve(spender, amount) on the token contract:

  • Token contract: the ERC20Address entry for the network's chainId, from GET /tokens.
  • Spender: the wallet address from GET /transactions/receiver/wallet.
  • Amount: expectedAllowance converted to base units with the token's decimals. You may approve more.

When needsReset is true (for example USDT on Ethereum mainnet): first call approve(spender, 0) and wait for the receipt. Then approve the amount.

Safe wallets: propose the approve as a Safe transaction and collect the required signatures. With a reset, both transactions must execute, in order. Continue only after the last one executes.

Call check-allowance again until hasAllowance is true.

Solana

The API supports crypto-to-fiat from Solana. Instead of an ERC-20 approve, approve an SPL token delegate with approveChecked:

  • Delegate: the Solana network's walletAddress, from GET /networks/client-visible.
  • Amount: expectedAllowance from check-allowance, which also works on Solana. It must cover tokensToSend at accept.

At accept, Skyline checks the delegated amount. If it is too low, accept returns 400 Delegated allowance is below the transaction total.

Step 7: Submit for a Quote

POST /transactions/:id/submit with no body.

The server prices every payment, recomputes all fees and moves the transaction to WAITING_ACCEPTANCE. The response now carries:

  • tokensToSend: the total that will leave your wallet.
  • totalCommission and totalBankingFee: in the token you pay with.
  • expiresAt: when the quote expires.

The quote is valid for about 120 seconds. Use expiresAt, not a local timer. Calling submit again on a WAITING_ACCEPTANCE transaction fetches a fresh quote.

Submit is allowed from DRAFT, WAITING_ACCEPTANCE and FAILED. It returns 403 Wallet not found if walletAddress is not an organisation wallet.

Step 8: Review and Accept

  1. GET /transactions/:id/review-context returns acceptBinding.hash, a fingerprint of the transaction as it stands.
  2. POST /transactions/:id/accept with that hash:
{
  "acceptBindingHash": "9f2c4e7a1b3d5f6e8a0c2e4f6a8b0d2e4f6a8c0e2a4c6e8f0a2b4d6f8a0c2e4f"
}

The hash is 64 lowercase hex characters. It is optional for transactions you create through the API, but send it: if the transaction changed since you read it, accept returns 409 ACCEPT_BINDING_MISMATCH and nothing happens.

Accept checks, in this order: the status is WAITING_ACCEPTANCE, the quote has not expired, your wallet and the destinations have passed screening, your wallet holds at least tokensToSend, and the allowance covers tokensToSend.

On success the transaction moves to PROCESSING. Skyline pulls tokensToSend from your wallet using the allowance and starts the fiat payout. Any payables in the transaction become PAID.

When the Quote Expires

Accepting after expiresAt returns 500 Internal server error, and the transaction moves to FAILED. Most other accept errors also move it to FAILED.

To recover:

  1. Call POST /transactions/:id/submit again. It re-quotes and returns the transaction to WAITING_ACCEPTANCE.
  2. Read the review context again.
  3. Accept before the new expiresAt.

Step 9: Track to Settlement

Poll GET /transactions/:id every 30 to 60 seconds.

StatusMeaning
PROCESSINGSkyline has your tokens and is converting and paying out.
RELEASEDThe fiat payout has been sent to the banking network.
COMPLETEDSettled. Final.
FAILEDRead statusInfo and each payment's status. Submit again once fixed.
REJECTED_BY_SKYLINERejected by Skyline's review. Final.

Endpoint Reference

EndpointMethodPurpose
/transactionsPOSTCreate the transaction
/transactions/:idPUTReplace the transaction body
/transactions/:id/validatePOSTValidate
/transactions/:id/check-allowancePOSTRead the required and current allowance
/transactions/receiver/walletGETThe spender address to approve (EVM)
/transactions/:id/submitPOSTGet a quote
/transactions/:id/review-contextGETRead the accept binding hash
/transactions/:id/acceptPOSTAccept the quote
/transactions/:idGETRead status
/transactions/:idDELETEDelete a draft or unaccepted transaction

On this page