Error codes
Every refusal is JSON:
{ "error": "human-readable explanation", "code": "MACHINE_READABLE" }Switch on code, not on error — the wording may improve, the code will not change meaning.
This page is not every code. It explains the ones a person or an integration is likely to meet and has to act on. Each route’s own refusals, with their codes, are in the API reference under that route, and there are many more of those than are listed here.
Some refusals carry extra fields alongside those two: issues on a validation failure, template on
a save conflict, balance when credits run out, missing when a bundle names something that does
not exist.
The API takes any client. Its hostnames do not apply Cloudflare’s browser check, so a script
with its HTTP library’s default User-Agent — Python’s urllib included — reaches the API like any
other. A 403 whose body is plain text, error code: 1010, is not from the API: it is that check,
on one of Sloose’s other hostnames (the app or the website), which keep it. Call the API’s own
hostname, or send a User-Agent of your own if the request has to go through another of them.
Limit and status reasons
Section titled “Limit and status reasons”These are a closed set. The app switches on them, and a code it has never seen is a dead end for whoever is reading it, so adding one is a deliberate act that ships with a release of the app.
| Code | Means | What to do |
|---|---|---|
LIMIT_RUNS |
The period’s import runs are used up. Dry runs still work. | Wait for the period to roll over, or upgrade. |
LIMIT_RUNS_CONCURRENT |
As many imports as the organisation’s plan runs at once are already running or starting in the organisation, and runsConcurrent says how many that is. Counted from the imports holding their claim, so one whose page was closed stops counting when its hold lapses, about two minutes later. Dry runs are not counted toward this limit and are never refused by it — though a dry run still holds its own import while it runs, so that import cannot start another run meanwhile (IMPORT_RUNNING). |
Wait for one of the other imports to finish, then run this one again. A plan with a higher number would allow more at once. |
LIMIT_ROWS_PER_RUN |
Before the run: the import set is bigger than the plan allows in one run, and rowsPerRun says the most. During the run: every row is admitted before any of its records is written, and the write of the row that would cross the line is refused 403 RUN_STOPPED, whose stop carries this reason with rowsPerRun, rowsWrittenTotal and stoppedBy. Nothing reaches the CRM for that row. The line is the lower of the rows the run declared and the plan’s limit as it stands, so a plan changed under a running import applies to the rest of it, never below what is already written. The rows written before the stop are counted and kept, and the run is closed there. A progress report after it answers with the same stopped; the rowsWritten a report sends is accepted from older clients and does not meter anything. stoppedBy says which line bound: plan, or declaration when the run tried to write more rows than it declared for itself. |
Before the run, and for stoppedBy: "plan": split the file, narrow the row range on Run, or upgrade. For stoppedBy: "declaration" the run tried to write more rows than it declared for itself, which upgrading cannot change — tell support. |
LIMIT_CONNECTORS |
One more connection would exceed the plan’s number of connections. A Zoho sandbox counts like any other. | Disconnect one, or upgrade. |
LIMIT_ROWS |
Server-run imports only: the run would exceed the monthly row allowance. Not returned yet: the month’s rows are counted and never enforced, on any plan. | Nothing today. (Some plans price rows past the allowance per thousand; nothing in Sloose switches that on today — see Plans and limits.) |
LIMIT_SCHEDULES |
The plan’s schedule limit is reached. Not returned yet: schedules are stored but not run, and the limit arrives with scheduled imports. | Remove a schedule, or upgrade. |
LIMIT_CRM_USERS |
Active CRM users exceed the plan’s band. | Nothing to do unless you want the numbers to agree: Usage & credits shows the CRM’s size beside the plan’s, and a larger plan is sized for it. This is a warning, never a block — a fifty-seat CRM where two people import is still that CRM’s size. |
LIMIT_FILES_BYTES |
The files the organisation stores at once would pass what the plan holds (1 GB on Free, 10 on Team, 100 on Business, 500 on Enterprise). Only a NEW upload is refused; nothing stored is deleted early. Returned when an upload is reserved (POST …/imports/{id}/files), with usedBytes and maxBytes. |
Delete files you no longer need, wait for them to expire (7 days after last use on Free, 30 on the others), or upgrade. |
LIMIT_FILE_SIZE |
One file is larger than a file may be: the lower of 5 GB and the maximum an administrator set. Returned when an upload is reserved, with maxFileBytes. |
Split it, or ask an administrator to raise the organisation’s per-file maximum (never above 5 GB). |
FEATURE_SHARED_TEMPLATES |
The plan cannot share templates across the organisation. | Keep the template private, or upgrade. |
FEATURE_HELPER_LIBRARIES |
The plan cannot use helper libraries. | Upgrade. |
FEATURE_SERVER_RUN |
The plan cannot run imports server-side. Not returned yet: nothing runs an import on the server, on any plan. | Nothing: an import runs in your browser on every plan. |
FEATURE_MCP |
The plan cannot use the MCP server. | Upgrade. |
FEATURE_ROLLBACK |
The plan cannot roll a run back. Not returned yet: Sloose has no way to undo a run, on any plan. | Nothing: a dry run before the real one is the check there is. |
FEATURE_SCHEDULED_IMPORTS |
The plan cannot schedule imports. Not returned yet, for the same reason as LIMIT_SCHEDULES. |
Upgrade. |
CREDITS_EXHAUSTED |
No AI credits left. The response carries balance. |
Buy a credit pack, or wait for the period to roll over. |
STATUS_SUSPENDED |
The subscription is suspended, and this was a real run: its authorisation is refused. Reads still work, templates, imports and configuration can still be edited, and a dry run still runs. (An AI request is refused 402 SUSPENDED.) |
Settle the account. |
STATUS_PAST_DUE |
A payment is overdue (a card payment failed, or an invoice passed its due date) and the grace period is running. Never a refusal: it is among the warnings of the organisation’s entitlements while the grace period runs. |
Pay the invoice or update the card before the grace period ends. |
An AI request refused for credits (CREDITS_EXHAUSTED) or for a suspended organisation
(SUSPENDED) is a 402 and carries the credit balance. A card declined on a change of plan is a
402 too, CARD_DECLINED, and carries none. STATUS_SUSPENDED is how a run is refused, a 403.
Authentication and authorisation
Section titled “Authentication and authorisation”| Code | Status | Means |
|---|---|---|
AUTH_REQUIRED |
401 | No bearer, or one that does not verify. |
TOKEN_REVOKED |
401 | The API token was revoked. |
TOKEN_EXPIRED |
401 | The API token passed its expiry. |
MEMBERSHIP_GONE |
401 / 403 | An API token whose creator is no longer a member of the organisation (401), or a request whose membership went while it was being answered (403). A person removed from an organisation is not signed out: their session is left in no organisation, and is refused 403 NO_ORG there. |
ROLE_REQUIRED |
403 | Your role is too low. The response names the role needed. |
ORG_MISMATCH |
403 | The bearer was issued for a different organisation. |
NOT_YOURS |
403 | The import is somebody else’s and your role cannot do this to it: an operator changes, runs and deletes only an import they started, and a builder deletes only their own. Ask whoever started it, or an administrator. See the roles. |
SESSION_REQUIRED |
403 | An API token was used on a session-only route. See Authentication. |
ADMIN_REQUIRED |
403 | A backoffice route (/staff, any …/admin) called with anything but a staff member’s session or the operator token: an organisation’s API token, say, or a customer’s session, however senior. |
ROLE_CHANGED |
403 | The role dropped below what this route needs, since the token was issued. Refresh the session. |
CONSENT_REVOKED |
401 | An MCP token whose grant has been revoked: by the person who allowed it (Your account → Apps with access), or by an administrator (Settings → Apps with access). Sign in through the client again. |
CURRENT_SESSION |
409 | DELETE /auth/account/sessions/{id} named the session asking. Sign out instead. |
SECOND_FACTOR_REQUIRED |
403 | A staff route, and this session has no recent second factor. enrolled: false means none is set up (Your account); enrolled: true means pass it at /second-factor. Also answered when a factor is enrolled and a session that has not passed one recently tries to add or remove a factor, or to delete the account. |
STAFF_VIEW |
403 | The session is a staff member’s view of an organisation, and this acts on the person rather than the organisation. Their own account and second factor are refused to a view, reading and writing alike; and any view — read-only or write-capable — makes none of their writes outside the organisation: making an organisation, accepting an invitation, asking another organisation for access, ending one of their sessions, moving the session. |
IMPERSONATION_READ_ONLY |
403 | The session is a read-only staff view of an organisation, and this request is not a read. Refreshing the view and ending it are the two exceptions. |
IMPERSONATION_REFUSED |
403 | The session is a write-capable staff view of an organisation (a read-only one is refused IMPERSONATION_READ_ONLY before this), and this would grant a credential or access that outlasts the view, or end the organisation: minting an API token, inviting somebody, approving an access request, raising a member’s role, or closing the organisation — or would spend or change the organisation’s money: a Checkout, the Customer Portal, the one-click top-up’s settings, overage, or a trial. Staff close an organisation, grant credits and start a trial from the backoffice, which records who did it. Connecting an app to the organisation in the member’s name is refused the same way, as OAuth’s access_denied. |
STEP_REQUIRED |
403 | One of Better Auth’s own two-factor endpoints was called directly; they are reached through /auth/second-factor. |
SECOND_FACTOR_LOCKED |
429 | Five wrong codes; the step is locked until until. |
INVALID_CODE |
401 / 400 | The authenticator or backup code was not right: 401 at the step (/auth/second-factor/verify), 400 for the first code of an enrolment, where the session is fine and the page shows it inline. |
TOTP_NOT_ENABLED |
400 | No authenticator app is enrolled for this person. |
Validation and conflicts
Section titled “Validation and conflicts”| Code | Status | Means |
|---|---|---|
INVALID_BODY |
400 | The body did not match the schema. issues carries the list. |
BAD_INPUT |
400 | The same thing, on the AI routes, which use their own code. |
UNKNOWN_MODULES |
422 | A module reference is not in the latest discovery. missing names them. A bundle’s template enabling a module the connection has, but that the push leaves unoffered, is not refused: it is stored and named in the push’s warnings. |
KEY_CLASH |
422 | Another module already uses that key or singular name. |
NAMESPACE_MISMATCH |
422 | A helper manifest’s namespace does not match the path it was written to. |
EXPIRY_IN_PAST |
422 | A token expiry that has already passed. |
NOT_UTF8 |
422 | A delimited file sent to an import is not valid all the way through in the encoding its start decided (its byte-order mark, or UTF-8 in its first 64 KiB). Its later accented characters would have been replaced; nothing was stored. Save it as UTF-8 (in Excel, “CSV UTF-8”) and send it again. A file that is not UTF-8 from the start is not refused: it is read as the legacy encoding its bytes look like, and the upload’s read says which and that it was a guess. |
TOO_MANY_FILES |
400 | A records write names more files than one request may: up to 250 for one record, its subform rows’ files included, and up to 20 between the records of a batch. most says the limit and files how many the request named. written: false: nothing was admitted or sent. Sloose’s own import sends a batch’s records in more requests to stay under it, so a run meets it only for one record that names more than 250. Send fewer records in each request; a single record past 250 files cannot be written in one request. |
RATE_LIMITED |
429 | Too many of something in a while, with retryAfterSeconds. On POST /orgs it is organisations made: one person may make ten in 24 hours and twenty in 30 days, closed ones counted, and error says which and about how long to wait. |
INVALID_TEMPLATE |
422 | A template configuration that does not validate. issues carries the list. |
TEMPLATE_CONFLICT |
409 | Somebody else saved the template first. The response includes their version so you can show the difference rather than overwrite it. |
TEMPLATE_MANAGED |
409 | A configuration repository owns the template, so the change belongs there: the next push would overwrite it. sourceRef names the file. Its visibility can still be changed. |
TEMPLATE_MOVED |
409 | The template has moved on since the import copied it, so updating it from the import would overwrite a version nobody here has seen. version, updatedBy and updatedAt say where it is now; take the latest, or save the import as a new template. |
FROM_FUTURE |
409 | The document was written by a newer build than this server. Stored shapes are migrated forward, never backward. |
RUN_RETIRED |
409 | A retry named a run that is past its plan’s history, removed or being removed. What that run made can no longer be read in full, so a retry could write some of it again. A name that finds no run of the organisation gets the same answer, since a removed run leaves nothing behind to tell it from one that never existed. Run the import again instead. |
Connections
Section titled “Connections”Every request about one connection names it — ?connector=<id> on a route, connectorId in a
body, connection on an MCP tool — even in an organisation that holds only one. Nothing picks one for a
request that does not say, because an organisation may hold a client’s CRM each, and writing to the wrong
one is the mistake that cannot be taken back.
| Code | Status | Means |
|---|---|---|
CONNECTION_REQUIRED |
400 | The request named no connection. connections lists the ones this bearer may name, each with its id, label and kind — empty when the organisation holds none yet. |
CONNECTION_NOT_FOUND |
404 | The connection named is not one this bearer may use: another organisation’s, another person’s own, or none at all. The answer is the same for each. |
NO_CONNECTION |
409 | A run was asked for an import that names no connection yet. Choose one on its Target. |
RUN_CONNECTOR_MISMATCH |
409 | A record write named a different connection from the one its run was authorised for. |
DESTINATION_ALLOWANCE_SPENT |
403 | A run that writes was asked for while the connection’s CRM is refusing calls because its API credits for the last 24 hours are spent. The run was not started: nothing was claimed, counted or recorded. error is a sentence to show as it is, and spentAt is when Sloose first heard. A dry run is not refused, and nor is any run while Sloose cannot tell (it gives up asking after two seconds and authorises). Not one of the plan’s reasons above: no plan changes it. GET /orgs/{org}/connectors/{id}/credits says whether it still holds (spent), and POST …/credits/check asks the CRM first. Zoho names no time at which credits return. |
DAILY_ALLOWANCE_SPENT |
429 | A call to the CRM was not sent, because the CRM has said its API credits for the last 24 hours are spent. While that holds Sloose refuses the CRM’s calls itself and lets one a minute through to ask again; the first one Zoho does as asked ends it. On a write the body says written: false. There is no Retry-After. The call that first met the limit is relayed as Zoho’s own 429, with zoho.quotaSpent: true. |
MODULE_NOT_FOUND |
404 | A records route named a module the connection does not have, as its last discovery read it (in any letter case: accounts is Accounts). Only a connection’s own modules are asked of the CRM. A module added to the CRM since the connection was made is read with Read again on the connection’s Overview, under Connections. |
Members
Section titled “Members”Changing who may do what has its own rules — see People and roles.
| Code | Status | Means |
|---|---|---|
SELF_CHANGE |
403 | You tried to raise your own role, or change your own membership. Lowering your own role is allowed. |
OUTRANKED |
403 | You cannot change somebody above you, or grant a role above your own. |
LAST_OWNER |
409 | The change would leave the organisation with no owner — leaving it included. Make somebody else an owner first, or close the organisation. |
MEMBER_CHANGED |
409 | The member’s role changed between your reading it and your request. Read it again and decide afresh. (Removed in between, they are MEMBER_NOT_FOUND.) |
MEMBER_NOT_FOUND |
404 | That person is not in this organisation. |
Your account
Section titled “Your account”| Code | Status | Means |
|---|---|---|
CONFIRMATION_MISMATCH |
400 | Deleting the account, and confirm was not the account’s own email. |
SOLE_OWNER |
409 | Deleting the account of the only owner of an open organisation. orgs names each: close it, or make someone else an owner, first. |
LAST_STAFF |
409 | The last staff member cannot be deleted, nor have the role taken away. |
STAFF_CANNOT_BE_DELETED |
409 | Staff deleting a staff member’s account. Take the role away first. |
Discovery
Section titled “Discovery”| Code | Status | Means |
|---|---|---|
DISCOVERY_FAILED |
502 | The CRM could not be read. The message says why, bounded to 500 characters. |
| Code | Status | Means |
|---|---|---|
AI_REFUSED |
422 | The model declined. category says why. |
AI_BAD_OUTPUT |
502 | The model answered, but not with something usable. |
AI_NOT_CONFIGURED |
503 | AI is not configured on this deployment. Unconfigured paths say so rather than failing obscurely. |
AI_UNAVAILABLE |
503 | The model could not be asked just now: the AI service is out of capacity, rate limited or down. Nothing was changed and nothing was charged; send the same request again shortly. Over MCP, the tool answers it as an error with the same code and words. |
ORG_NOT_CONFIGURED |
409 | The organisation has no module registry yet. Run discovery and enable a module first. |
SESSION_FORBIDDEN |
403 | That chat session belongs to somebody else. |
SESSION_NOT_FOUND |
404 | sessionId names no chat session in this organisation — including one the history window has deleted. Leave sessionId out to start a new one. |
Not found
Section titled “Not found”404 with a *_NOT_FOUND code — TEMPLATE_NOT_FOUND, MODULE_NOT_FOUND, FIELD_NOT_FOUND,
LIBRARY_NOT_FOUND, SCHEDULE_NOT_FOUND, TOKEN_NOT_FOUND, ORG_NOT_FOUND.
A template you cannot see answers 404 rather than 403, deliberately: whether a private template
exists is not something another member should be able to probe for.