Get started
Errors
Every response, success or failure, has the same envelope. Check success first: when it is false, message says what went wrong in one line and errors lists each problem. The HTTP status tells you what kind of problem it is.
The error body
{
"success": false,
"message": "Invoice not found",
"errors": [
{
"code": "http.404",
"message": "Invoice not found",
"statusCode": 404
}
],
"data": null,
"requestId": "1b1f55e8-b214-48a9-92f1-d6aad8f7a312",
"timestamp": "2026-10-17T09:30:00.000Z"
}| Field | What it holds |
|---|---|
success | Always false on an error. |
message | A short summary you can show or log. |
errors[] | One entry per problem: a code and a message, sometimes a field or extra detail such as required. |
data | Always null on an error. |
requestId | The id of this call. Quote it when you contact support. You can also send your own in an X-Request-Id header. |
Status codes
| Status | Meaning | What to do |
|---|---|---|
| 200 OK | The read or update worked. | — |
| 201 Created | A POST created something. | — |
| 400 Bad Request | A field is missing or invalid. | Fix the request; errors[] names each field. |
| 401 Unauthorized | The access token is missing, wrong or expired. | Refresh the token and retry once. |
| 403 Forbidden | Your API key lacks the permission the endpoint needs, or the endpoint isn't open to API keys. | Add the permission in required to your key under VezmoPay → Developers. |
| 404 Not Found | The object doesn't exist in your account. With a test key, an object your account has in live mode answers 404 with the message No such invoice: a similar object exists in live mode, but a test mode key was used to make this request. (and the reverse with a live key). | Check the id, or switch keys. |
| 409 Conflict | The request clashes with the object's state, for example the payment is already paid. | Reconcile the object; don't retry under a new key. |
| 422 Unprocessable | An Idempotency-Key was reused with a different body. | Use a new key for a new request. |
| 429 Too Many Requests | You went over a rate limit. | Wait, then retry with backoff. |
| 500 and above | Something failed on our side. The message is always generic. | Retry with backoff. If it keeps failing, send support the requestId. |
Validation errors
A 400 from a bad body lists every failing field, so you can fix them in one pass. Fields the endpoint doesn't know are ignored, not rejected, so check the spelling of optional fields against the endpoint's page.
{
"success": false,
"message": "Bad Request",
"errors": [
{ "code": "validation.name must be a string", "message": "name must be a string" },
{ "code": "validation.email must be an email", "message": "email must be an email" }
],
"data": null,
"requestId": "…",
"timestamp": "…"
}Permission errors
A 403 names the permissions the endpoint needs in required. Add them to your key under VezmoPay → Developers; tokens you create afterwards carry them.
{
"success": false,
"message": "Insufficient API permissions",
"errors": [
{
"code": "http.403",
"message": "Insufficient API permissions",
"required": ["subscription.read"]
}
],
"data": null,
"requestId": "…",
"timestamp": "…"
}Handling errors
- Retry only 429 and 5xx answers, with exponential backoff (for example 1 s, 2 s, 4 s, then stop).
- Never retry a 4xx unchanged; it will fail the same way.
- Before retrying a call that moves money, make it safe to repeat. See Idempotency.
const res = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } });
const body = await res.json();
if (!body.success) {
// Show body.message; log body.requestId so support can find the call.
console.error(res.status, body.message, body.requestId, body.errors);
if (res.status === 401) await refreshAccessToken(); // then retry once
if (res.status === 429 || res.status >= 500) await retryWithBackoff();
}