ErrorsComing soon
The Matchai API error format, every error code with its HTTP status and what to do about it, which requests are safe to retry, and how to use request ids.
Every error from the Matchai API has the same JSON shape. Branch on the stable code, and show the message to the person using your app: it says what happened and what to do next.
The error format
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "price.amount must be greater than 0.",
"param": "price.amount",
"doc_url": "https://matchai.co/docs/developers/errors#parameter_invalid",
"request_id": "req_7Hq2LmZ9xV4c"
}
}Field
Type
Three habits make errors painless:
- Branch on
code, not onmessage. Codes are stable. Messages are written for people, follow the account's language, and can be reworded. - Treat unknown values as unknown, not as a crash. New codes and types can be added without a new version. If you meet a
codeyou do not know, fall back to itstypeand its HTTP status. An unknowntypeis handled likeapi_error. - Log the request id. See request ids.
Status codes
| Status | Type | Meaning | Retry? |
|---|---|---|---|
202 | None | Not an error. A final action is waiting for the account holder's approval. Nothing has happened yet. | No. Wait for approval.resolved. |
400 | invalid_request_error | The request is malformed or a parameter is invalid. | No. Fix the request. |
401 | authentication_error | The key or token is missing, expired, revoked or wrong. | After refreshing an expired token, once. |
402 | energy_error | Not enough Matchai Energy, or the connection reached its Energy limit. | No, not until there is Energy again. |
403 | permission_error | The credential is valid, but cannot do this. | No. |
404 | not_found_error | It does not exist, or this account cannot see it. | No. |
409 | conflict_error | It clashes with the current state. | No. Change the request. |
422 | unprocessable_error | The request is well formed but breaks a product rule. | No. Change the request. |
429 | rate_limit_error | Too many requests for this connection. | Yes, after Retry-After seconds. |
500 | api_error | Something went wrong on Matchai's side. | Yes, with the same Idempotency-Key. |
Request ids
Every response, successful or not, carries a Matchai-Request-Id header. Errors repeat it as error.request_id. Log it with every failed call. When you contact Matchai support, include it: it lets Matchai find exactly what happened on that request.
Matchai-Request-Id: req_7Hq2LmZ9xV4cRetry safely
Retrying is safe when the same request can be repeated without doing the action twice. That is what the Idempotency-Key header is for.
- Send an
Idempotency-Keyon everyPOST. A UUID works. - A retry with the same key within 24 hours returns the first response instead of acting twice. A retry of a final action under Ask me returns the same pending approval.
- Reusing a key with a different body returns
409 idempotency_key_reused. Make a new key for a new request.
Retry network errors and timeouts, 429 and 500. Do not retry 400, 403, 404, 409 or 422 unchanged: they fail the same way again. A 402 keeps failing until the account has Energy again or the account holder raises the connection's limit.
Wait before each retry: honour Retry-After when there is one, and otherwise wait longer each time, with a little randomness so that many clients do not retry together.
import { randomUUID } from 'node:crypto';
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// 1 s, 2 s, 4 s... plus up to a second of jitter.
const backoff = (attempt) => 2 ** (attempt - 1) * 1000 + Math.random() * 1000;
export async function postWithRetry(url, body, { attempts = 4 } = {}) {
const idempotencyKey = randomUUID(); // The same key on every attempt.
for (let attempt = 1; ; attempt += 1) {
let response;
try {
response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MATCHAI_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(body),
});
} catch (networkError) {
if (attempt >= attempts) throw networkError;
await sleep(backoff(attempt));
continue;
}
const retryable = response.status === 429 || response.status >= 500;
if (!retryable || attempt >= attempts) return response;
const retryAfter = Number(response.headers.get('Retry-After'));
await sleep(retryAfter > 0 ? retryAfter * 1000 : backoff(attempt));
}
}Error codes
Each code below is the target of the doc_url that comes with it.
Invalid requests
parameter_invalid
400 · invalid_request_error. A field is missing, has the wrong type, or is out of range. param names the field, with dots for nested fields, such as price.amount. Fix the request: sending it again unchanged fails again.
Other 400 answers, such as a body that is not valid JSON, carry the same type. Treat any 400 as "this request is wrong".
Authentication
token_expired
401 · authentication_error. The access token has expired. With Connect with Matchai, exchange your refresh token for a new access token, then repeat the request once. If the refresh fails, the connection has ended and the professional needs to connect again.
A 401 can also mean that the key or token is missing, mistyped or revoked, or that the connection was ended. Those carry the same type. Ask for a new API key, or send the professional through the connect flow again.
Energy
Matchai Energy is never a number in the API. These errors say only that there is not enough.
insufficient_energy
402 · energy_error. The account does not have enough Matchai Energy for this action. Nothing was done and nothing was spent. The account holder can get more from the Matchai Store, and GET /v1/me reports the state of their Energy (full, comfortable, low or empty). Show the message to the person, and try again once they have topped up.
energy_limit_reached
402 · energy_error. This connection has used the Energy limit the account holder set for it. Nothing was done and nothing was spent. GET /v1/me/connection reports energy_limit_reached: true. The account holder decides whether to raise the limit; your app should not retry in a loop.
Permissions
scope_missing
403 · permission_error. The key or token is valid, but lacks the scope this action needs. The message names it. Create a new API key that includes the scope, or send the professional through the connect flow again with a longer scope list.
not_allowed
403 · permission_error. The account's role does not allow this. The connection acts with the account holder's own permissions and no more, so something they cannot do in Matchai, such as editing a team's work as a member without edit rights, they cannot do through your app either. Tell the person, and do not retry.
Not found
resource_not_found
404 · not_found_error. The thing does not exist, or this account cannot see it. Matchai gives the same answer for both, so a response never confirms that something exists in someone else's account. Check the id, and check that you are using the credential of the account you meant.
Conflicts
contact_exists
409 · conflict_error. The email or phone number you sent already belongs to one of the account's contacts, so nothing was added. The id of the existing contact is in error.param. Update that contact instead, or use POST /v1/enquiries, which matches existing contacts for you.
subdomain_taken
409 · conflict_error. The subdomain you asked for is not available. Subdomains use lowercase letters, digits and hyphens. Choose another and try again.
calendar_conflict
409 · conflict_error. The time overlaps another appointment, and the response lists the appointments that clash. Offer another time. Only send book_over_conflict: true once the account holder has agreed to the clash.
slot_unavailable
409 · conflict_error. The time you asked for was taken in the meantime, for example between showing a visitor the free times and recording their visit request. Fetch the free times again and offer another.
idempotency_key_reused
409 · conflict_error. You sent an Idempotency-Key that Matchai already saw within the last 24 hours, with a different request body. Use a new key for a new request. Reuse a key only to repeat the exact same request.
Refused by a product rule
rooms_exceed_count
422 · unprocessable_error. A listing's bedroom or bathroom count cannot go below the rooms listed on it. Remove the room first, then lower the count. param names the field, such as property.bedrooms.
recipient_suppressed
422 · unprocessable_error. Matchai will not send email to this address, because earlier messages to it bounced or were reported as unwanted. Tell the person, and reach the contact another way.
contact_blocked
422 · unprocessable_error. This contact is blocked, so Matchai refuses to message them. Tell the person, and do not retry.
content_not_allowed
422 · unprocessable_error. The content you sent breaks Matchai's content rules. The message says what to change. Do not retry it unchanged.
handle_not_routable
422 · unprocessable_error. Publishing the Matchai Page failed because the account's username cannot be a web address yet. The account holder needs to choose a different username in Matchai.
Rate limits
rate_limited
429 · rate_limit_error. Too many requests for this connection. The response carries Retry-After, the number of seconds to wait. See rate limits.
Matchai errors
internal_error
500 · api_error. Something went wrong on Matchai's side. It is safe to retry with the same Idempotency-Key, a few times, with growing delays. If it keeps failing, contact Matchai support with the request id.