Limai Docs
API ReferenceValidation Scripts

Push Validation Script

Upload a validation script and register it on a model or deployment in one call

POST/api/v1/extraction-schema/{extractionSchemaId}/validation-scripts

Uploads the script source to storage and registers (or updates) its database record. This is the only step needed to add a custom validation — no separate storage or database credentials are involved.

The endpoint is idempotent on key and content-addressed on source. The action field in the response tells you what happened:

actionMeaning
createdNo script with this key existed; stored as version 1. Responds 201.
new-versionThe source changed; stored under a new version and the record now points at it.
metadata-updatedThe source is byte-identical but name, description, behavior, enabled or timeoutMs differed. No new version.
unchangedSource and metadata already match. Nothing was written.

Each version is stored under its own key, so earlier versions are never overwritten.

Requires the EDIT_MODELS permission (OWNER or DEVELOPER).

Parameters

NameTypeInRequiredDescription
extractionSchemaIdstringpathYesThe model or deployment's extraction schema ID, returned when listing a project's deployments.
keystringbodyYesStable identifier for the script — lowercase letters, digits and hyphens only
namestringbodyYesHuman-readable name shown in the UI
descriptionstringbodyNoWhat the script checks
behaviorstringbodyNoSURFACE (report only) or RESEND (request a re-extraction of failing cells). Defaults to SURFACE
sourcestringbodyYesThe full ES module source, up to 512 KB
enabledbooleanbodyNoWhether the script runs after extraction. Defaults to true
timeoutMsnumberbodyNoPer-run timeout in milliseconds, 1000–600000. Defaults to 60000

Request

import { readFileSync } from "node:fs";

const res = await fetch(
"https://app.limai.io/api/v1/extraction-schema/esch_abc123/validation-scripts",
{
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    key: "vin-checksum",
    name: "VIN format (ISO 3779)",
    description: "Validates the VIN against the ISO 3779 format",
    behavior: "RESEND",
    source: readFileSync("./script.mjs", "utf-8"),
  }),
}
);
const { action, script } = await res.json();

Response

Response201
{
  "action": "created",
  "script": {
    "id": "cvs_abc123",
    "extractionSchemaId": "esch_abc123",
    "key": "vin-checksum",
    "name": "VIN format (ISO 3779)",
    "description": "Validates the VIN against the ISO 3779 format",
    "behavior": "RESEND",
    "s3Key": "custom-validations/esch_abc123/vin-checksum/v1-9f2b7c1d4e05.mjs",
    "version": 1,
    "contentHash": "9f2b...",
    "enabled": true,
    "timeoutMs": 60000,
    "createdAt": "2025-01-01T00:00:00.000Z",
    "updatedAt": "2025-01-01T00:00:00.000Z"
  }
}

Script contract

The script is a standalone ES module executed with node script.mjs input.json. It reads the extracted data from the path in process.argv[2] and writes a JSON { findings, breakdown? } object to stdout.

import { readFileSync } from 'node:fs'
import { pathToFileURL } from 'node:url'

function validate(input) {
  const findings = []
  for (const row of input.rows) {
    // inspect row.cells[columnId].value against input.schema.tables
  }
  return { findings }
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  const input = JSON.parse(readFileSync(process.argv[2], 'utf8'))
  process.stdout.write(JSON.stringify(validate(input)))
}

Each finding has the shape:

{
  "ruleKey": "vin-length",
  "scope": "ROW",
  "tableId": "tbl_abc",
  "rowId": "row_abc",
  "columnIds": ["col_vin"],
  "passed": false,
  "message": "VIN has 16 characters, expected exactly 17",
  "expected": "17 characters",
  "actual": "WVWZZZ1KZAW12345",
  "resend": true,
  "resendHint": "Re-read field E and extract all 17 characters"
}

On this page