What changed
New endpoint names, direct charges, and why nothing you built has broken.
We renamed the API around two words — collect and disburse — and added direct charges plus a set of endpoints that tell you what we support.
You don't have to change anything. Every previous path still answers, behaves identically, and returns the same fields. Move when it suits you.
The names
The old names did not describe what they did — POST /v1/payments created a checkout
page rather than taking a payment. The new ones say which direction money moves:
| Before | Now |
|---|---|
POST /v1/payments | POST /v1/collect/checkout |
GET /v1/payments/{reference} | GET /v1/collect/{reference} |
POST /v1/payouts | POST /v1/disburse |
GET /v1/payouts/{reference} | GET /v1/disburse/{reference} |
GET /v1/payout-limits/{currency} | GET /v1/account/disburse-limits/{currency} |
GET /v1/channels | GET /v1/account/providers |
POST /v1/webhooks | PUT /v1/webhook |
Money in is collect, money out is disburse. Every endpoint, field and error uses those two words and no others.
What's new
Charge a number directly — POST /v1/collect/charge
pushes a prompt straight to a phone, so your customer never leaves your checkout.
Endpoints that answer "what do you support?" — instead of hardcoding what we told you in an email:
/v1/countries— where we operate, and the number format there/v1/providers— every network we support/v1/limits— amount bounds per currency/v1/account/providers— what your account may use
Disburse limits answer one question
GET /v1/account/disburse-limits/{currency} now
returns enabled — true only when a payout would actually be accepted.
It always answered 200 OK, including when payouts were switched off, and the only hint
was configured, which described whether an admin had set you a ceiling and said nothing
about whether the capability was on. An integration that checked the status code, or
checked configured, could read a green light while payouts were off platform-wide.
- if (!limits.configured) throw new Error('Payouts not enabled');
+ if (!limits.enabled) throw new Error(limits.message);configured still returns and still means what it meant, so nothing breaks. Alongside
enabled you now get platformEnabled, blockedBy (PLATFORM, ACCOUNT_DISABLED,
CURRENCY_CLOSED, or null) and a message you can show a person.
You don't need a payout limit provisioned. Your per-payout ceiling is the currency's own maximum — the same one that bounds a collection — unless support has agreed a different one with you. There is nothing to request before your first payout.
Errors have a shape now
Previously an error carried message, error and statusCode, and validation failures
returned message as an array — two shapes for the same problem, neither with
anything stable to branch on.
Now every error is:
{ "code": "INVALID_REQUEST", "message": "...", "param": "amount", "requestId": "..." }Branch on code. See errors.
This is the one change worth a moment. If you were matching on error text, switch
to code — wording will keep improving, and code won't move.
Statuses are untouched
PENDING, PROCESSING, SUCCESS, FAILED — same values, same spelling, same casing.
Nothing to change.
Webhooks
Endpoints registered before this change keep receiving the old names —
payment.succeeded, payment.failed, payout.sent, payout.paid, payout.failed.
New ones get collect.succeeded, collect.failed, disburse.sent,
disburse.succeeded and disburse.failed.
Your handler keeps working with no change. When you want the new names, deploy a handler
that reads them and then re-register with "events": "v2" — see
webhooks.
Rate limits can be raised
The default is 30/minute and 600/hour per key. If you're driving volume, we raise it per key — write to tech@tchokopay.com. Takes effect immediately, no key rotation.
If you're integrating today
Build against the new names. The old ones keep working and we will give you plenty of notice before anything changes, but the new names are what to write against.