Propose how existing records are recognised
const url = 'https://api.sloose.com/ai/match?connector=con_8f2a&keep=0199a1b2-7c3d-7e4f-8a9b-0c1d2e3f4a5b%3A3';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"columns":["example"],"sampleRows":[],"modules":["example"],"declined":[{"module":"example","field":"example"}],"note":"example","fit":{"file":"example","others":[{"file":"example","columns":["example"]}]},"draft":{"importId":"example","scope":"import","draftId":"example","source":"example","version":1,"file":{"digest":"example","reading":"example"}}}'};
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/match?connector=con_8f2a&keep=0199a1b2-7c3d-7e4f-8a9b-0c1d2e3f4a5b%3A3' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "columns": [ "example" ], "sampleRows": [], "modules": [ "example" ], "declined": [ { "module": "example", "field": "example" } ], "note": "example", "fit": { "file": "example", "others": [ { "file": "example", "columns": [ "example" ] } ] }, "draft": { "importId": "example", "scope": "import", "draftId": "example", "source": "example", "version": 1, "file": { "digest": "example", "reading": "example" } } }'For each module asked about, proposes the ONE field an existing record is recognised by, the expression over the row that produces the value to look it up by, how it is compared, a confidence, a one-sentence reason and — when the file makes it plain — a reading of the file as a whole: every row new, some rows existing, or updates only.
Candidates are keys, not fields. The model may only name a field the destination enforces as unique, the org’s default key, or a writable text, email, phone, website or number field; a date, a picklist, a lookup or an amount can be written but nothing is recognised by one. A rule naming anything else — or a field the org does not have, or a module this call did not ask about — is dropped and said in notes, never handed back weakened: a half-right identity is what makes duplicates. Every expression is compiled and run on your sample rows first, and one that reads the record context is refused, because a match runs before any record of the row exists.
Priced by the modules asked, not by the columns: the model reads every column and maps none of them, which is why this is its own call rather than part of POST /ai/auto-map. Ask for one module from an identity card, or for all of them at once, and pass the answer to auto-map as keys.
What the model sees. Fields the org 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 org 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 org 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 org’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 org’s, or another person’s — is 404 CONNECTION_NOT_FOUND.
Example
con_8f2aThe connection this is about, by id: one of the org’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 org’s, or another person’s — is 404 CONNECTION_NOT_FOUND.
The versions of the org’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 org’s own stored publish — nothing a request sends is run. One that names no publish of a library the org 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 org’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 org’s own stored publish — nothing a request sends is run. One that names no publish of a library the org 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 org you believe this session is in. When it is not the org 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 org’s schema. Omit it and nothing changes.
The org you believe this session is in. When it is not the org 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 org’s schema. Omit it and nothing changes.
Request Bodyrequired
Section titled “Request Bodyrequired”object
The source file’s column headings, in order.
Sample rows for context. At most 20 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 org 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
The modules to answer for — one rule each. Defaults to everything the org has enabled. A module the org does not have is dropped; naming only such modules is refused with UNKNOWN_MODULE. The call is priced by how many modules are asked, after that.
Rules this person has already turned down, as (module, field). The FIELD is what a decline closes — the field is the identity, the expression only where its value comes from — so another field for that module may still be proposed. At most 200. This changes the answer, not the price.
object
What the person doing the import said before asking — “the address is where we ship to” — for what the file cannot say about itself. Given to the model as context, in their words, never as an instruction that changes what the route answers. At most 500 characters.
Ask first whether this file’s rows are records of each module (#1292): file is this file’s name, and others the import’s other files by name and columns (at most 10, 60 columns each), so a column naming a record another file holds reads as a link. The answer’s unfit names each module the file does not feed, with one sentence saying why, and carries no rule for it. Absent, the call answers as it always has. The price is the same.
object
object
A draft asking — Draft everything, Fill the gaps for what a template’s check left open, or Draft it anyway for one file and module (draft.scope): the answer is kept as a turn at each module’s Which rows & existing, each turn recording which (draft.source names the file). See the operation description.
object
How much of the import this draft was asked about, kept on each part’s turn (ConversationTurn.draftScope): import for Draft everything, gaps for Fill the gaps, anyway for Draft it anyway. Absent, the turn says nothing of it, and reads as the whole import.
Which draft — Draft everything, Fill the gaps or Draft it anyway — this call is part of: the same on every call of one draft, and kept on each part’s turn (ConversationTurn.draftId), so the import’s AI activity lists the draft as one entry. Absent, each turn is an entry of its own.
The file the answer is about: required where the route answers about a file’s modules (/ai/match, /ai/auto-map), and not sent for Translate values’ (/ai/value-map), which are the import’s.
The import’s version the draft’s picture of it was built from, as a kept question’s version (409 IMPORT_CHANGED).
The file the draft believes the import holds, as a kept question’s file (409 FILE_CHANGED).
object
Responses
Section titled “Responses”The rules, in creation order. modules is what was asked; a module missing from rules is one the model proposed nothing for, or whose proposal was dropped — notes says which — except a module in unfit, which a draft asked with fit found the file does not feed: it has no rule and no note, only its sentence. declinedAgain names the modules missing because the model proposed nothing but a rule declined had turned down. libraryProblems reports any helper library that would not load. With a draft, turns are the turns the answer was kept as, one per module asked.
One validated rule per module the model could recognise a record in, with what it cost.
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
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 org 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
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 org 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 org’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); none of the modules named exist (UNKNOWN_MODULE); or draft names no file (PLACE_NOT_ANSWERED).
object
Examplegenerated
{ "error": "example", "code": "example"}No bearer, or one that is expired, revoked or no longer resolves to a member.
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 org 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 org’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 draft names is one this person may not change: an operator asks about only an import they started (NOT_YOURS).
object
Example
{ "code": "NOT_YOURS"}No such org (ORG_NOT_FOUND); ?connector= names a connection this person may not use (CONNECTION_NOT_FOUND); the import draft names is not this org’s, or is being deleted (IMPORT_NOT_FOUND); it does not hold the file named (SOURCE_NOT_FOUND); or the import or that file went, or the file was replaced or read again, while the answer was written, so it was not kept and nothing was charged (CONVERSATION_GONE).
object
Example
{ "code": "ORG_NOT_FOUND"}The org has no module registry yet (ORG_NOT_CONFIGURED); the session is in a different org now (ORG_CHANGED); a version keep names is not one the org holds now (KEPT_VERSION_NOT_FOUND, with missing); the import writes to another connection than ?connector= names (CONNECTION_MISMATCH); the file draft.file names is not the one the import holds (FILE_CHANGED); or the import moved on from draft.version (IMPORT_CHANGED, with the version it is at, updatedBy and updatedAt). None of these is charged.
object
IMPORT_CHANGED only: the version the import is at now.
IMPORT_CHANGED only: who changed it last.
object
IMPORT_CHANGED only: when it was last changed.
KEPT_VERSION_NOT_FOUND only: each kept publish keep names that the org 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.
object
Example
{ "code": "AI_NOT_CONFIGURED"}