Configuration
Get or update deployment configuration including schema, tables, and columns
Get Configuration
/api/v1/deployments/{deploymentId}/configurationReturns the full configuration of a model or deployment including its extraction settings (returned under extractionSchema), tables, columns with their labels and units, shared column IDs, and the slug of every table and column.
Parameters
| Name | Type | In | Required | Description |
|---|---|---|---|---|
deploymentId | string | path | Yes | The model or deployment ID |
Request
const res = await fetch(
"https://app.limai.io/api/v1/deployments/dep_abc123/configuration",
{
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
}
);
const config = await res.json();Response
{
"deployment": {
"id": "dep_abc123",
"name": "Invoice Extractor",
"status": "ACTIVE",
"type": "MODEL"
},
"extractionSchema": {
"id": "es_001",
"instructions": "Extract invoice fields",
"writeMode": "INSERT_ROWS",
"modelName": "LIMAI_STANDARD_1_0",
"reasoningLevel": "LOW",
"temperature": 1,
"useExamples": true,
"numberOfExamples": 1,
"enableConfidenceScore": false,
"parsePdf": false,
"enableBoundingBoxes": false
},
"tables": [
{
"id": "tbl_001",
"name": "Primary Table",
"slug": "primary_table",
"instructions": null,
"isPrimary": true,
"parentTableId": null,
"wrapColumns": false,
"columns": [
{
"id": "col_001",
"name": "Invoice Number",
"slug": "invoice_number",
"type": "TEXT",
"index": 0,
"description": "The invoice number",
"isKey": true,
"isList": false,
"labels": [],
"units": []
}
]
}
],
"sharedColumnIds": []
}Slugs
Every table and column carries a slug: a stable, human-readable identifier matching ^[a-z][a-z0-9_]{0,63}$. It is generated from the display name when the table or column is created — Invoice Number becomes invoice_number — and is decoupled from that name from then on. Renaming a table or column in the app never changes its slug.
The slug exists because the other two identifiers on these objects cannot serve as a stable address:
| Identifier | Why it is not an address |
|---|---|
name | A display label. Renaming a column is routine and silently breaks anything that matched on the old name. |
id | Stable but opaque, and it dies with the deployment — recreating a deployment produces all-new IDs. |
slug is the identifier to code your integration against. Column slugs are unique within their table; table slugs are unique within their deployment.
| Field | Type | Description |
|---|---|---|
tables[].slug | string | null | Stable identifier for the table, unique within the deployment. |
tables[].columns[].slug | string | null | Stable identifier for the column, unique within its table. |
Both are nullable: tables and columns created before slugs were introduced have no slug until they are backfilled. Treat the field as optional when reading, and fail loudly rather than falling back to name if you find a null you did not expect.
A slug does not change how extracted data is shaped. The cells map returned by the document data endpoints is still keyed by column display name; adding slugs does not re-key it. Use the slug on the column metadata to resolve which column a cell belongs to.
Do not confuse slug with isKey. isKey marks a column as an identifying column for extraction — it is unrelated to addressing.
Update Configuration
/api/v1/deployments/{deploymentId}/configurationUpdate table and column configurations for a model or deployment. You can update table names, instructions, and column properties in a single request.
Parameters
| Name | Type | In | Required | Description |
|---|---|---|---|---|
deploymentId | string | path | Yes | The model or deployment ID |
tables | array | body | No | Array of table updates with id and fields to update (name, slug, instructions) |
columns | array | body | No | Array of column updates with id and fields to update (name, slug, description, type, dateFormat, measurementType, isList, isKey, etc.) |
confirmSlugChange | boolean | body | No | Required only when a supplied slug differs from the stored one. See Changing a slug(default: false) |
reasoningLevel | string | body | No | How deeply the model reasons before answering: LOW, MEDIUM or HIGH(default: LOW) |
nestedExtraction | string | body | No | How child tables are extracted: PER_PARENT_ROW or ONE_SHOT. See Nested extraction(default: PER_PARENT_ROW) |
emailNestedExtraction | string | null | body | No | Nesting strategy for email documents: PER_PARENT_ROW, ONE_SHOT or null. See Email documents(default: null) |
emailBodySplit | boolean | body | No | Split an email body into its typed and quoted parts(default: true) |
Request
const res = await fetch(
"https://app.limai.io/api/v1/deployments/dep_abc123/configuration",
{
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
tables: [{ id: "tbl_001", name: "Invoices" }],
columns: [{ id: "col_001", description: "Unique invoice identifier" }],
}),
}
);Response
{
"success": true,
"updated": {
"tables": 1,
"columns": 1
},
"tableIds": [
"tbl_001"
],
"columnIds": [
"col_001"
]
}Reasoning level
reasoningLevel controls how much the model thinks before it answers. It takes three values — LOW, MEDIUM and HIGH — and defaults to LOW.
The level is translated per model, because providers expose reasoning differently. On the Gemini 3.x models behind the LIMAI_* aliases it becomes a thinkingLevel, clamped up to whatever floor the model enforces; on models that only accept a numeric budget it becomes a token budget.
reasoningLevel | Gemini 3.x thinkingLevel | Numeric-budget models |
|---|---|---|
LOW | the model's lowest supported level (minimal, or low where minimal is rejected) | 1024 tokens |
MEDIUM | medium | 4096 tokens |
HIGH | high | 16384 tokens |
Two fields on this endpoint are deprecated and exist only so existing integrations keep working:
thinkingBudget— an integer token budget. Still accepted, and mapped onto areasoningLevel:-1or a value above 6144 becomesHIGH, 2049–6144 becomesMEDIUM, and anything else becomesLOW. SendingreasoningLevelalongside it takes precedence.temperature— accepted and ignored. Every extraction runs at the model default of1.0: the models behind the current aliases discard sampling parameters, and upcoming model generations reject requests that carry one. The field is still validated (0–2) and reads back as1in configuration responses, but it no longer changes anything and is scheduled for removal.
Prefer reasoningLevel.
Nested extraction
nestedExtraction controls how a deployment extracts the child tables of a document. It takes two values — PER_PARENT_ROW and ONE_SHOT — and defaults to PER_PARENT_ROW.
nestedExtraction | How the document is read |
|---|---|
PER_PARENT_ROW | The parent table is extracted first, then the child rows of each parent row in a call of their own. |
ONE_SHOT | The parent table and its child tables come back from a single call. |
ONE_SHOT is supported by the single-shot extraction engine only. A request that leaves the deployment with nestedExtraction: "ONE_SHOT" alongside a bigTableExtraction other than OFF, or alongside useDynamicMapping: true, fails and nothing is updated. The check reads the stored configuration for whatever the request omits, so sending nestedExtraction on its own is enough to trip it.
{
"error": "nestedExtraction=ONE_SHOT is only supported by the single-shot extraction engine; it cannot be combined with bigTableExtraction=BASIC"
}Email documents
Two fields on this endpoint apply only when the document being extracted is an email — an .eml upload. Every other document type is unaffected by both.
emailNestedExtraction is the nesting strategy for those documents, and it overrides nestedExtraction for them. It accepts PER_PARENT_ROW, ONE_SHOT and null, and defaults to null, which resolves to the email lane default of ONE_SHOT — one model call per email. Set it to PER_PARENT_ROW to opt this deployment's email lane out: one-shot extracts fewer child rows than per-parent-row, so a schema whose child rows are recall-sensitive is the case for the override. Emails always run on the single-shot engine, so bigTableExtraction and useDynamicMapping do not constrain this field the way they constrain nestedExtraction.
emailBodySplit splits an email body into the part the sender typed and the quoted thread beneath it, and defaults to true.
const res = await fetch(
"https://app.limai.io/api/v1/deployments/dep_abc123/configuration",
{
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ emailNestedExtraction: "PER_PARENT_ROW" }),
}
);Both fields are returned under extractionSchema by the GET configuration endpoint.
Changing a slug
A slug is a stable external contract: other systems address your columns by it, and rewriting one breaks them the moment it lands. There is no human in an API call to warn, so this endpoint asks the caller to say it meant it.
Every supplied slug is compared against the stored value:
| Supplied slug | Result |
|---|---|
| Identical to the stored slug | No-op. The slug is not rewritten and no flag is needed. |
| Different from the stored slug | Requires confirmSlugChange: true at the top level of the request body. Without it the request fails with 400 and nothing is updated. |
| Omitted | The slug is left untouched. |
The identical case is what makes read-modify-write safe. Integrations routinely GET the configuration, change one field, and PATCH the whole object back — the round-tripped slug matches what is stored, so it costs nothing and needs no flag. A config-sync script only trips the 400 when it is genuinely about to rewrite a slug, which is exactly the moment somebody should be forced to confirm.
confirmSlugChange is a single top-level flag, not a per-column one: it confirms every slug change in the request.
const res = await fetch(
"https://app.limai.io/api/v1/deployments/dep_abc123/configuration",
{
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
columns: [{ id: "col_001", slug: "invoice_no" }],
confirmSlugChange: true,
}),
}
);A confirmed slug must still match ^[a-z][a-z0-9_]{0,63}$ and be unused within its scope — its table for a column slug, its deployment for a table slug. Changing a slug cascades nowhere: no data moves, no alias is kept, and consumers of the old slug start failing immediately.
Error Responses
Slug change not confirmed:
{
"error": "A slug is a stable external contract that integrations reference, and changing it breaks them immediately. Set confirmSlugChange: true to confirm. Supplying a slug identical to the stored one is always accepted.",
"changedSlugs": [
{
"id": "col_001",
"slug": "invoice_no"
}
]
}Slug does not match the required format:
{
"error": "Some slugs do not match the required format ^[a-z][a-z0-9_]{0,63}$",
"invalidSlugs": [
{
"id": "col_001",
"slug": "Invoice-No"
}
]
}Slug already taken within its scope:
{
"error": "Some slugs are already in use within their scope",
"conflictingSlugs": [
{
"id": "col_001",
"slug": "invoice_number"
}
]
}