Errors & retries
A MADATA API error always carries two things: a code, which your program reads, and a message, which a human reads. Knowing which of the two to look at saves a lot of time.
01The XML-RPC shape
A failed call returns a Fault: an integer code and a string.
Most libraries raise it as an exception.
import xmlrpc.client
try:
models.execute_kw(DB, uid, KEY, 'account.move', 'unlink', [[1042]])
except xmlrpc.client.Fault as f:
print(f.faultCode) # 2
print(f.faultString) # "warning -- UserError\n\nYou cannot delete a
# posted accounting document."
| Code | Meaning | What to do with it |
|---|---|---|
1 | Application error. The faultString holds the traceback. | Log and alert: this is unexpected. |
2 | Business warning: a rule, a missing value, an incompatible state. | The message is meant for a human. Show it as is. |
3 | Access denied: invalid or expired credentials. | Replay authenticate once; if it fails again, the key is revoked. |
4 | Insufficient right on the model or the record. | Do not retry: this is about permissions, not the network. |
02The JSON-RPC shape
In JSON, an error arrives with HTTP 200 and an
error key. Checking the HTTP status is therefore not enough:
check for error.
{"jsonrpc": "2.0", "id": null,
"error": {
"code": 200,
"message": "Madata Server Error",
"data": {
"name": "madata.exceptions.UserError",
"message": "You cannot delete a posted accounting document.",
"arguments": ["You cannot delete a posted accounting document."],
"debug": "Traceback (most recent call last): ...",
"context": {}
}}}
| Field | What it is for |
|---|---|
error.code | 200 application error · 100 session expired · 404 unknown path. |
error.data.message | The message to show. That one, not error.message. |
error.data.name | The error type, as a machine identifier. Use it to decide what to do. |
error.data.arguments | The parts of the message, separated. Handy for custom rendering. |
error.data.debug | The technical traceback. Log it, never show it to a user. |
UserError and
ValidationError: a business rule, the message is usable as is.
AccessError: a permission is missing. AccessDenied:
credentials refused. MissingError: the record no longer
exists.
03Common cases
| Symptom | Most frequent cause |
|---|---|
authenticate returns false | Database other than prod_<workspace>, or revoked key, or a password used while two factor authentication is on. |
| "Access denied" on a model | The key holder does not have the relevant application in their permissions. |
| An empty list while the screen shows rows | Different company scope: pass allowed_company_ids. |
| A row "missing" after creation | It is archived. Read again with context: {active_test: False}. |
| "Required field missing" | Ask the workspace what it expects: default_get and fields_get. |
| The call is cut off after a few minutes | The per call duration limit. Paginate or split the work. |
503 with Retry-After | The workspace is being updated. Wait for the given delay and resume. |
04Retrying properly
Three families, three behaviours.
| Family | Behaviour |
|---|---|
Transient: 503, dropped network, timeout. | Retry, backing off and honouring Retry-After. Past five attempts, alert. |
Business: UserError, ValidationError. | Never retry as is: the same request gives the same answer. Fix the data, or surface the message. |
Permissions: AccessError, AccessDenied. | Stop. A retry loop on a revoked key only fills the logs. |
A create cut
off by a network timeout may well have gone through on the workspace side.
Before replaying a write, look for it: see
do not create twice.
05What the response says, and what it does not
Every API response names the platform MADATA: the generic
error label is "Madata Server Error", session expiry is
"Madata Session Expired", error types are prefixed
madata.exceptions., and common.about() returns
"Madata. See https://madata.africa". No response names another
vendor.
The server_version field is "17.0": that is the
version of the call protocol, the one client libraries
compare to know how to talk. It does not move from one update to the next and
names no brand. The version of your workspace is shown in its settings.
If an API response ever shows you anything but MADATA, that is a defect: report it to support with the exact call and the response you got.
06Logging on the right side
On the workspace side, every write leaves a trace attached to the user
holding the key: that is what lets support reconstruct a sequence. On the
program side, keep for every failed call the model, the method, the arguments
and the full data.debug or faultString. These are
exactly the elements you will be asked for, and exactly the ones nobody has
when they were not recorded.