Payouts
Create a Payout

Create a payout

Sends stablecoin from your API account to an external address. Knit checks the balance, holds the amount, and submits the transfer.

POST
https://api-prod.useknit.io/api/v1/payouts
Scopepayouts:writeAuthBearer token

Before you call

  1. The API account must exist and be funded. Create it for the token you are paying out, then fund it from the dashboard or let collections credit it. A short balance is rejected with 400, and nothing is held.
  2. Your server IP must be allow-listed. Requests from other addresses are rejected before reaching this endpoint.
  3. The destination address must be valid for the network. Addresses are checked per chain; a malformed or wrong-chain address fails with 400.

Supported networks

NetworkIdentifier
PolygonMATIC_MAINNET
TronTRON_MAINNET
BNB Smart ChainBSC_MAINNET
SolanaSOL_MAINNET
⚠️

Payout availability is narrower than collection availability — a network that accepts collections may not support payouts. Check payoutStatus in GET /api/v1/networks before you build against a chain.

Body

networkstringrequired

One of the payout networks above.

tokenstringrequired

USDT or USDC.

amountnumberrequired

The amount to send, in token units — not fiat. Minimum 0.01.

toAddressstringrequired

The destination address. Validated against the format and checksum rules of the chosen network.

merchantReferencestringrequired

Your own reference for this payout. Must be unique across your payouts — this is what makes retries safe.

⚠️

merchantReference is required, not optional. Generate it from something stable on your side — an invoice ID, a ledger entry ID — so that replaying a request whose outcome you are unsure of is rejected as a duplicate rather than sending the money twice.

Request

cURL
curl -X POST "https://api-prod.useknit.io/api/v1/payouts" \
  -H "Authorization: Bearer $KNIT_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "network": "MATIC_MAINNET",
    "token": "USDT",
    "amount": 25.5,
    "toAddress": "0x56adfcc254ab3b8142a275c1837bcffaff5aa38b",
    "merchantReference": "INV-2045"
  }'

Response

Returns 201. The payout starts at PENDING — it has not settled on-chain yet.

201 Created
{
  "statusCode": 201,
  "message": "Payout created successfully",
  "data": {
    "id": "8e5697e3-8265-455b-984a-0eb40e10b0f9",
    "businessId": "b7b93a40-4e18-4e97-9c5f-8a7a4fd0df92",
    "network": "MATIC_MAINNET",
    "token": "USDT",
    "amount": "25.500000000000000000",
    "toAddress": "0x56adfcc254ab3b8142a275c1837bcffaff5aa38b",
    "merchantReference": "INV-2045",
    "transactionHash": null,
    "status": "PENDING",
    "info": null,
    "createdAt": "2024-08-27T13:44:20.000000Z",
    "updatedAt": "2024-08-27T13:44:20.000000Z"
  },
  "success": true
}

Status lifecycle

StatusMeaning
PENDINGAccepted and queued. The balance has been held
PROCESSINGSubmitted for settlement; awaiting on-chain confirmation
COMPLETEDConfirmed on-chain. transactionHash is populated and a PAYOUT_SUCCESSFUL webhook is delivered
FAILEDCould not be settled. Check info for the reason

A failed payout is not retried automatically at the same merchantReference. To try again, submit a new payout with a new merchantReference once you have resolved the cause.

Track completion with the PAYOUT_SUCCESSFUL webhook rather than polling. If you need to check state on demand — reconciling after an outage, say — use Retrieve a payout or Get payout status.

Errors

StatusCause
400A field failed validation: amount below 0.01, an unsupported network, an invalid toAddress, or a merchantReference you have already used
400Insufficient balance in the API account for that token, or the payout could not be accepted
401Missing or invalid token, IP not allow-listed, or missing payouts:write
404Wallet not found for the specified token — no API account exists for that token yet

Both cases are 400, but the bodies differ. A validation failure carries an errors object naming the offending fields:

400 — validation failed
{
  "message": "The merchant reference has already been taken.",
  "errors": {
    "merchantReference": ["The merchant reference has already been taken."]
  }
}

A rejected-but-valid request carries the standard envelope and no errors:

400 — insufficient balance
{
  "statusCode": 400,
  "message": "Insufficient balance",
  "data": null,
  "success": false
}

Branch on the presence of errors to tell them apart — see Requests & responses.