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
authorizedrole, 2FA off, in anACTIVEorganisation.
Step 1: Look Up the Ids
| Call | What you take from it |
|---|---|
GET /users/me | selectedOrganisation.operationalStatus (must be ACTIVE) and selectedOrganisation.wallets (your organisation wallets). |
GET /networks/client-visible | A network whose features includes FIAT. Keep its _id, chainId and type. |
GET /tokens?tradableInFiat=true&limit=300 | The 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-pairs | Confirm a pair TOKEN/CURRENCY exists for your token and the bank account's currency, with isHidden false. |
GET /receivers/me?paymentType=fiat | Receivers that have bank accounts, with their bankAccounts. Keep the bank account _id and its currency.isoCode. |
GET /transactions/receiver/wallet | wallet: 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:
| Field | Required | Description |
|---|---|---|
type | Yes | FIAT. |
token | Yes | The token isoCode you pay with. |
network | Yes | The network _id. |
walletAddress | Yes | Your organisation wallet on that network. Lowercase for EVM. |
payments | Yes | 1 to 30 payments. |
Payment fields:
| Field | Required | Description |
|---|---|---|
type | Yes | FIAT. |
bankAccount.id | Yes | The receiver's bank account _id. The receiver is taken from the bank account. |
currencyIsoCode | Yes | The bank account's currency, for example EUR. |
amount | Yes | The fiat amount the receiver gets, as a decimal string. |
reason | Yes | Payment reference shown to the receiver. See the rules above. |
bankingFeesOption | No | OUR (default), SHA or BEN. See Money Fields. |
documentS3Key | No | A 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. |
instructions | No | Free-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
}| Field | Meaning |
|---|---|
expectedAllowance | The 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. |
amountAllowed | The current allowance from your wallet to the spender. |
hasAllowance | true when the current allowance is enough. Skip to step 7. |
needsReset | true 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
ERC20Addressentry for the network'schainId, fromGET /tokens. - Spender: the
walletaddress fromGET /transactions/receiver/wallet. - Amount:
expectedAllowanceconverted to base units with the token'sdecimals. 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, fromGET /networks/client-visible. - Amount:
expectedAllowancefrom check-allowance, which also works on Solana. It must covertokensToSendat 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.totalCommissionandtotalBankingFee: 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
GET /transactions/:id/review-contextreturnsacceptBinding.hash, a fingerprint of the transaction as it stands.POST /transactions/:id/acceptwith 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:
- Call
POST /transactions/:id/submitagain. It re-quotes and returns the transaction toWAITING_ACCEPTANCE. - Read the review context again.
- Accept before the new
expiresAt.
Step 9: Track to Settlement
Poll GET /transactions/:id every 30 to 60 seconds.
| Status | Meaning |
|---|---|
PROCESSING | Skyline has your tokens and is converting and paying out. |
RELEASED | The fiat payout has been sent to the banking network. |
COMPLETED | Settled. Final. |
FAILED | Read statusInfo and each payment's status. Submit again once fixed. |
REJECTED_BY_SKYLINE | Rejected by Skyline's review. Final. |
Endpoint Reference
| Endpoint | Method | Purpose |
|---|---|---|
| /transactions | POST | Create the transaction |
| /transactions/:id | PUT | Replace the transaction body |
| /transactions/:id/validate | POST | Validate |
| /transactions/:id/check-allowance | POST | Read the required and current allowance |
| /transactions/receiver/wallet | GET | The spender address to approve (EVM) |
| /transactions/:id/submit | POST | Get a quote |
| /transactions/:id/review-context | GET | Read the accept binding hash |
| /transactions/:id/accept | POST | Accept the quote |
| /transactions/:id | GET | Read status |
| /transactions/:id | DELETE | Delete a draft or unaccepted transaction |