Explain validation problems, and propose fixes
const url = 'https://api.sloose.com/ai/explain?connector=con_8f2a&keep=0199a1b2-7c3d-7e4f-8a9b-0c1d2e3f4a5b%3A3';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"problems":[{"figures":{"additionalProperty":1},"id":"example","severity":"error","where":"example","module":"example","apiName":"example","code":"example","message":"example","rows":[1],"column":"example","fieldLabel":"example","fieldType":"example","badRows":[1],"sampleBad":[{"row":1,"value":"example","column":"example","columns":["example"],"columnsCut":true}],"sourceId":"example","sourceUnknown":true,"failure":"refused","issueType":"example","place":{"module":"","apiName":"example"}}],"columns":["example"],"sampleRows":[],"sampleRowNumbers":[1],"enabledModules":["example"],"mappings":{"additionalProperty":{"additionalProperty":"example"}},"sourceId":"example","sourceLabel":"example","otherSources":[{"id":"example","label":"example","columns":["example"],"mappings":{"additionalProperty":{"additionalProperty":"example"}}}],"question":"example","run":{"id":"example","plan":{"files":[{"id":"example","rows":1,"modules":["example"]}],"more":1}},"conversation":{"importId":"example","place":{"kind":"source","source":"example"},"question":"example","file":{"digest":"example","reading":"example"},"version":1,"last":{"turnId":"example","waiting":[1],"declined":[1]}}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url 'https://api.sloose.com/ai/explain?connector=con_8f2a&keep=0199a1b2-7c3d-7e4f-8a9b-0c1d2e3f4a5b%3A3' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "problems": [ { "figures": { "additionalProperty": 1 }, "id": "example", "severity": "error", "where": "example", "module": "example", "apiName": "example", "code": "example", "message": "example", "rows": [ 1 ], "column": "example", "fieldLabel": "example", "fieldType": "example", "badRows": [ 1 ], "sampleBad": [ { "row": 1, "value": "example", "column": "example", "columns": [ "example" ], "columnsCut": true } ], "sourceId": "example", "sourceUnknown": true, "failure": "refused", "issueType": "example", "place": { "module": "", "apiName": "example" } } ], "columns": [ "example" ], "sampleRows": [], "sampleRowNumbers": [ 1 ], "enabledModules": [ "example" ], "mappings": { "additionalProperty": { "additionalProperty": "example" } }, "sourceId": "example", "sourceLabel": "example", "otherSources": [ { "id": "example", "label": "example", "columns": [ "example" ], "mappings": { "additionalProperty": { "additionalProperty": "example" } } } ], "question": "example", "run": { "id": "example", "plan": { "files": [ { "id": "example", "rows": 1, "modules": [ "example" ] } ], "more": 1 } }, "conversation": { "importId": "example", "place": { "kind": "source", "source": "example" }, "question": "example", "file": { "digest": "example", "reading": "example" }, "version": 1, "last": { "turnId": "example", "waiting": [ 1 ], "declined": [ 1 ] } } }'Takes the problems the engine reported and explains them in the organisation’s own vocabulary, proposing a fix where there is one the widget can apply.
This is the moment AI earns its place: not at the start, where the free matcher is already good, but here, where somebody is stuck. Proposals are returned for a person to accept — nothing is applied.
Send a problem’s values apart from its words (sampleBad, with fieldLabel, fieldType and badRows where they apply). The model is shown each value as the organisation’s AI data setting allows. A message with no sampleBad beside it — a failed row’s error, in the destination’s or the import’s own words — is taken to quote values: under masked and deny it is withheld, and the problem is explained from its code, module and apiName; under allow it is withheld too for a field excluded from AI, or for one filled straight from a column named like one (read from mappings). A value map is proposed only for a field whose values the model was shown as they are, and maps only the values it was shown.
On an import of several files, describe every file a problem is about and say which each problem is about. columns, sampleRows and mappings are the file on screen, named by sourceId and sourceLabel; otherSources are the others — every one a problem is about, and up to 5 more as context — their columns and mappings, never their rows; and each problem names its file by sourceId, or none when it is about the import as a whole. A mapping, a match rule or a row rule (a module’s Which rows, gate, or its update rule, guard) is checked against the columns of the file its problems are about and names it (sourceId) — apply it to that file. A fix whose problems are about two files, or about a file the request does not describe, is not offered; a value map is the import’s and names no file. A problem whose file cannot be named — taken out of the import since the run, or recorded without it by an older build — says so with sourceUnknown, on an import of one file too: it is explained, and no mapping, match rule or row rule is offered for it, since there is no file to make one in. The model knows each file by its name, or by its place (“file 2”) when it has none, never by its id. Every described file’s columns and mapping are in the prompt, and the call is priced by the prompt it sends, so an import of several files can cost a band more. Name the file on an import of one file too (sourceId): a fix then names it, as a fix on an import of several does.
A conversation kept with the import. Name it with conversation: the import, and the place the question was asked from — one kind of failure on a run ({ kind: 'failed', source, module, failure, code, field }, kept with the failure and not the run, so the same failure on the next run carries on the same conversation), one of Validate’s problems ({ kind: 'issue', source, type, module, apiName }), Validate’s own ({ kind: 'checks' }), or Run’s own ({ kind: 'run' }) — the last two naming no file, and asked about no problem if there is none. At Run’s own place, name the run (run), which is required there: a question at Run’s place with no run is refused 400 BAD_INPUT before anything is charged. That narrows the route as #1071 shipped it, when such a question was charged, kept and answered about nothing; nothing outside this app called it then, so no caller depends on the looser form. Name it as the last one by its id, whose outcome — dry or real, how it ended, and the rows it wrote, made, updated and failed, overall — is read from the server’s record of it, never from the request (404 RUN_NOT_FOUND for a run that is not this import’s, 409 RUN_NOT_ENDED for one that has not ended and 409 RUN_RETIRED for one the history’s purge has begun to take), with the run as it stands now beside it (after a dry run, which writes nothing and so records no row made or updated, what the real run would do); or before any — or after one the page cannot name (ran), of which nothing is said — the run as it stands alone: each file in the order the run takes them, with the rows it would send and its modules in the order it writes them, at most 50 files and the rest counted, a file the import does not hold and a module the connection does not have left out. At a failure’s or a problem’s place every problem must be what the place names — its file, module and field (a problem’s own place where it sends one), and the failure’s kind (failure) and code, or the problem’s kind (issueType) — and on an import that holds several files must name its file itself (400 SCOPE_NOT_PLACE). The import is checked as the other routes check it (404 IMPORT_NOT_FOUND, 403 NOT_YOURS, 409 CONNECTION_MISMATCH, 404 SOURCE_NOT_FOUND, 409 FILE_CHANGED, 409 IMPORT_CHANGED), none of it charged. The server reads the place’s earlier turns itself — the newest six from where replay starts, and for the newest that proposed anything, what became of each fix it proposed, read off the import and the stamps its files’ work keeps — and tells them to the model. Each fix that names its file and can be taken as a guess is kept as a target (a mapping from a column, a formula or a search, a way to recognise a record, a module’s Which rows or update rule — the update rule only where its card looks existing records up, as the import’s recipe stands or with a rule the same answer puts down, since the page refuses one with nowhere to wait — or what one value becomes, each with what its slot held), a value map’s values whichever file; a fixed value or a field to leave out is said in the answer and kept as no target, since a person sets those by hand on Map fields. Nothing is applied. The turn is returned as turn; one another turn beat to its place is not kept, and not charged (409 CONVERSATION_MOVED_ON).
What the model sees. Fields the organisation has excluded from AI are withheld from every sample — never sent in any form; their examples stand in for them — from the moment they are excluded: on /ai/chat an earlier turn is replayed as it was first sent — and an organisation whose AI sample policy is deny sends it no rows at all. The same applies to what a failed expression is allowed to say back: a thrown value is a value.
What you see is your own, where it is computed for you. This policy governs the model, not this response. Evidence produced FOR the caller comes from the rows you sent, unmasked, because you are the organisation and the model is not: the sample outputs on a validated proposal, and a formula’s own results and error.
Except what mirrors the conversation. A tool_result event on /ai/chat carries what the MODEL was told, so under masked or deny it carries the model’s copy rather than yours. It is capped at 200 characters and ellipsed past that, so a short result arrives whole and a long one does not — check for the ellipsis rather than assuming either. And none of this says anything about the rest of a response, which may be a generated file or a list of destination options rather than anything of yours.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”The connection this is about, by id: one of the organisation’s, or your own. REQUIRED: absent or empty is 400 CONNECTION_REQUIRED, listing the connections this bearer may name, because nothing picks one for a request that does not say (design §14.8). Any other — another organisation’s, or another person’s — is 404 CONNECTION_NOT_FOUND.
Example
con_8f2aThe connection this is about, by id: one of the organisation’s, or your own. REQUIRED: absent or empty is 400 CONNECTION_REQUIRED, listing the connections this bearer may name, because nothing picks one for a request that does not say (design §14.8). Any other — another organisation’s, or another person’s — is 404 CONNECTION_NOT_FOUND.
The versions of the organisation’s helper libraries the import keeps (“Keep v3 for this import”), which this request evaluates in place of the current ones: comma-separated libraryId:version, one per library, as the resolved configuration names each library (libraryId) — at most as many as an import may keep (200). Each is read from the organisation’s own stored publish — nothing a request sends is run. One that names no publish of a library the organisation holds now is 409 KEPT_VERSION_NOT_FOUND, with missing; one that cannot be read is 400 BAD_INPUT. Absent, every library runs as it currently is.
Example
0199a1b2-7c3d-7e4f-8a9b-0c1d2e3f4a5b:3The versions of the organisation’s helper libraries the import keeps (“Keep v3 for this import”), which this request evaluates in place of the current ones: comma-separated libraryId:version, one per library, as the resolved configuration names each library (libraryId) — at most as many as an import may keep (200). Each is read from the organisation’s own stored publish — nothing a request sends is run. One that names no publish of a library the organisation holds now is 409 KEPT_VERSION_NOT_FOUND, with missing; one that cannot be read is 400 BAD_INPUT. Absent, every library runs as it currently is.
Header Parameters
Section titled “Header Parameters”The organisation you believe this session is in. When it is not the organisation the session is in NOW — POST /auth/session/org moves a session without issuing a new token — the request is refused with 409 ORG_CHANGED rather than served against the other organisation’s schema. Omit it and nothing changes.
The organisation you believe this session is in. When it is not the organisation the session is in NOW — POST /auth/session/org moves a session without issuing a new token — the request is refused with 409 ORG_CHANGED rather than served against the other organisation’s schema. Omit it and nothing changes.
Request Bodyrequired
Section titled “Request Bodyrequired”object
The validation problems in front of the user. At most 40, and at least one — save at Validate’s or Run’s own place (conversation), where a question may be about none: whether the import is ready, or what a run will do or did.
object
object
The error code the destination or the import gave, for a failed row (INVALID_DATA, DUPLICATE_DATA…): letters, digits, _, . and - only — anything else is ignored rather than refused, and never reaches the model. What a run error is explained from when the organisation’s AI data setting withholds its message.
The problem in words. Send the values it is about in sampleBad, not here: a problem that carries sampleBad is taken to name no value of the file in its message, which is sent to the model as written. One that does not is treated as quoting values: withheld under masked and deny, so that it is explained from its code, module and apiName, and under allow too when the field it names is excluded from AI, or its mapping names — or has a formula that may read — a column named like one (per mappings).
For a failed row’s problem: the file’s column the field it names is filled from, as it stands, in the file the row came from. A column named like a field excluded from AI withholds the problem’s words under allow too — a run’s errors come from every file, and a request need not describe the file each came from (otherSources).
The label of the field the problem is about.
What the field expects: email, date, number…
1-based source rows holding a value this problem is about (at most 50).
The values of the file this problem is about, kept apart from its words (at most 20). Shown to the model as the organisation’s AI data setting allows: as they are under allow, as shapes under masked, and not at all under deny — and never, in any form, for a field or column excluded from AI. Present, even empty, it says message quotes none of them.
object
For a value a formula produced — its output, or an error it threw — the source columns that formula reads. The value is withheld, under every setting, when any of them is a column named like a field excluded from AI.
columns was cut to fit its bound. The value is withheld under every setting, since a column excluded from AI may be among those left out.
On an import of several files: the file this problem is about, by its id — the request’s own sourceId, or one of otherSources. A fix proposed for it is checked against that file’s columns and mapping, and names it. Absent — with no sourceUnknown either — the problem is about the import as a whole, and a fix for it is made in the file the request’s own fields describe. Ignored on an import of one file.
True when the file this problem is about cannot be named: taken out of the import since the run that failed on it, or a row an older build recorded without its file. It is explained, but no mapping or match rule is proposed for it — there is no file to check one against or make it in. It is not the import as a whole, which is a problem naming no sourceId and no sourceUnknown. Heeded on an import of one file too.
For a failed row’s problem: how its record failed — refused by the destination, never answered, or failed of the import’s own. A question kept at a failure’s place (conversation) must send it, and it must be the place’s.
For one of Validate’s problems: its kind (blank_required, type_mismatch…). A question kept at a problem’s place (conversation) must send it, and it must be the place’s.
For one of Validate’s problems whose module and apiName name the field its values are, not where it is: the module and field it is about, as its place (conversation) names them — a link that finds nothing quotes the other file’s key, and is about this file’s lookup. A question kept at a problem’s place is held to place where it is sent, and to module and apiName otherwise. Masking reads module and apiName either way.
The source file’s column headings, in order.
Sample rows for context. At most 5 are used; the rest are ignored rather than refused. Columns named like a field excluded from AI are withheld (never sent, in any form), and an organisation whose AI sample policy is deny sends it none at all — see the operation description for what that does and does not say about the response you get back.
One source row: column name → cell value, always as a string.
object
Each sample row’s number — its place in its file, from 1, which excluding or filtering rows never changes — one per row of sampleRows and in its order; a list of another length is refused. The model is shown each sample by it and names a row by it. Absent, a row is known only by its place among the samples.
The current mappings, so the explanation can refer to them.
object
object
The file columns, sampleRows and mappings describe — the one on screen — by its id, on an import of one file too. A problem naming it, or naming no file, is about it, and a fix for one is checked against its columns and rows, and names it.
That file’s name, as its tab shows it: what the model calls it.
On an import of several files, the others (at most 45: every file a problem is about, which is at most one per problem, and up to 5 more as context): each one’s id, name, column headings and mappings — never its rows. A problem naming one by sourceId is explained beside that file’s columns and mapping, and a fix proposed for it is checked against them and names it; a formula for it is compiled but not run, since its rows are not sent. Absent, the request describes one file and every problem is about it.
object
Its name, as its tab shows it: what the model calls it. Absent, the model calls it by its place among the request’s files (“file 2”), never by its id.
Its column headings, in order.
Its mappings as they stand, by module key and field apiName.
object
object
What the person asked, in their words or a suggestion’s (“Why did these fail?”): the model answers it from the problems. Absent, it explains the problems, as it always did; a conversation naming its own question is the one asked and recorded, and this is its fallback. Past 8000 characters it is cut there.
object
The run asked about, by its id: one of this import’s that has ended, or 404 RUN_NOT_FOUND (409 RUN_NOT_ENDED while it is authorised, running or refused; 409 RUN_RETIRED once the history’s purge has begun to take it). Its outcome — dry or real, how it ended and the rows it wrote, made, updated and failed — is read from the run’s own record, as its history shows it. A dry run writes nothing, so its record holds how it ended and the rows that failed in it.
The run as it would go if it were started again now — after a dry run, what the real run would do.
object
Each file in the order the run takes them, by its id, with the rows it would send — at most 50; the rest are counted in more. A file the import does not hold is left out, and so is a module the connection does not have.
object
Its modules, in the order this file writes them.
How many more files the run would take, past the ones named.
object
Before any run: the run as it stands.
object
Each file in the order the run takes them, by its id, with the rows it would send — at most 50; the rest are counted in more. A file the import does not hold is left out, and so is a module the connection does not have.
object
Its modules, in the order this file writes them.
How many more files the run would take, past the ones named.
A run was made that the page cannot name — it has no id for it (#1095): the plan is then the run as it stands now, and nothing is said of how that run went.
A conversation kept with the import, at one kind of failure on a run ({ kind: 'failed', source, module, failure, code, field }), one of Validate’s problems ({ kind: 'issue', source, type, module, apiName }), Validate’s own ({ kind: 'checks' }), or Run’s own ({ kind: 'run' }, with run naming the run asked about); any other place is 400 PLACE_NOT_ANSWERED. At a failure’s or a problem’s place every problem must be what the place names — its file, module and field (a problem’s own place where it sends one), and the failure’s kind (failure) and code, or the problem’s kind (issueType) — or it is 400 SCOPE_NOT_PLACE. Its file is read as a fix’s is: on an import of one file, the file sourceId names; on an import that holds several, the problem’s own sourceId. Validate’s and Run’s own places name no file, so conversation.file is refused there, and a question there may be about no problem at all. See the operation description.
object
object
object
object
object
object
object
object
object
object
What to record as the question when the request asks none in words (a suggestion pressed, say). Absent, the route records the question the request asks.
The file the asker believes the import holds for the place, as the import last said it: the digest of its bytes (null for one stored before digests were kept) and how they are read — the source’s reader, options and worksheet as one string (fileReading in @sloose/core). The answer is made from the rows the request sends and its turn kept against the file the import holds, so a file replaced, or read again under other options, since the asker last read the import is refused before anything is asked or charged (409 FILE_CHANGED). Absent, the question is about whatever file the import holds.
object
The import’s version the asker’s picture of it was built from: what it last wrote or read, with nothing of its own left unwritten. What became of earlier answers is judged, and a new answer’s was recorded, from the import as the server holds it, so a question asked of a picture the import has moved on from — another person’s edit, or the asker’s own that was never saved — is refused before anything is asked or charged (409 IMPORT_CHANGED, with the version it is at and who changed it last). Absent, the question is about the import as it is.
The kept turn whose answer is live on the page — its newest that proposed anything, which an answer in words alone leaves standing — and which of its targets, by position, still wait on the page (waiting) or were turned down there (declined): what only the page knows. Read only at the formula editor’s place (formula), whose answer waits in the editor and nowhere else, and taken there only when it names the turn the question is judged against: the newest of the turns sent that proposed anything. At Source and every place of Map fields the import keeps its guesses stamped with the answer that made them, so what still waits and what was turned down is read off the stamps each file’s work keeps, and a last sent there is not read.
object
Responses
Section titled “Responses”One explanation per problem, with a proposed fix where one is possible. With a conversation, turn is the turn it was kept as.
Explanations, and applicable fixes where they exist.
object
This call’s id. It is also the ledger’s idempotency key.
Credits settled for this call.
Token counts, including cache reads and writes.
object
The model that answered.
object
object
object
object
object
object
object
object
object
object
Its position in the conversation at its place, from 0.
The AI route that answered it.
What the answer proposed, and where.
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
What the answer said that is not the answer: a caveat, what the server refused.
What of the file the answer was made from: its values (allow); the distinct values of list fields, a picklist’s or a multi-select’s, as Translate values sends them under Masked (values); their shapes (masked); or none of it (none) — counting the earlier turns it was sent with, since an answer can repeat what one of them said.
When the question was asked, which the history window counts from.
Who asked: a person, or the person whose API token asked. Null once they are deleted.
object
How the question was asked: a person’s session in the app (session), an organisation API token (api_token), or an agent over MCP (mcp).
Whether the next question at its place may send it to the model again. False for a turn before where replay starts, which a stricter AI data setting moves past everything already asked; only the newest 6 are ever sent.
Whether a draft asked it — Draft everything, Fill the gaps or Draft it anyway (draftScope): one call of the draft answers several parts, and each part’s share of the answer is kept at that part’s own place, as a turn saying so, so a later question there — and Go there — finds it (D6). A draft’s turn is sent with no earlier turns, and lands after whatever its place holds without moving where replay starts.
Which draft asked it — Draft everything, Fill the gaps or Draft it anyway: the same for every part’s turn of one draft, so the import’s AI activity lists the draft as one entry. Null for a turn no draft asked, and for a draft’s turn kept before drafts said so, which is an entry of its own.
How much of the import the draft that asked it was asked about: import, Draft everything; gaps, Fill the gaps, the parts a template’s check left open and no other; anyway, Draft it anyway, one file and module a draft found the file does not feed. Null for a turn no draft asked, and for a draft’s turn kept before drafts said, which reads as the whole import.
object
object
object
object
object
object
object
object
object
object
Its position in the conversation at its place, from 0.
The AI route that answered it.
What the answer proposed, and where.
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
object
What the answer said that is not the answer: a caveat, what the server refused.
What of the file the answer was made from: its values (allow); the distinct values of list fields, a picklist’s or a multi-select’s, as Translate values sends them under Masked (values); their shapes (masked); or none of it (none) — counting the earlier turns it was sent with, since an answer can repeat what one of them said.
When the question was asked, which the history window counts from.
Who asked: a person, or the person whose API token asked. Null once they are deleted.
object
How the question was asked: a person’s session in the app (session), an organisation API token (api_token), or an agent over MCP (mcp).
Whether the next question at its place may send it to the model again. False for a turn before where replay starts, which a stricter AI data setting moves past everything already asked; only the newest 6 are ever sent.
Whether a draft asked it — Draft everything, Fill the gaps or Draft it anyway (draftScope): one call of the draft answers several parts, and each part’s share of the answer is kept at that part’s own place, as a turn saying so, so a later question there — and Go there — finds it (D6). A draft’s turn is sent with no earlier turns, and lands after whatever its place holds without moving where replay starts.
Which draft asked it — Draft everything, Fill the gaps or Draft it anyway: the same for every part’s turn of one draft, so the import’s AI activity lists the draft as one entry. Null for a turn no draft asked, and for a draft’s turn kept before drafts said so, which is an entry of its own.
How much of the import the draft that asked it was asked about: import, Draft everything; gaps, Fill the gaps, the parts a template’s check left open and no other; anyway, Draft it anyway, one file and module a draft found the file does not feed. Null for a turn no draft asked, and for a draft’s turn kept before drafts said, which reads as the whole import.
Said once, when the request named a conversation and this question starts afresh because the organisation’s AI data setting is stricter than the one its earlier turns were made from. Null otherwise.
Example
{ "turn": { "place": { "kind": "source" }, "route": "source", "targets": [ { "kind": "filter" } ], "samples": "allow", "askedVia": "session", "draft": false, "draftId": null, "draftScope": "import" }, "turns": [ { "place": { "kind": "source" }, "route": "source", "targets": [ { "kind": "filter" } ], "samples": "allow", "askedVia": "session", "draft": false, "draftId": null, "draftScope": "import" } ]}The body did not match the schema (BAD_INPUT, with issues); the request named no connection (CONNECTION_REQUIRED, with connections); or keep cannot be read (BAD_INPUT); conversation names a place this route does not answer, or names a file at Validate’s own place, which is about no one file (PLACE_NOT_ANSWERED); or the problems are not all what the conversation’s place is about (SCOPE_NOT_PLACE).
object
Examplegenerated
{ "error": "example", "code": "example"}No bearer, or one that no longer resolves: a session that is unknown or expired (code: AUTH_REQUIRED), or an API token that was revoked or expired (TOKEN_REVOKED, TOKEN_EXPIRED), whose organisation is gone (ORG_NOT_FOUND) or whose member or user row is (MEMBERSHIP_GONE).
The error envelope every non-2xx answer uses.
object
Human-readable explanation.
Machine-readable reason. Absent on a few legacy 400s.
Examplegenerated
{ "error": "example", "code": "example"}Not enough AI credits (CREDITS_EXHAUSTED), or the organisation is suspended (SUSPENDED). balance shows what is left, needed what this asked for, planIncludesCredits whether the plan includes any each period, and renewsAt, where a subscription renews them, when they come back.
object
AI credits. Included credits reset each period; purchased ones do not.
object
Included + purchased − reserved
The least the action would hold: its price, or for an action priced by how much it reads, the cheapest band, since such an action holds what the balance covers of its dearest band. The balance fell short of it — which is not the same as nothing left.
When the credits ran short (CREDITS_EXHAUSTED) under a subscription that renews them — active, not set to cancel, and renewing on a plan with an allowance, whether charged to a card or billed by invoice (whose credits come as each invoice is issued, #1337): when that allowance comes back, the end of the current period. Absent on a plan that includes none, on a trial, under a subscription that is cancelled, set to cancel or past due, when the plan it moves to at the period’s end includes none, for a suspension, and when the plan could not be read.
When the credits ran short (CREDITS_EXHAUSTED): whether the organisation’s plan, as the catalogue has it, includes AI credits each period — a staff override changes this period’s limits, not that. Absent for a suspension, and when the plan could not be read — which says nothing either way.
Examplegenerated
{ "error": "example", "code": "example", "balance": { "included": 1, "purchased": 1, "reserved": 1, "available": 1, "periodStart": "example", "periodEnd": "example" }, "needed": 1, "renewsAt": "example", "planIncludesCredits": true}The import conversation names is one this person may not change: an operator asks about only an import they started (NOT_YOURS). Like every route behind a session: a session signed in but in no organisation is refused code: NO_ORG, a banned person code: BANNED, and a closed organisation code: ORG_CLOSED.
object
Example
{ "code": "NOT_YOURS"}No such organisation (ORG_NOT_FOUND); ?connector= names a connection this person may not use (CONNECTION_NOT_FOUND); the import named is not this organisation’s, or is being deleted (IMPORT_NOT_FOUND); it does not hold the file the place names (SOURCE_NOT_FOUND); or the import or that file went while the answer was written, so it was not kept and nothing was charged (CONVERSATION_GONE); or the run run names is not this import’s (RUN_NOT_FOUND), refused before anything is charged.
object
Example
{ "code": "ORG_NOT_FOUND"}The organisation has no module registry yet (ORG_NOT_CONFIGURED); the session is in a different organisation now (ORG_CHANGED); a version keep names is not one the organisation holds now (KEPT_VERSION_NOT_FOUND, with missing); the import writes to another connection than ?connector= names (CONNECTION_MISMATCH); the file is not the one conversation.file names (FILE_CHANGED); the import moved on from the version named (IMPORT_CHANGED, with the version it is at, updatedBy and updatedAt); or another turn landed at the conversation’s place while this one was answered (CONVERSATION_MOVED_ON); or the run run names has not ended — authorised, still running, or refused — so it has no outcome to read (RUN_NOT_ENDED), or the history’s purge has begun to take it (RUN_RETIRED). None of these is charged.
object
IMPORT_CHANGED only: the version the import is at now.
IMPORT_CHANGED only: who changed it last.
object
The app an agent changed it through over MCP, when its last write was one: userId is then the person whose agent it was.
IMPORT_CHANGED only: when it was last changed.
KEPT_VERSION_NOT_FOUND only: each kept publish keep names that the organisation does not hold now, by its library’s id and number.
object
Example
{ "code": "ORG_NOT_CONFIGURED"}The model declined. category says why.
object
Example
{ "code": "AI_REFUSED"}The model’s answer could not be used.
object
Example
{ "code": "AI_BAD_OUTPUT"}AI is not configured on this deployment (AI_NOT_CONFIGURED): unconfigured paths say so rather than failing. Or the model could not be asked just now (AI_UNAVAILABLE) — the gateway out of credits, rate limited or failing, or the provider unavailable: nothing was changed and nothing was charged, so the same request can be sent again shortly. Or the restore switch is on (code: MAINTENANCE): every route but /health and /robots.txt is refused before anything is read, with Retry-After (the operator’s ADMIN_TOKEN alone is let through to POST /staff/people/{id}/delete, and to nothing else).
Headers
Section titled “Headers”Seconds until asking again is worth it (300 while the switch is on).