Skip to content

Propose how existing records are recognised

POST
/ai/match
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.

connector

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.

string
Example
con_8f2a

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.

keep

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.

string
Example
0199a1b2-7c3d-7e4f-8a9b-0c1d2e3f4a5b:3

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.

x-sloose-org

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.

string

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.

Media typeapplication/json
object
columns
required

The source file’s column headings, in order.

Array<string>
>= 1 items <= 500 items
sampleRows

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.

Array<object>
default:

One source row: column name → cell value, always as a string.

object
key
additional properties
string
modules

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.

Array<string>
declined

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.

Array<object>
<= 200 items
object
module
required
string
>= 1 characters
field
required
string
>= 1 characters
note

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.

string
<= 500 characters
fit

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
file
required
string
>= 1 characters <= 200 characters
others
required
Array<object>
<= 10 items
object
file
required
string
>= 1 characters <= 200 characters
columns
required
Array<string>
<= 60 items
draft

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
importId
required
string
>= 1 characters <= 100 characters
scope

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.

string
Allowed values: import gaps anyway
draftId

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.

string
>= 1 characters <= 100 characters
source

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.

string
/^[A-Za-z0-9_-]{1,64}$/
version

The import’s version the draft’s picture of it was built from, as a kept question’s version (409 IMPORT_CHANGED).

integer
>= 1
file

The file the draft believes the import holds, as a kept question’s file (409 FILE_CHANGED).

object
digest
required
string | null
<= 128 characters
reading
required
string | null
<= 16384 characters

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.

Media typeapplication/json

One validated rule per module the model could recognise a record in, with what it cost.

object
requestId
required

This call’s id. It is also the ledger’s idempotency key.

string
credits
required

Credits settled for this call.

number
usage
required

Token counts, including cache reads and writes.

object
key
additional properties
model
required

The model that answered.

string
turn
object
id
required
string
place
required
One of:
object
kind
required
string
Allowed values: source
source
required
string
/^[A-Za-z0-9_-]{1,64}$/
seq
required

Its position in the conversation at its place, from 0.

integer
route
required

The AI route that answered it.

string
Allowed values: source chat auto-map match value-map formula explain
question
required
string
answer
required
string
targets
required

What the answer proposed, and where.

Array
<= 500 items
One of:
object
kind
required
string
Allowed values: filter
where
required
object
source
required
string
/^[A-Za-z0-9_-]{1,64}$/
what
required
string
was
required
string | null
notes
required

What the answer said that is not the answer: a caveat, what the server refused.

Array<string>
samples
required

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.

string
Allowed values: allow values masked none
askedAt
required

When the question was asked, which the history window counts from.

string
askedBy
required

Who asked: a person, or the person whose API token asked. Null once they are deleted.

object
userId
required
string
name
required
string
askedVia
required

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).

string
Allowed values: session api_token mcp
replayed
required

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.

boolean
draft

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.

boolean
draftId

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.

string | null
draftScope

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.

string | null
Allowed values: import gaps anyway
turns
Array<object>
object
id
required
string
place
required
One of:
object
kind
required
string
Allowed values: source
source
required
string
/^[A-Za-z0-9_-]{1,64}$/
seq
required

Its position in the conversation at its place, from 0.

integer
route
required

The AI route that answered it.

string
Allowed values: source chat auto-map match value-map formula explain
question
required
string
answer
required
string
targets
required

What the answer proposed, and where.

Array
<= 500 items
One of:
object
kind
required
string
Allowed values: filter
where
required
object
source
required
string
/^[A-Za-z0-9_-]{1,64}$/
what
required
string
was
required
string | null
notes
required

What the answer said that is not the answer: a caveat, what the server refused.

Array<string>
samples
required

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.

string
Allowed values: allow values masked none
askedAt
required

When the question was asked, which the history window counts from.

string
askedBy
required

Who asked: a person, or the person whose API token asked. Null once they are deleted.

object
userId
required
string
name
required
string
askedVia
required

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).

string
Allowed values: session api_token mcp
replayed
required

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.

boolean
draft

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.

boolean
draftId

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.

string | null
draftScope

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.

string | null
Allowed values: import gaps anyway
notice

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.

string | null
key
additional properties
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).

Media typeapplication/json
object
error
required
string
code
required
string
key
additional properties
Examplegenerated
{
"error": "example",
"code": "example"
}

No bearer, or one that is expired, revoked or no longer resolves to a member.

Media typeapplication/json

The error envelope every non-2xx answer uses.

object
error
required

Human-readable explanation.

string
code

Machine-readable reason. Absent on a few legacy 400s.

string
key
additional properties
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.

Media typeapplication/json
object
error
required
string
code
required
string
balance
required

AI credits. Included credits reset each period; purchased ones do not.

object
included
required
number
purchased
required
number
reserved
required
number
available
required

Included + purchased − reserved

number
periodStart
required
string
periodEnd
required
string
needed
required

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.

number
renewsAt

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.

string
planIncludesCredits

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.

boolean
key
additional properties
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).

Media typeapplication/json
object
error
required
string
code
required
string
Allowed values: NOT_YOURS
key
additional properties
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).

Media typeapplication/json
object
error
required
string
code
required
string
Allowed values: ORG_NOT_FOUND CONNECTION_NOT_FOUND IMPORT_NOT_FOUND SOURCE_NOT_FOUND CONVERSATION_GONE
key
additional properties
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.

Media typeapplication/json
object
error
required
string
code
required
string
Allowed values: ORG_NOT_CONFIGURED ORG_CHANGED KEPT_VERSION_NOT_FOUND CONNECTION_MISMATCH FILE_CHANGED IMPORT_CHANGED
version

IMPORT_CHANGED only: the version the import is at now.

integer
updatedBy

IMPORT_CHANGED only: who changed it last.

object
userId
required
string | null
tokenId
required
string | null
scheduleId
required
string | null
name
required
string | null
updatedAt

IMPORT_CHANGED only: when it was last changed.

string
missing

KEPT_VERSION_NOT_FOUND only: each kept publish keep names that the org does not hold now, by its library’s id and number.

Array<object>
object
library
required
string
version
required
number
key
additional properties
Example
{
"code": "ORG_NOT_CONFIGURED"
}

The model declined. category says why.

Media typeapplication/json
object
error
required
string
code
required
string
Allowed values: AI_REFUSED
category
required
string
key
additional properties
Example
{
"code": "AI_REFUSED"
}

The model’s answer could not be used.

Media typeapplication/json
object
error
required
string
code
required
string
Allowed values: AI_BAD_OUTPUT
key
additional properties
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.

Media typeapplication/json
object
error
required
string
code
required
string
Allowed values: AI_NOT_CONFIGURED AI_UNAVAILABLE
key
additional properties
Example
{
"code": "AI_NOT_CONFIGURED"
}

Report a problem with this page