The DiscoFi API
One API for the whole payout: create it, let DiscoFi pick the fastest rail the recipient’s bank accepts, follow live status, and read a double-entry ledger that reconciles itself.
Authentication
Requests authenticate with a secret key in the Authorization header. Keys are managed in the dashboard (Settings → API keys); test keys exercise the sandbox ledger.
Authorization: Bearer sk_live_xxxxxxxxxxxx
Create a payout
One call creates the payout, runs the transaction-monitoring gate, selects the rail, and writes both ledger legs. Rail selection is automatic; pass rail only to constrain it to rtp or fednow.
Request
curl https://api.trydiscofi.com/api/payouts \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"recipientId": "rcp_7f2d",
"amountCents": 240000,
"memo": "Race day crew",
"rail": "rtp"
}'Response
{
"transferId": "tr_91c4",
"chainId": "ch_2210",
"status": "processing",
"fee": {
"amountCents": 120,
"basisPoints": 5
},
"activation": {
"wasFirst": false,
"secondsSinceApproval": null
}
}Recipients
A recipient is created once and paid many times. Creating one triggers the verification invite; the record carries its own status so you can see who is ready to pay.
Request
curl https://api.trydiscofi.com/api/recipients \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jordan",
"lastName": "Avery",
"email": "jordan@example.com",
"type": "individual"
}'Response
{
"recipientId": "rcp_7f2d",
"status": "pending_kyc"
}status moves to verified when the recipient completes verification; businesses pass type: "business" with a company name.
Fund your balance
Balances fund over ACH from a linked bank, or by requesting the money over RTP with a Request for Payment when it needs to arrive before a batch goes out.
Request
curl https://api.trydiscofi.com/api/funding/ach \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"externalBankId": "bnk_31aa",
"amountCents": 5000000,
"memo": "Settlement float"
}'Response
{
"transferId": "tr_44b0",
"chainId": "ch_5921",
"status": "processing"
}POST /api/funding/rfp takes the same shape plus a subledgerId and returns { requestId, expiresAt }: the response is the receipt, and the funds move when the payer’s bank approves, announced by the payment.rfp.response_received event.
Payout lifecycle
Every transfer moves through a small, explicit status set. Ledger postings happen only on settlement, so the books never show money that has not actually moved.
| Status | Meaning |
|---|---|
| processing | Accepted, monitoring passed, rail submission in flight. |
| completed | Settled on the rail; ledger legs posted. |
| pending | Queued (for example, awaiting approval or a retry window). |
| failed | Declined by the rail or the monitoring gate; nothing posted. |
Live status
Status changes stream over server-sent events, so a dashboard or your own system sees each payout clear without polling.
event: payout.status_changed
data: { "transferId": "tr_91c4", "status": "completed" }| Event | Fires when |
|---|---|
| payout.status_changed | A transfer changes status; refresh the activity row. |
| payment.rfp.response_received | The payer’s bank answers a Request for Payment. |
| link.claimed | A pay-by-link recipient collects the money. |
| customer.phase_changed | Your account’s onboarding phase changes. |
Errors & declines
Validation problems return standard HTTP errors. A payment the transaction-monitoring gate declines is its own case: the API answers 422 with the limit that declined it and why, so your system can show the reason instead of a shrug.
HTTP/1.1 422 Unprocessable Entity
{
"approved": false,
"declinedBy": {
"limitId": "txn_cap",
"reason": "Exceeds per-transaction limit"
}
}The gate runs inside the payment call, before any transfer is created, so a declined payment never touches the ledger.
Pay by link
For a recipient you will pay once, generate a secure claim link instead of collecting bank details up front. The recipient opens it and connects their own bank to receive the money; you get the link.claimed event when they do.
Request
curl https://api.trydiscofi.com/api/links \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"amountCents": 75000,
"note": "Local crew, night of Aug 30",
"recipientHint": "Sam R."
}'Response
{
"token": "8FK2QX",
"url": "https://trydiscofi.com/receive/8FK2QX",
"amountCents": 75000
}Links pass the same monitoring gate as payouts when created, expire if unclaimed, and an unclaimed link can be revoked with POST /api/links/:token/cancel.
Activity & balance
Two reads cover most operational questions. Activity is filterable by date range, status and free text; balance splits available, ledger and held funds.
GET /api/customers/me/activity?from=2026-08-01&status=completed GET /api/customers/me/balance
{
"available": 1084723000,
"ledger": 1084723000,
"hold": 0,
"currency": "USD"
}Rails & timing
| Rail | Availability |
|---|---|
| RTP | Typically 60 seconds or less* See Instant Pay disclosure |
| FedNow | Typically 60 seconds or less* See Instant Pay disclosure |
| Visa Direct | Typically 60 seconds or less* See Instant Pay disclosure |
| ACH | Standard ACH timelines* See Instant Pay disclosure |