Withdrawals
A withdrawal moves funds from your TEDR Pay balance to your own payout wallet. You create a request, confirm it with a code emailed to your profile address, and then follow it until the transfer settles. This module never starts a payout on its own.
Withdrawals cannot be triggered with an API key.
All five endpoints on this page accept a dashboard session (JWT) only, and every request has to be confirmed with a code sent by email. A backend cannot complete a withdrawal unattended.
How a withdrawal works
- Create the request with your own
requestId, thecurrencyId, and theamount. The money moves from your available balance into a reserve right away. - TEDR Pay emails a confirmation code to your profile address. Creating a request requires a confirmed email address.
- Confirm the request with that code. A correct code moves it to
PROCESSING. - Cancel it before confirmation if you change your mind. After confirmation, cancelling is no longer allowed.
The network comes from the cryptocurrency you pick, and the address is the main payout wallet of that network. An arbitrary address, network, or merchantId in the request body is not accepted. The request freezes the address, the network, the token symbol, the amounts, and the precision: changing your wallet, your currency, or the fee afterwards does not change a payout that already exists.
The confirmation code is valid for 10 minutes and allows at most five wrong attempts. You can create one request per minute and keep at most five requests awaiting confirmation. There is no resend with a new code — cancel the request and create a new one instead. Confirming twice does not send the money twice.
Cancelling before confirmation, the code expiring, or five wrong codes all move the request to CANCELED and return the reserve to your available balance exactly once.
Retrying a create request
Your client generates requestId once per withdrawal. If the create call times out, repeat it with the same requestId and the same parameters: the response is the request that already exists, with no second reserve and no second email. That repeat does not use up your one-per-minute allowance and does not change the code's expiry. The same requestId with different parameters is rejected with 409. A new withdrawal needs a new UUID.
Amounts and fees
amount is a positive decimal string without an exponent, or the string all. A normal amount is not rounded. With all, TEDR Pay takes the balance that is available at the moment of the reserve; a remainder smaller than the provider's minimum unit stays available. Funds already reserved by another request cannot be withdrawn again.
serviceFee is the TEDR Pay service fee — currently 1% — rounded up to the precision the payout provider supports. Treat it as a formula, not as a fixed number: read serviceFee from the response rather than computing it yourself. providerAmount is amount minus serviceFee, and that is what goes to the payout provider.
The provider takes its own fee out of providerAmount. Its exact fee and the sum that lands on the address are not part of the contract, which is why receivedAmount is null. That null is not a promise that the full providerAmount arrives.
Where to get currencyId
This version of the documentation has no currency reference, so there is no endpoint here that lists the currencies you may withdraw and their ids. The only place a currency id is observable today is the currency.id field on a row of the transaction history, which does not help a merchant with no transactions yet.
The same goes for precision: the only observable hint is amount.currency.displayDecimal in that transaction response. A withdrawal response carries no such field.
Statuses
These are the states a request reports once it has left the waiting-for-confirmation stage — by being confirmed, by being cancelled, by its code expiring, or by running out of attempts. The status it holds while it is still waiting is not published in this contract: read it from the create response rather than assuming one of the three below. A provider-side result — a transfer the provider reports as failed, for instance — is not a status either: the request keeps its own status and flags the situation with reviewRequired. Branch on status, never on providerStatus.
| Status | What it means |
|---|---|
| PROCESSING | The request was confirmed with a correct code and is being sent out. It can no longer be cancelled. |
| COMPLETED | The payout is confirmed on-chain and an external transaction hash is recorded. An internal hash on its own does not complete a withdrawal. |
| CANCELED | Cancelled before confirmation, the code expired, or five wrong codes were entered. The reserve went back to your available balance, exactly once. |
A failed payout does not mean the funds were returned.
When the provider reports the transfer as failed, that is not a confirmation that the money is back on your TEDR Pay balance: under the current payout contract this flow has no automatic refund. Refunds are not supported here at all — no refund transaction id is stored or returned.
A failed transfer can still be reprocessed by the provider, and reviewRequired is set while that is open. Automatic attempts stop altogether on a mismatched address, amount, or identifier, on a payout that disappears after it was already observed, and on a result this flow does not support. In either case the request stays PROCESSING with reviewRequired set to true, and its reserve needs an operational reconciliation against the transaction hashes. There is no manual financial-adjustment API in this module.
Response fields
Every endpoint on this page that returns a withdrawal returns these fields. The confirmation code itself, the provider signature, and the provider secret are never included.
| Field | Type | Description |
|---|---|---|
| id | string (UUID) | The request identifier; used as :id in the confirm, cancel, and get paths. |
| requestId | string (UUID) | The idempotency key you sent on create. |
| currencyId | string (UUID) | The cryptocurrency you asked to withdraw. |
| blockchain | string | The network the payout goes out on, taken from that currency. |
| token | string | The token symbol on that network. |
| walletAddress | string | The main payout wallet for that network, frozen into the request when it was created. |
| status | string | The state of the request — see Statuses above. |
| providerStatus | string | null | The payout provider’s own status. The set of values it can hold is not published here, so do not branch on it — branch on status. |
| reviewRequired | boolean | True when the request needs an operational reconciliation against the transaction hashes — either because the provider reported a failure that may still be reprocessed, or because automatic attempts stopped. |
| failReason | string | null | Why the payout failed, once it has. |
| internalTxId | string | null | The provider-side transfer identifier. |
| externalTxId | string | null | The on-chain transaction hash. A withdrawal only reaches COMPLETED with one. |
| amount | string | The whole reserved amount. |
| serviceFee | string | The TEDR Pay service fee — see Amounts and fees. |
| providerAmount | string | amount minus serviceFee: the sum handed to the payout provider. |
| receivedAmount | string | null | Null. The provider’s exact fee and the sum that lands on the address are outside the contract, so this stays empty. |
| Creation, confirmation and processing timestamps, and the confirmation code’s expiry | datetime | The response carries all four. Their exact field names are not published here — read them from the response body rather than hard-coding them. |
Create a withdrawal request
/merchants/me/withdrawalsAuth: Session JWT only — how to get a JWT.
Reserves the amount on your balance and emails a confirmation code. The request is not sent anywhere until you confirm it.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| requestId | string (UUID) | Required | An idempotency key you generate once per withdrawal. Retry a timed-out create with the same value and the same parameters. | |
| currencyId | string (UUID) | Required | The cryptocurrency to withdraw. The network and the payout address follow from it. | |
| amount | string | Required | A positive decimal string without an exponent, or all for the available balance. |
/merchants/me/withdrawalscurl -X POST 'https://dev-app.tedr.com/api/merchants/me/withdrawals' \
-H 'Authorization: Bearer YOUR_SESSION_JWT' \
-H 'Content-Type: application/json' \
-d '{"requestId": "e2c1f5a8-1d3b-4a61-9a0c-7f5b2c84d90e", "currencyId": "8a4d2f7c-5b21-4f0e-9c63-1ab8e2d47f95", "amount": "100"}'
const response = await fetch('https://dev-app.tedr.com/api/merchants/me/withdrawals', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_SESSION_JWT',
'Content-Type': 'application/json',
},
body: JSON.stringify({
requestId: 'e2c1f5a8-1d3b-4a61-9a0c-7f5b2c84d90e',
currencyId: '8a4d2f7c-5b21-4f0e-9c63-1ab8e2d47f95',
amount: '100',
}),
});
const withdrawal = await response.json();
import requests
response = requests.post(
'https://dev-app.tedr.com/api/merchants/me/withdrawals',
headers={'Authorization': 'Bearer YOUR_SESSION_JWT'},
json={
'requestId': 'e2c1f5a8-1d3b-4a61-9a0c-7f5b2c84d90e',
'currencyId': '8a4d2f7c-5b21-4f0e-9c63-1ab8e2d47f95',
'amount': '100',
},
)
withdrawal = response.json()
Response: 201
| Code | Status | When |
|---|---|---|
| — | 409 | The same requestId was reused with different parameters. Repeating it with identical parameters returns the existing request instead. |
The body carries the withdrawal request, with the fields listed above under Response fields. The status a request holds before it is confirmed is not published here — read it from the response instead of hard-coding it. Creating a request needs a confirmed email address on your profile, and is limited to one new request per minute and five requests awaiting confirmation at a time.
Confirm a withdrawal request
/merchants/me/withdrawals/:id/confirmAuth: Session JWT only — how to get a JWT.
Confirms the request with the code emailed to your profile address and hands it over for processing.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string (UUID) | Required | The request's id, from the create or list response. |
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| code | string | Required | The code from the confirmation email. |
/merchants/me/withdrawals/:id/confirmcurl -X POST 'https://dev-app.tedr.com/api/merchants/me/withdrawals/3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1/confirm' \
-H 'Authorization: Bearer YOUR_SESSION_JWT' \
-H 'Content-Type: application/json' \
-d '{"code": "012345"}'
const response = await fetch(
'https://dev-app.tedr.com/api/merchants/me/withdrawals/3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1/confirm',
{
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_SESSION_JWT',
'Content-Type': 'application/json',
},
body: JSON.stringify({ code: '012345' }),
},
);
const body = await response.json();
import requests
response = requests.post(
'https://dev-app.tedr.com/api/merchants/me/withdrawals/3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1/confirm',
headers={'Authorization': 'Bearer YOUR_SESSION_JWT'},
json={'code': '012345'},
)
body = response.json()
Response: 200
A correct code moves the request to PROCESSING. The code is valid for 10 minutes and allows at most five wrong attempts; the fifth wrong attempt, or the code expiring, cancels the request and returns the reserved funds. There is no resend with a new code — cancel the request and create a new one. Confirming again does not send the money twice. The shape of the response body is not specified here — read the updated request with Get a withdrawal if you need its fields.
Cancel a withdrawal request
/merchants/me/withdrawals/:id/cancelAuth: Session JWT only — how to get a JWT.
Cancels a request that has not been confirmed yet and releases its reserve. Takes no request body.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string (UUID) | Required | The request's id, from the create or list response. |
/merchants/me/withdrawals/:id/cancelcurl -X POST 'https://dev-app.tedr.com/api/merchants/me/withdrawals/3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1/cancel' \
-H 'Authorization: Bearer YOUR_SESSION_JWT'
await fetch('https://dev-app.tedr.com/api/merchants/me/withdrawals/3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1/cancel', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_SESSION_JWT' },
});
import requests
requests.post(
'https://dev-app.tedr.com/api/merchants/me/withdrawals/3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1/cancel',
headers={'Authorization': 'Bearer YOUR_SESSION_JWT'},
)
Response: 200
Only before confirmation: once the request is confirmed, cancelling is no longer allowed. Cancelling moves the request to CANCELED and returns the reserve to your available balance exactly once. The shape of the response body is not specified here — read the request with Get a withdrawal if you need its fields.
Get a withdrawal request
/merchants/me/withdrawals/:idAuth: Session JWT only — how to get a JWT.
Returns the current state of one of your own requests.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string (UUID) | Required | The request's id, from the create or list response. |
/merchants/me/withdrawals/:idcurl 'https://dev-app.tedr.com/api/merchants/me/withdrawals/3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1' \
-H 'Authorization: Bearer YOUR_SESSION_JWT'
const response = await fetch(
'https://dev-app.tedr.com/api/merchants/me/withdrawals/3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1',
{
headers: { Authorization: 'Bearer YOUR_SESSION_JWT' },
},
);
const withdrawal = await response.json();
import requests
response = requests.get(
'https://dev-app.tedr.com/api/merchants/me/withdrawals/3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1',
headers={'Authorization': 'Bearer YOUR_SESSION_JWT'},
)
withdrawal = response.json()
Response: 200
{
"id": "3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1",
"requestId": "e2c1f5a8-1d3b-4a61-9a0c-7f5b2c84d90e",
"currencyId": "8a4d2f7c-5b21-4f0e-9c63-1ab8e2d47f95",
"blockchain": "<network of the selected currency>",
"token": "<token symbol on that network>",
"walletAddress": "<your main payout wallet on that network>",
"status": "PROCESSING",
"providerStatus": null,
"reviewRequired": false,
"failReason": null,
"internalTxId": "<provider transfer id>",
"externalTxId": "<on-chain hash, once the transfer is confirmed>",
"amount": "100",
"serviceFee": "1",
"providerAmount": "99",
"receivedAmount": null
}
Values in angle brackets depend on your currency and payout wallet. The body also carries the creation, confirmation and processing timestamps and the expiry of the confirmation code.
List withdrawal requests
/merchants/me/withdrawalsAuth: Session JWT only — how to get a JWT.
Returns your withdrawal requests, page by page.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| limit | integer | Optional | 20 | How many requests to return, from 1 to 100. |
| offset | integer | Optional | 0 | How many requests to skip before the page starts. |
/merchants/me/withdrawalscurl 'https://dev-app.tedr.com/api/merchants/me/withdrawals?limit=20&offset=0' \
-H 'Authorization: Bearer YOUR_SESSION_JWT'
const response = await fetch(
'https://dev-app.tedr.com/api/merchants/me/withdrawals?limit=20&offset=0',
{
headers: { Authorization: 'Bearer YOUR_SESSION_JWT' },
},
);
const { items, total } = await response.json();
import requests
response = requests.get(
'https://dev-app.tedr.com/api/merchants/me/withdrawals',
headers={'Authorization': 'Bearer YOUR_SESSION_JWT'},
params={'limit': 20, 'offset': 0},
)
page = response.json()
Response: 200
{
"items": [
{
"id": "3f0f6c61-9e8a-4f44-9a3f-6d2a5f5ee2b1",
"requestId": "e2c1f5a8-1d3b-4a61-9a0c-7f5b2c84d90e",
"currencyId": "8a4d2f7c-5b21-4f0e-9c63-1ab8e2d47f95",
"blockchain": "<network of the selected currency>",
"token": "<token symbol on that network>",
"walletAddress": "<your main payout wallet on that network>",
"status": "PROCESSING",
"providerStatus": null,
"reviewRequired": false,
"failReason": null,
"internalTxId": "<provider transfer id>",
"externalTxId": "<on-chain hash, once the transfer is confirmed>",
"amount": "100",
"serviceFee": "1",
"providerAmount": "99",
"receivedAmount": null
}
],
"total": 1,
"limit": 20,
"offset": 0
}
The envelope of this list is { items, total, limit, offset } — the other list endpoints of the API use different envelopes, so read this one on its own terms.
