Errors
One error envelope, eight codes, and the request id to quote when you ask about one.
Every failure answers with the same envelope and the HTTP status that matches it. There is no second error shape anywhere in the API, so one handler covers every call you make.
{
"error": {
"code": "validation_failed",
"message": "The request body does not match the schema for a kanban item.",
"details": [
{
"path": "/data/columns/0/title",
"message": "must be a string"
}
],
"docsUrl": "https://mockflow.com/developers/concepts/errors#validation_failed"
},
"requestId": "req_45d49816999bac40"
}
Fields
error.codeenum<string>requiredA stable machine name. Switch on this, never on the message.
error.messagestringrequiredOne sentence for a person, written to be shown to the caller. It can change between releases, so do not match on it.
error.detailsobject[]Present on validation_failed. One entry per problem, each with the JSON path of the field that was refused.
error.docsUrlstringThe page explaining this code.
error.upgradeUrlstringPresent on plan_limit, where the fix is a plan rather than a payload.
requestIdstringrequiredQuote this when you ask us about a request. It is also returned on success, in the MockFlow-Request-Id header, and it is how we find the call in the log.
Codes
unauthorizedNo token, or a token that is expired, revoked or not a MockFlow token. Check the Authorization: Bearer header.
forbiddenThe token is good but is not allowed to do this: either the key lacks the scope the operation requires, or the account behind it cannot reach that board. The message says which.
not_foundNo such space, board, item, skill or job, or one the caller cannot see. The two are deliberately the same answer, so that probing ids tells you nothing.
validation_failedThe body does not match the schema. details carries a JSON path per problem, so the field that is wrong can be pointed at rather than searched for.
conflictThe request cannot apply to the current state: a member who is already a member, an Idempotency-Key reused with a different body.
plan_limitThe workspace's plan does not allow this, such as one more board or an AI generation with no credits left. upgradeUrl goes to the plan page.
rate_limitedToo many requests in the window. Retry-After says how many seconds to wait.
internalOur fault. Safe to retry with a delay. Quote the requestId if it persists.
Retrying
rate_limited and internal are worth retrying, with a delay that doubles each time. rate_limited says how long to wait in Retry-After. Nothing else is worth retrying: the same request will be refused the same way.
Retrying a write safely means sending an Idempotency-Key with the first attempt, not only with the retry.