Limai Docs
API ReferenceDeployments

Configuration

Get or update deployment configuration including schema, tables, and columns

Get Configuration

GET/api/v1/deployments/{deploymentId}/configuration

Returns 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

NameTypeInRequiredDescription
deploymentIdstringpathYesThe 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

Response200
{
  "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:

IdentifierWhy it is not an address
nameA display label. Renaming a column is routine and silently breaks anything that matched on the old name.
idStable 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.

FieldTypeDescription
tables[].slugstring | nullStable identifier for the table, unique within the deployment.
tables[].columns[].slugstring | nullStable 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

PATCH/api/v1/deployments/{deploymentId}/configuration

Update table and column configurations for a model or deployment. You can update table names, instructions, and column properties in a single request.

Parameters

NameTypeInRequiredDescription
deploymentIdstringpathYesThe model or deployment ID
tablesarraybodyNoArray of table updates with id and fields to update (name, slug, instructions)
columnsarraybodyNoArray of column updates with id and fields to update (name, slug, description, type, dateFormat, measurementType, isList, isKey, etc.)
confirmSlugChangebooleanbodyNoRequired only when a supplied slug differs from the stored one. See Changing a slug(default: false)
reasoningLevelstringbodyNoHow deeply the model reasons before answering: LOW, MEDIUM or HIGH(default: LOW)
nestedExtractionstringbodyNoHow child tables are extracted: PER_PARENT_ROW or ONE_SHOT. See Nested extraction(default: PER_PARENT_ROW)
emailNestedExtractionstring | nullbodyNoNesting strategy for email documents: PER_PARENT_ROW, ONE_SHOT or null. See Email documents(default: null)
emailBodySplitbooleanbodyNoSplit 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

Response200
{
  "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.

reasoningLevelGemini 3.x thinkingLevelNumeric-budget models
LOWthe model's lowest supported level (minimal, or low where minimal is rejected)1024 tokens
MEDIUMmedium4096 tokens
HIGHhigh16384 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 a reasoningLevel: -1 or a value above 6144 becomes HIGH, 2049–6144 becomes MEDIUM, and anything else becomes LOW. Sending reasoningLevel alongside it takes precedence.
  • temperature — accepted and ignored. Every extraction runs at the model default of 1.0: the models behind the current aliases discard sampling parameters, and upcoming model generations reject requests that carry one. The field is still validated (02) and reads back as 1 in 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.

nestedExtractionHow the document is read
PER_PARENT_ROWThe parent table is extracted first, then the child rows of each parent row in a call of their own.
ONE_SHOTThe 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.

Response400
{
  "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 slugResult
Identical to the stored slugNo-op. The slug is not rewritten and no flag is needed.
Different from the stored slugRequires confirmSlugChange: true at the top level of the request body. Without it the request fails with 400 and nothing is updated.
OmittedThe 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:

Response400
{
  "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:

Response400
{
  "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:

Response400
{
  "error": "Some slugs are already in use within their scope",
  "conflictingSlugs": [
    {
      "id": "col_001",
      "slug": "invoice_number"
    }
  ]
}

On this page