Skip to content

Propose value maps for picklists and lookups

POST
/ai/value-map
curl --request POST \
--url 'https://api.sloose.com/ai/value-map?connector=con_8f2a&keep=0199a1b2-7c3d-7e4f-8a9b-0c1d2e3f4a5b%3A3' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "fields": [ { "module": "example", "apiName": "example", "mapType": "picklist", "values": [ "example" ], "counts": { "additionalProperty": 1 }, "targets": [ { "label": "example", "value": "example" } ], "column": "example" } ], "columns": [], "sampleRows": [], "enabledModules": [ "example" ], "note": "example", "conversation": { "importId": "example", "place": { "kind": "source", "source": "example" }, "question": "example", "file": { "digest": "example", "reading": "example" }, "version": 1, "last": { "turnId": "example", "waiting": [ 1 ], "declined": [ 1 ] } }, "draft": { "importId": "example", "scope": "import", "draftId": "example", "source": "example", "version": 1, "file": { "digest": "example", "reading": "example" } } }'

Maps the values in a file onto the values a destination field accepts — the step the free matcher cannot do, because no amount of string distance gets from “Aus” to “Australia”.

Every option it names is checked against the field’s own before you see it: picklist options from the organisation’s schema, and lookup targets only from the records the caller sent, since the API has no CRM connection here. A name that two of those records share resolves to neither.

Certainty decides what happens next. An answer that is both confident and clearly ahead of its runner-up is applied; anything else comes back as candidates, best first, and nothing is written until a person picks one. A confident wrong answer is the expensive kind — it is the one nobody reads.

It has to read the values. What a value means cannot be worked out from its shape, so an organisation whose AI data setting is deny is refused with SAMPLES_DENIED, and a field excluded from AI, or filled straight from a column named like a field excluded from AI (column), is left out and named in notes. Under allow the model is shown the values asked about, with how many rows hold each where you send counts, and, for a lookup, the names of the records you sent. Under masked it is shown the distinct values of a picklist or multi-select field filled straight from a column (column sent), each once and without a count; a lookup, whose values and candidates are the names of records, and a field whose values a formula made, which may come of any column, are left out and named in notes — and a request left with nothing to ask about is refused (400 NO_FIELDS) before anything is charged. No row is sent under any setting.

A conversation kept with the import. Name it with conversation — the import, and a place of Translate values: a module’s ({ kind: 'vals', module }), or one value of one of its fields ({ kind: 'val', module, apiName, value }) — and the question is kept at that place. Translate values name no file: a value map is the import’s, per module and field, whichever file a value came from, so conversation.file is refused (400 PLACE_NOT_ANSWERED), and so is any other place. fields must be what the place is about — that module’s fields at vals, and at val that one field with that one value — or it is refused before anything is charged (400 SCOPE_NOT_PLACE). The server reads the place’s earlier turns itself, as POST /ai/chat does at a place of Map fields: the newest six from where replay starts, and for the newest that proposed anything, what became of each value it proposed — used, changed by hand, still waiting or turned down, not taken — read off the import’s value maps and the stamps every file’s work keeps, whichever file’s round a guess landed in. The import is checked as the other routes check it (404 IMPORT_NOT_FOUND, 403 NOT_YOURS, 409 CONNECTION_MISMATCH, 409 IMPORT_CHANGED), none of it charged. What the answer proposed is kept as targets — what each value would become, the best candidate, sure or not — and 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.

connector

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.

string
Example
con_8f2a

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.

keep

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.

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

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.

x-sloose-org

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.

string

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.

Media typeapplication/json
object
fields
required
Array<object>
>= 1 items <= 12 items
object
module
required
string
>= 1 characters
apiName
required
string
>= 1 characters
mapType
required
string
Allowed values: picklist lookup
values
required
Array<string>
>= 1 items <= 150 items
counts
object
key
additional properties
number
targets
Array<object>
<= 400 items
object
label
required
string
<= 300 characters
value
required
string
<= 120 characters
column

The file’s column the values were read from as they are, when they were — not for values a formula made. A column named like a field excluded from AI (its name, its label or one of its aliases) keeps the field’s values from the model under allow too, as it keeps that column’s sample values.

string | null
>= 1 characters <= 500 characters
columns

Accepted from a caller that sends it, and not read.

Array<string>
default: <= 500 items
sampleRows

Accepted from a caller that sends it, and shown to nobody: every value asked about is in fields already, and a row carries every other column beside it — one of which may fill a field excluded from AI under a name that says nothing.

Array<object>
default:
object
key
additional properties
string
enabledModules
Array<string>
note

What the person doing the import said before asking — “Lead Source is where they heard of us” — for what the values cannot say about themselves. 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
conversation

A conversation kept with the import, at a module’s Translate values ({ kind: 'vals', module }) or one value of one of its fields ({ kind: 'val', module, apiName, value }); any other place is 400 PLACE_NOT_ANSWERED, and fields that are not what the place is about are 400 SCOPE_NOT_PLACE. Translate values name no file, so conversation.file is refused. See the operation description.

object
importId
required
string
>= 1 characters <= 100 characters
place
required
One of:
object
kind
required
string
Allowed values: source
source
required
string
/^[A-Za-z0-9_-]{1,64}$/
question

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.

string
>= 1 characters <= 8000 characters
file

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
digest
required
string | null
<= 128 characters
reading
required
string | null
<= 16384 characters
version

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.

integer
>= 1
last

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
turnId
required
string
>= 1 characters <= 100 characters
waiting
Array<integer>
<= 500 items
declined
Array<integer>
<= 500 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 Translate values, which name no file, each turn recording which (draft.source and draft.file are refused). 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

Proposals. Applied and offered are separated, so a client never has to guess which is which. With a conversation, turn is the turn it was kept as; with a draft, turns are the turns the answer was kept as, one per module asked about.

Media typeapplication/json

Per field: the mappings confident enough to apply, and candidates for the rest.

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 organisation 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 organisation 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 organisation’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 — conversation and draft together is one); the request named no connection (CONNECTION_REQUIRED, with connections); or keep cannot be read (BAD_INPUT); a field does not exist or the organisation shares no values with AI (SAMPLES_DENIED); conversation names a place this route does not answer, or it or draft names a file, which Translate values are not about (PLACE_NOT_ANSWERED); or fields are not what the conversation’s place is about (SCOPE_NOT_PLACE).

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

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

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

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 conversation or draft 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.

Media typeapplication/json
object
error
required
string
code
required
string
Allowed values: NOT_YOURS NO_ORG BANNED ORG_CLOSED
key
additional properties
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); or the import went 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 CONVERSATION_GONE
key
additional properties
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 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). 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 IMPORT_CHANGED CONVERSATION_MOVED_ON
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
app

The app an agent changed it through over MCP, when its last write was one: userId is then the person whose agent it was.

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

Media typeapplication/json
One of:
object
error
required
string
code
required
string
Allowed values: AI_NOT_CONFIGURED AI_UNAVAILABLE
key
additional properties
Example
{
"code": "AI_NOT_CONFIGURED"
}
Retry-After
integer

Seconds until asking again is worth it (300 while the switch is on).

Report a problem with this page

sloose.com