> For the complete documentation index, see [llms.txt](https://developer.celigo.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.celigo.com/api/api-reference/exports-imports-and-tools/tools.md).

# Tools

Tools are reusable processing units within integrations that encapsulate input transformation, conditional routing, output mapping, and data enrichment logic behind an input/output contract. They can be referenced from flows, APIs, AI agents, MCP servers, and other tools to promote modularity and reuse.

### Tool schema

## The Tool object

````json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"components":{"schemas":{"Tool":{"type":"object","required":["_id","name","_integrationId","createdAt","lastModified"],"description":"Tool object as returned by the API.","allOf":[{"$ref":"#/components/schemas/ToolBase"},{"$ref":"#/components/schemas/ResourceResponse"},{"type":"object","properties":{"_sourceId":{"type":"string","format":"objectId","readOnly":true,"description":"Origin resource ID when this tool was created by cloning or installing a template."},"draftExpiresAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when a draft tool auto-deletes. Server-computed when `draft` is set at\ncreation."}}}]},"ToolBase":{"type":"object","description":"Writable tool fields shared by the request and response schemas.","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable name for the tool.\n\nDisplayed in the UI and used to identify the tool's purpose.\n"},"description":{"type":"string","maxLength":5120,"description":"Optional detailed description of what the tool does.\n\nUse this to document the tool's purpose, expected inputs/outputs,\nand any special considerations.\n"},"_integrationId":{"type":"string","format":"objectId","description":"Reference to the integration this tool belongs to.\n\nEvery tool must be associated with an integration. The integration\ndetermines the scope and access controls for the tool.\n"},"input":{"$ref":"#/components/schemas/Input"},"output":{"$ref":"#/components/schemas/Output"},"routers":{"type":"array","description":"Optional routers for conditional processing logic.\n\nRouters allow you to direct input data to different processing branches\nbased on filter criteria or script logic. Tools only support\n\"first_matching_branch\" routing strategy.\n\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal value to exit the tool.\n\nA tool saves without `routers`, but it can't run: `POST /v1/tools/{_id}/invoke`\nand a test run answer 400 `tool_empty_router` (\"Tool has no steps configured.\nAdd at least one step before executing the tool.\"). A full PUT that omits\n`routers` removes the steps the same way. Put linear steps in one pass-through\nrouter with a single branch.\n","items":{"$ref":"#/components/schemas/Router"}},"aiDescription":{"$ref":"#/components/schemas/AIDescription"},"draft":{"type":"boolean","description":"When true, this tool is a draft that auto-deletes when its expiry passes\n(`draftExpiresAt` in the response). Set at creation; an update can clear the\nflag but never set it."}}},"Input":{"type":"object","description":"Configuration for the tool's input processing.\n\nDefines the expected input structure, optional transformations to apply\nbefore routing, and mock data for testing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the input configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the expected input data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the expected input data structure.\n\nUsed for validation, documentation, and AI-assisted tooling.\nMust be a valid JSON Schema document. Leave `$schema` out or name draft-07: a\n`$schema` naming draft 2020-12 fails the save with 422 \"Invalid input.schema -\nno schema with key or ref ...\". An `$id` saves.\n\nEach invoke's input is validated against it. A missing `required` property\nrefuses the call with 422 `tool_input_schema_validation_failed` (\"Tool input\nvalidation failed: must have required property '<name>'\"), and a failed\n`pattern` with 422 \"Tool input validation failed: <field>: must match pattern\n...\". A property made required later therefore breaks callers that don't send\nit.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; invocation input validates the same either way.\nUnset opens the visual editor, and an edit there rebuilds `schema` from the field list,\nkeeping only `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"transform":{"$ref":"#/components/schemas/Transform"},"traceKeyTemplate":{"type":"string","maxLength":1024,"description":"Handlebars template evaluated against the invocation input (`record`) to produce\nthe `traceKey` stored on each run-history entry, so an invocation can be found again\nwith the `traceKeyPrefix` filter on `GET /v1/tools/{_id}/invocations`. Omit it and\nentries carry an empty `traceKey`."},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Input stage until this time: while it lies in\nthe future, every record entering the tool is written to\n`GET /v1/tools/{_id}/tool_input/requests` with its input-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/input/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Sample input saved with the tool. `POST /v1/tools/{_id}/test/run` doesn't read it:\nsend it as that call's `input`, or the run starts from an empty record. Maximum\nsize: 1MB.\n","additionalProperties":true}}},"Transform":{"type":"object","description":"Configuration for transforming data during processing operations. This object enables\nreshaping of records.\n\n**Transformation capabilities**\n\nCeligo's transformation engine offers powerful features for data manipulation:\n- Precise field mapping with JSONPath expressions\n- Support for any level of nested arrays\n- Formula-based field value generation\n- Dynamic references to flow and integration settings\n\n**Implementation approaches**\n\nThere are two distinct transformation mechanisms available:\n\n**Rule-Based Transformation (`type: \"expression\"`)**\n- **Best For**: Most transformation scenarios from simple to complex\n- **Capabilities**: Field mapping, formula calculations, lookups, nested data handling\n- **Advantages**: Visual configuration, no coding required, intuitive interface\n- **Configuration**: Define rules in the `expression` object\n- **Use When**: You have clear mapping requirements or need to reshape data structure\n\n**Script-Based Transformation (`type: \"script\"`)**\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Capabilities**: Full programmatic control, custom processing, complex business rules\n- **Advantages**: Maximum flexibility, can implement any transformation logic\n- **Configuration**: Reference a script in the `script` object\n- **Use When**: Visual transformation tools aren't sufficient for your use case\n","properties":{"type":{"type":"string","description":"Determines which transformation mechanism to use. This choice affects which properties\nmust be configured and how transformation logic is implemented.\n\n**Available types**\n\n**Rule-Based Transformation (`\"expression\"`)**\n- **Required Config**: The `expression` object with mapping definitions\n- **Behavior**: Applies declarative rules to reshape data\n- **Best For**: Most transformation scenarios from simple to complex\n- **Advantages**: Visual configuration, no coding required\n\n**Script-Based Transformation (`\"script\"`)**\n- **Required Config**: The `script` object with _scriptId and function\n- **Behavior**: Executes custom JavaScript to transform data\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Advantages**: Maximum flexibility, can implement any logic\n\n**Implementation guidance**\n\n1. For standard data transformations, use `\"expression\"`\n2. For complex logic or specialized processing, use `\"script\"`\n3. When selecting a type, you must configure the corresponding object:\n    - `type: \"expression\"` requires the `expression` object\n    - `type: \"script\"` requires the `script` object\n","enum":["expression","script"]},"expression":{"type":"object","description":"Configuration for declarative rule-based transformations. This object enables reshaping data\nwithout requiring custom code.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"expression\" and should not be\nconfigured otherwise. It provides a standardized way to define transformation rules that\ncan map, modify, and generate data elements.\n\n**Implementation guidance**\n\nThe expression system uses a rule-based approach where:\n- Field mappings define how input data is transformed to target fields\n- Formulas can be used to calculate or generate new values\n- Lookups can enrich data by fetching related information\n- Mode determines how records are processed (create new or modify existing)\n","properties":{"version":{"type":"string","description":"Version of the expression format. Determines which rules\nproperty contains the transformation logic.\n","enum":["1","2"]},"rules":{"type":"array","description":"Transformation rules for version 1 expressions. An array of\nrule groups; each group is an array of field-mapping objects.\nMost transforms have a single group. Present when `version`\nis `\"1\"`. The output record contains ONLY the generated\nfields — every unmapped field is dropped (v1 has no\nequivalent of Transform 2.0's `modify` mode), and the\nrecord's trace key does not survive the rebuild.\n","items":{"type":"array","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path to read from. Supports multiple\nsyntaxes: bare field names (`id`), dot notation\n(`fulfillment.shipment_id`), slash-prefixed paths\nfor XML (`/FeedProcessingStatus`), wildcards (`*.id`,\n`*.[Internal ID]`), and array indexing (`SDF[0]`).\n"},"generate":{"type":"string","description":"Target field name to write to. Typically a bare name\n(`id`) or dot path (`SDF.Filter.ID`).\n"},"key":{"type":"string","description":"Auto-generated identifier for this rule, used by the\nUI to track individual rules for editing and reordering.\n"}},"required":["extract","generate"]}}},"rulesTwoDotZero":{"type":"object","description":"Configuration for version 2 transformation rules. This object contains the core logic\nfor how data is mapped, enriched, and transformed.\n\n**Capabilities**\n\nTransformation 2.0 provides:\n- Precise field mapping with JSONPath expressions\n- Support for deeply nested data structures\n- Formula-based field generation\n- Dynamic lookups for data enrichment\n- Multiple operating modes to fit different scenarios\n","properties":{"mode":{"type":"string","description":"Transformation mode that determines how records are handled during processing.\n\n**Available modes**\n\n**Create Mode (`\"create\"`)**\n- **Behavior**: Builds entirely new output records from inputs\n- **Use When**: Output structure differs significantly from input\n- **Advantage**: Clean slate approach, no field inheritance\n\n**Modify Mode (`\"modify\"`)**\n- **Behavior**: Makes targeted edits to existing records\n- **Use When**: Output structure should remain similar to input\n- **Advantage**: Preserves unmapped fields from the original record\n","enum":["create","modify"]},"mappings":{"$ref":"#/components/schemas/Mappings"},"lookups":{"allOf":[{"description":"Shared lookup tables used across all mappings defined in the transformation rules.\n\n**Purpose**\n\nLookups provide centralized value translation that can be referenced from any mapping\nin your transformation configuration. They enable consistent translation of codes, IDs,\nand values between systems without duplicating translation logic.\n\n**Usage in transformations**\n\nLookups are particularly valuable in transformations for:\n\n- **Data Normalization**: Standardizing values from diverse source systems\n- **Code Translation**: Converting between different coding systems (e.g., status codes)\n- **Field Enrichment**: Adding descriptive values based on ID or code lookups\n- **Cross-Reference Resolution**: Mapping identifiers between integrated systems\n\n**Implementation**\n\nLookups are defined once in this array and referenced by name in mappings:\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"statusMapping\",\n    \"map\": {\n      \"A\": \"Active\",\n      \"I\": \"Inactive\",\n      \"P\": \"Pending\"\n    },\n    \"default\": \"Unknown Status\"\n  }\n]\n```\n\nThen referenced in mappings using the lookupName property:\n\n```json\n{\n  \"generate\": \"status\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.statusCode\",\n  \"lookupName\": \"statusMapping\"\n}\n```\n\nThe system automatically applies the lookup during transformation processing.\n\nFor complete details on lookup properties and behavior, see the Lookups schema.\n"},{"$ref":"#/components/schemas/Lookups"}]},"inputContext":{"type":"string","enum":["record","envelope"],"description":"Controls the JSON shape the transformTwoDotZero processor\nevaluates `mappings[].extract` JSONPath values against at\nflow runtime. Applies only to Transform 2.0 (v2,\n`rulesTwoDotZero`); v1 transforms (the `rules` array on\n`transform.expression.rules`) and script-mode transforms\nignore this field.\n"}}}}},"script":{"type":"object","description":"Configuration for programmable script-based transformations. This object enables complex, custom\ntransformation logic beyond what expression-based transformations can provide.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"script\" and should not be configured\notherwise. It provides a way to execute custom JavaScript code to transform data according to\nspecialized business rules or complex algorithms.\n\n**Implementation approach**\n\nScript-based transformation works by:\n1. Executing the specified function from the referenced script\n2. Passing input data to the function\n3. Using the function's return value as the transformed output\n\n**Common use cases**\n\nScript transformation is ideal for:\n- Complex business logic that can't be expressed through mappings\n- Algorithmic transformations requiring computation\n- Dynamic transformations based on external factors\n- Legacy system data format compatibility\n- Multi-stage processing with intermediate steps\n\nOnly use script-based transformation when expression-based transformation is insufficient.\nScript transformation requires maintaining custom code, which adds complexity to the integration.\n","properties":{"_scriptId":{"type":"string","description":"Reference to a predefined script resource containing the transformation logic.\n\nThe referenced script should contain the function specified in the\n'function' property. It must be a script in this account: any other id fails the save\nwith 422 (\"Only a script owned by the user or a script shared by the Integration App can\nbe used at transform.script._scriptId.\").\n","format":"objectid"},"function":{"type":"string","description":"Name of the function within the script to execute for transformation. This function\nmust exist in the script referenced by _scriptId.\n"}}}}},"Mappings":{"type":"array","description":"Array of field mapping configurations for transforming data from one format into another.\n\n**Guidance**\n\nThis schema is designed around RECURSION as its core architectural principle. Understanding this recursive\nnature is essential for building effective mappings:\n\n1. The schema is self-referential by design - a mapping can contain nested mappings of the same structure\n2. Complex data structures (nested objects and arrays of objects, to any depth) are ALL\n   handled through this recursive pattern; an array of arrays is not (build one in a script hook)\n3. Each mapping handles one level of the data structure; deeper levels are handled by nested mappings\n\nWhen generating mappings programmatically:\n- For simple fields (string, number, boolean): Create single mapping objects\n- For objects: Create a parent mapping with nested 'mappings' array containing child field mappings\n- For arrays: Use 'buildArrayHelper' with extract paths defining array inputs and\n  recursive 'mappings' to define object structures\n\nThe system will process these nested structures recursively during runtime, ensuring proper construction\nof complex hierarchical data while maintaining excellent performance.\n\nOn save, a leaf row with neither a source (`extract`, `hardCodedValue`, `lookupName`) nor a\ndestination (`generate`) is dropped silently, at every nesting level, and so is an object or\nobject-array row left with no source, no destination and no remaining child `mappings` or\n`buildArrayHelper` entries; a row with only one of the two, and a source-less, destination-less\nwrapper that still has children, are kept.\n\nThe shape of the array is the mapper's **JSON structure**. Named top-level items produce an object\nrecord (`record { }`). Exactly one top-level item with `dataType: objectarray` and no `generate` is\nthe `rows [ ]` structure: the mapped record is an array — usually one element per element of the\nsource array that root's `buildArrayHelper` iterates — Handlebars templates such as `http.body`\nbind it as `rows` instead of `record`, and the mapping editor shows that row's destination disabled.\n","items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}}},"items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}},"Lookups":{"type":"array","description":"Configuration for value-to-value transformations using lookup tables.\n\n**Purpose**\n\nLookups provide a way to translate values from one system to another. They transform\ninput values into output values using either static mapping tables or\ndynamic lookup caches.\n\n**Lookup mechanisms**\n\nThere are two distinct lookup mechanisms available:\n\n1. **Static Lookups**: Define a simple key-value map object and store it as part of your resource\n   - Best for: Small, fixed sets of values that rarely change\n   - Implementation: Configure the `map` object with input-to-output value mappings\n   - Example: Country codes, status values, simple translations\n\n2. **Dynamic Lookups**: Reference an existing 'Lookup Cache' resource in your Celigo account\n   - Best for: Large datasets, frequently changing values, or complex reference data\n   - Implementation: Configure `_lookupCacheId` to reference cached data maintained independently\n   - Example: Product catalogs, customer databases, pricing information\n\n**Property usage**\n\nThere are two mutually exclusive ways to configure lookups, depending on which mechanism you choose:\n\n1. **For Static Mappings**: Configure the `map` property with a direct key-value object\n   ```json\n   \"map\": {\"US\": \"United States\", \"CA\": \"Canada\"}\n   ```\n\n2. **For Dynamic Lookups**: Configure the following properties:\n   - `_lookupCacheId`: Reference to the lookup cache resource\n   - `extract`: JSON path to extract specific value from the returned lookup object\n\n**When to use**\n\nLookups are ideal for:\n\n1. **Value Translation**: Mapping codes or IDs to human-readable values\n\n2. **Data Enrichment**: Adding related information to records during processing\n\n3. **Normalization**: Ensuring consistent formatting of values across systems\n\n**Implementation details**\n\nLookups can be referenced in:\n\n1. **Field Mappings**: Direct use in field transformation configurations\n\n2. **Handlebars Templates**: Use within templates with the syntax:\n   ```\n   {{lookup 'lookupName' record.fieldName}}\n   ```\n\n**Example usage**\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"countryCodeToName\",\n    \"map\": {\n      \"US\": \"United States\",\n      \"CA\": \"Canada\",\n      \"UK\": \"United Kingdom\"\n    },\n    \"default\": \"Unknown Country\",\n    \"allowFailures\": true\n  },\n  {\n    \"name\": \"productDetails\",\n    \"_lookupCacheId\": \"60a2c4e6f321d800129a1a3c\",\n    \"extract\": \"$.details.price\",\n    \"allowFailures\": false\n  }\n]\n```\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for the lookup table within this configuration.\n\nThis name must be unique within the scope where the lookup is defined and is used to reference\nthe lookup in handlebars templates with the syntax {{lookup 'name' value}}.\n\nChoose descriptive names that indicate the transformation purpose, such as:\n- \"countryCodeToName\" for country code to full name conversion\n- \"statusMapping\" for status code translations\n- \"departmentCodes\" for department code to name mapping\n"},"map":{"type":["object","null"],"description":"The lookup mapping table as key-value pairs. The platform stores `null`\nhere on dynamic lookups, which resolve values at runtime instead of\nfrom a static table.\n\nThis object contains the input values as keys and their corresponding\noutput values. When a input value matches a key in this object,\nit will be replaced with the corresponding value.\n\nThe map should be kept to a reasonable size (typically under 100 entries)\nfor optimal performance. For larger mapping requirements, consider using\ndynamic lookups instead.\n\nMaps can include:\n- Simple code to name conversions: {\"US\": \"United States\"}\n- Status transformations: {\"A\": \"Active\", \"I\": \"Inactive\"}\n- ID to name mappings: {\"100\": \"Marketing\", \"200\": \"Sales\"}\n\nValues can be strings, numbers, or booleans, but all are stored as strings\nin the configuration.\n"},"_lookupCacheId":{"type":"string","description":"Reference to a LookupCache resource that contains the reference data for the lookup.\n\n**Purpose**\n\nThis field connects the lookup to an external data source that has been cached in the system.\nUnlike static lookups that use the `map` property, dynamic lookups can reference large datasets\nor frequently changing information without requiring constant updates to the integration.\n\n**Implementation details**\n\nThe LookupCache resource referenced by this ID contains:\n- The data records to be used as a reference source\n- Configuration for how the data should be indexed and accessed\n- Caching parameters to balance performance with data freshness\n\n**Usage patterns**\n\nCommonly used to reference:\n- Product catalogs or SKU databases\n- Customer or account information\n- Pricing tables or discount rules\n- Complex business logic lookup tables\n\nFormat: 24-character hexadecimal string (MongoDB ObjectId)\n","format":"objectid"},"extract":{"type":"string","description":"JSON path expression that extracts a specific value from the cached lookup object.\n\n**Purpose**\n\nWhen using dynamic lookups with a LookupCache, this JSON path identifies which field to extract\nfrom the cached object after it has been retrieved using the lookup key.\n\n**Implementation details**\n\n- Must use JSON path syntax (similar to mapping extract fields)\n- Operates on the cached object returned by the lookup operation\n- Examples:\n  - \"$.name\" - Extract the name field from the top level\n  - \"$.details.price\" - Extract a nested price field\n  - \"$.attributes[0].value\" - Extract a value from the first element of an array\n\n**Usage scenario**\n\nWhen a lookup cache contains complex objects:\n```json\n// Cache entry for key \"PROD-123\":\n{\n  \"id\": \"PROD-123\",\n  \"name\": \"Premium Widget\",\n  \"details\": {\n    \"price\": 99.99,\n    \"currency\": \"USD\",\n    \"inStock\": true\n  }\n}\n```\n\nSetting extract to \"$.details.price\" would return 99.99 as the lookup result.\n\nIf no extract is provided, the entire cached object is returned as the lookup result.\n"},"default":{"type":["string","null"],"description":"Default value to use when the source value is not found in the lookup map.\nThe platform stores `null` here when no default is configured.\n\nThis value is used as a fallback when:\n1. The source value doesn't match any key in the map\n2. allowFailures is true or omitted\n\nSetting an appropriate default helps prevent flow failures due to unexpected\nvalues and provides predictable behavior for edge cases.\n\nCommon default patterns include:\n- Descriptive unknowns: \"Unknown Country\", \"Unspecified Status\"\n- Original value indicators: \"{Original Value}\", \"No mapping found\"\n- Neutral values: \"Other\", \"N/A\", \"Miscellaneous\"\n\nA save refuses a default together with an explicit `allowFailures: false`\n(422 `Lookups with default set can not have allowFailures explicitly set to\nfalse.`): a lookup either fails a miss (`allowFailures: false`, no default) or\nfalls back to its default.\n"},"allowFailures":{"type":["boolean","null"],"description":"When true, missing lookup values will use the default value rather than causing an error.\n\n**Behavior control**\n\nThis field determines how the system handles source values that don't exist in the map:\n\n- true: Use the default value for missing mappings and continue processing\n- false: Treat missing mappings as errors, failing the record (`lookup_error` for a\n  static map, `lookupcache_evaluation_failed` for a lookup cache)\n- omitted or null: the record doesn't fail either. On an import a static lookup writes\n  its default if it has one, and a static or cache lookup without one leaves the\n  mapped field out of the output, with no error\n\n**Recommendation**\n\nSet this to true when:\n- New source values might appear over time\n- Data quality issues could introduce unexpected values\n- Processing should continue even with imperfect mapping\n\nSet this to false when:\n- Complete data accuracy is critical\n- All possible source values are known and controlled\n- Missing mappings indicate serious data problems that should be addressed\n\nA default raises no error: records that miss go out with the default value and\nnothing reports them. Use true only when that fallback is acceptable at the\ndestination, and set false explicitly when a missing value must stop the record.\n"}}}},"Output":{"type":"object","description":"Configuration for the tool's output processing.\n\nDefines how the tool's results are mapped, transformed, and enriched\nbefore being returned. Supports field mappings, lookups for data\nenrichment, and custom script hooks for pre/post-mapping processing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the output configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the output data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the output data structure.\n\nUsed for documentation and validation of the tool's output.\nMust be a valid JSON Schema document.\n\nThe mapped output is validated against it. While the stored schema holds\n`additionalProperties: false`, `POST /v1/tools/{_id}/invoke` answers 422 \"Tool\noutput validation failed: must NOT have additional properties\" when the output\nmappings write a field the schema doesn't list.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; the output validates the same either way. Unset\nopens the visual editor, and an edit there rebuilds `schema` from the field list, keeping\nonly `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"mappings":{"description":"Field mappings to transform data into the output format.\n\nMaps data from processing results to the output structure.\nUses Celigo's standard mapping format with extract/generate field paths —\na flat array of mapping entries (each entry may recurse via its own\nnested ``mappings`` for object/array structures).\n","allOf":[{"$ref":"#/components/schemas/Mappings"}]},"lookups":{"type":"array","description":"Lookup tables for data enrichment during output processing.\n\nStatic key-value mappings used to translate values (e.g., status codes,\ncategory names) during output generation.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the lookup, used to reference it from mappings.\n"},"map":{"type":"object","description":"Key-value mapping object. Keys are the input values and\nvalues are the corresponding output values.\n","additionalProperties":true},"default":{"type":"string","description":"Default value returned when the input key is not found in the map.\n"},"allowFailures":{"type":"boolean","description":"Whether to continue processing if the lookup fails to find a match\nand no default is provided.\n"}}}},"hooks":{"type":"object","description":"Custom script hooks for pre- and post-mapping processing.\n\nAllows running custom JavaScript functions before and after\noutput mappings are applied.\n","properties":{"preMap":{"type":"object","description":"Script to run before applying output mappings.\n\nCan modify the data before it is mapped to the output structure.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}},"postMap":{"type":"object","description":"Script to run after applying output mappings.\n\nCan modify the final output data after mappings are applied.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}}}},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Output stage until this time: while it lies in\nthe future, every post-mapped record is written to\n`GET /v1/tools/{_id}/tool_output/requests` with its output-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/output/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Mock data for testing the tool's output processing.\n\nProvides sample data that would arrive from the routing/processing\nstage, used to test mapping and lookup logic. Maximum size: 1MB.\n","additionalProperties":true}}},"Router":{"type":"object","description":"Configuration for conditional routing within a tool.\n\nRouters evaluate input data and direct it to different processing branches\nbased on criteria. This enables complex business logic and conditional\nprocessing within the tool.\n\nUnlike flows, tools only support \"first_matching_branch\" routing strategy.\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal sink to exit the tool and return results.\n\nA router comes in two shapes, told apart by whether `routeRecordsTo` and\n`routeRecordsUsing` are set: a **branching router** sets both and has two or\nmore branches, each taking the inputs its `inputFilter` (or the router's\n`script`) matches — the node you see in the Tool builder; a **pass-through\nrouter** sets neither and has exactly one branch that every input goes\nstraight through — not drawn, it holds the steps before the first branching\nrouter and merges branches back into shared steps (branches rejoin only by\npointing `nextRouterId` at the same router).\n","properties":{"id":{"type":"string","maxLength":36,"pattern":"^[A-Za-z0-9_-]+$","description":"Unique identifier for this router within the tool.\n\nUsed to reference this router from other routers' branch `nextRouterId`.\nUse at most 36 letters, digits, underscores and hyphens: a longer id fails\nthe save with 422 (\"Router id is missing or contains more than 36 characters\nor invalid characters. Please correct the router id.\"), as on flow routers.\n"},"name":{"type":"string","maxLength":300,"description":"Human-readable name for the router.\n"},"routeRecordsTo":{"type":"string","enum":["first_matching_branch"],"description":"Routing strategy. Tools only support \"first_matching_branch\",\nwhich routes to the first branch whose criteria match the input.\nSet together with `routeRecordsUsing` on a branching router; leave\nboth unset on a one-branch pass-through router.\n"},"routeRecordsUsing":{"type":"string","enum":["input_filters","script"],"description":"How a branching router decides which branch an input matches. Set together\nwith `routeRecordsTo`; leave both unset on a one-branch pass-through router.\n"},"script":{"type":"object","description":"Script configuration when routeRecordsUsing is \"script\".\n\nThe function should return the name of the branch to route to.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name that returns the branch name"}}},"branches":{"type":"array","description":"List of branches defining different processing paths.\n\nEach branch has optional filter criteria and a set of processing steps.\nRecords are evaluated against branch criteria in order; the first\nmatching branch is selected.\n","items":{"type":"object","properties":{"name":{"type":"string","maxLength":300,"description":"Name of this branch.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of when and why this branch is selected.\n"},"branchId":{"type":"string","description":"Stable identifier for this branch within the tool, generated by\nthe builder. Used to reference the branch independently of its\nposition in the branches array (e.g., from step requests).\n"},"inputFilter":{"type":"object","description":"Filter criteria to determine if this branch should be selected.\n\nUses Celigo's expression-based filter format. Send `version` and `rules`\ndirectly on `inputFilter` — not inside the `{type: \"expression\", expression: {...}}`\nwrapper that export and import filters use. A wrapped filter is saved without an\nerror, but the branch is stored with no `inputFilter` and takes every input.\n","properties":{"version":{"type":"string","enum":["1"],"description":"Filter version"},"rules":{"type":"array","description":"Filter rules in Celigo expression-based filter format.\n\nArray-based DSL where the first element is an operator (e.g., \"equals\", \"and\", \"or\"),\nfollowed by operands which can be nested expressions.\n","items":{}}}},"nextRouterId":{"type":"string","description":"Identifier of the next router to chain to after this branch completes.\n\nUse \"outputRouter\" as a special terminal value to exit the tool\nand return the processing results.\n\nEvery branch needs one. A branch without it is saved, but every invoke is\nthen refused with 422 and every test run with 400 `tool_dangling_branch`\n(\"One or more branches are not merged back. Review the setup to ensure all\nbranches converge.\"), whichever branch the input takes. An id that names no\nrouter fails the save with 422 \"Invalid nextRouterId is defined on the branch.\nPlease correct the branch configuration.\"\n"},"pageProcessors":{"type":"array","description":"Processing steps to execute in this branch.\n\nEach processor references an export (lookup) or import resource\nfor data retrieval or submission. An export is a step at most once in a\ntool: using it again, on another branch or in the same one, fails the save\nwith 422 \"The export <id> is already included as a pageProcessor in your\nflow, and cannot be added again.\"\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["export","import"],"description":"Type of processor.\n\n- **export**: Retrieves data from an external system (lookup)\n- **import**: Sends data to an external system\n"},"_exportId":{"type":"string","format":"objectId","description":"Export resource reference (when type is \"export\")"},"_importId":{"type":"string","format":"objectId","description":"Import resource reference (when type is \"import\")"},"proceedOnFailure":{"type":"boolean","description":"When true, the later steps and the output stage still run after this\nprocessor fails; when false, they're skipped. Either way a direct\n`POST /v1/tools/{_id}/invoke` answers 422 with the failed step's error,\nwithout the output."},"setupInProgress":{"type":"boolean","description":"When true, the processor's configuration is still being\nset up in the UI and the step is not yet runnable.\n"},"responseMapping":{"type":"object","description":"Merges fields from this processor's response back onto the\nin-flight record so later processors and the tool's output\ncan read them. Extracts do NOT read the raw application\nresponse — they evaluate against the platform's canonical\nper-record envelope: for lookups (`type: \"export\"`) that is\n`{\"statusCode\", \"data\": [<result records>], \"errors\"}`\nwhere `data` holds the records AFTER the export's own\n`transform` and output `filter`, so paths must start from\n`data` and name transformed fields by their transformed\nname (e.g. `data[0].name`) — a lookup result field\nwritten without the `data` prefix resolves to nothing;\nfor imports it is `{\"id\", \"statusCode\", \"ignored\",\n\"_json\", ...<body fields flattened>}`, so use `id`,\n`_json.<path>`, or the body field itself at the root\n(`orderNumber` and `_json.orderNumber` read the same\nvalue). An extract may be a Handlebars expression over\nthe same envelope (`{{data.0.name}}`,\n`{{#if data.length}}found{{/if}}`; dot indexes only; a\nstring result); there is no `record` object in this\ncontext, so `{{record.…}}` renders nothing.\n","properties":{"fields":{"type":"array","description":"Simple field-level mappings","items":{"type":"object","properties":{"extract":{"type":"string","description":"Path within the canonical response envelope to\ncopy the value from (`data[0].x` / `data.0.x`\nfor lookups; `id`, `_json.<path>` or the\nflattened body field `<path>` for imports), or\na Handlebars expression over that envelope (dot\nindexes inside `{{ }}`, never a `record.` root).\n"},"generate":{"type":"string","description":"Field path on the in-flight record where the\nextracted value is stored (dot notation for\nnesting).\n"}}}},"lists":{"type":"array","description":"List-level mappings for array data","items":{"type":"object","properties":{"generate":{"type":"string","description":"Target list path"},"fields":{"type":"array","description":"Field-level mappings applied to each item in the list.","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path"},"generate":{"type":"string","description":"Target field path"}}}}}}}}},"hooks":{"type":"object","description":"Custom scripts for processing","properties":{"postResponseMap":{"type":"object","description":"Script to run after response mapping","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute"}}}}}}}}}}}}},"AIDescription":{"type":"object","description":"AI-generated descriptions and documentation for the resource.\n\nThis object contains automatically generated content that helps users\nunderstand the purpose, behavior, and configuration of the resource without\nrequiring them to analyze the technical details. The AI-generated content\nis sanitized and safe for display in the UI.\n","properties":{"summary":{"type":["string","null"],"description":"Brief AI-generated summary of the resource's purpose and functionality.\n\nThis concise description provides a quick overview of what the resource does,\nwhat systems it interacts with, and its primary role in the integration.\nThe summary is suitable for display in list views, dashboards, and other\ncontexts where space is limited.\n\nMaximum length: 10KB\n"},"detailed":{"type":["string","null"],"description":"Comprehensive AI-generated description of the resource's functionality.\n\nThis detailed explanation covers the resource's purpose, configuration details,\ndata flow patterns, filtering logic, and other technical aspects. It provides\nin-depth information suitable for documentation, tooltips, or detailed views\nin the administration interface.\n\nThe content may include HTML formatting for improved readability.\n\nMaximum length: 10KB\n"},"generatedOn":{"type":["string","null"],"format":"date-time","description":"Timestamp indicating when the AI description was generated.\n\nThis field helps track the freshness of the AI-generated content and\ndetermine when it might need to be regenerated due to changes in the\nresource's configuration or behavior.\n\nThe timestamp is recorded in ISO 8601 format with UTC timezone (Z suffix).\n"}}},"ResourceResponse":{"type":"object","description":"Response","properties":{"_id":{"type":"string","format":"objectId","readOnly":true,"description":"Unique identifier for the resource. Format is a 24-character hexadecimal string."},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was created. Set automatically and cannot be modified."},"lastModified":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was last updated. Changes whenever any property is modified."},"deletedAt":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the resource was soft-deleted. When null or absent, the resource is active."}},"required":["_id"]}}}}
````

## List tools

> Returns all tools in the account. Filter by \`\_integrationId\` to scope\
> results to a single integration.

````json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"Include":{"name":"include","in":"query","required":false,"description":"Comma-separated list of fields to project into each returned record.\nTriggers summary projection: the response contains a minimal identity\nset (`_id`, `name`, plus resource-specific fields) with the requested\nfields added on top. Supports dot notation for nested fields.\nMutually exclusive with `exclude`.","schema":{"type":"string"}},"Exclude":{"name":"exclude","in":"query","required":false,"description":"Comma-separated list of fields to strip from the default response.\nUnlike `include`, does not trigger summary projection — returns the\nfull record with the named fields removed. Protected identity fields\n(e.g. `name`) cannot be stripped. Mutually exclusive with `include`.","schema":{"type":"string"}}},"schemas":{"Tool":{"type":"object","required":["_id","name","_integrationId","createdAt","lastModified"],"description":"Tool object as returned by the API.","allOf":[{"$ref":"#/components/schemas/ToolBase"},{"$ref":"#/components/schemas/ResourceResponse"},{"type":"object","properties":{"_sourceId":{"type":"string","format":"objectId","readOnly":true,"description":"Origin resource ID when this tool was created by cloning or installing a template."},"draftExpiresAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when a draft tool auto-deletes. Server-computed when `draft` is set at\ncreation."}}}]},"ToolBase":{"type":"object","description":"Writable tool fields shared by the request and response schemas.","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable name for the tool.\n\nDisplayed in the UI and used to identify the tool's purpose.\n"},"description":{"type":"string","maxLength":5120,"description":"Optional detailed description of what the tool does.\n\nUse this to document the tool's purpose, expected inputs/outputs,\nand any special considerations.\n"},"_integrationId":{"type":"string","format":"objectId","description":"Reference to the integration this tool belongs to.\n\nEvery tool must be associated with an integration. The integration\ndetermines the scope and access controls for the tool.\n"},"input":{"$ref":"#/components/schemas/Input"},"output":{"$ref":"#/components/schemas/Output"},"routers":{"type":"array","description":"Optional routers for conditional processing logic.\n\nRouters allow you to direct input data to different processing branches\nbased on filter criteria or script logic. Tools only support\n\"first_matching_branch\" routing strategy.\n\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal value to exit the tool.\n\nA tool saves without `routers`, but it can't run: `POST /v1/tools/{_id}/invoke`\nand a test run answer 400 `tool_empty_router` (\"Tool has no steps configured.\nAdd at least one step before executing the tool.\"). A full PUT that omits\n`routers` removes the steps the same way. Put linear steps in one pass-through\nrouter with a single branch.\n","items":{"$ref":"#/components/schemas/Router"}},"aiDescription":{"$ref":"#/components/schemas/AIDescription"},"draft":{"type":"boolean","description":"When true, this tool is a draft that auto-deletes when its expiry passes\n(`draftExpiresAt` in the response). Set at creation; an update can clear the\nflag but never set it."}}},"Input":{"type":"object","description":"Configuration for the tool's input processing.\n\nDefines the expected input structure, optional transformations to apply\nbefore routing, and mock data for testing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the input configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the expected input data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the expected input data structure.\n\nUsed for validation, documentation, and AI-assisted tooling.\nMust be a valid JSON Schema document. Leave `$schema` out or name draft-07: a\n`$schema` naming draft 2020-12 fails the save with 422 \"Invalid input.schema -\nno schema with key or ref ...\". An `$id` saves.\n\nEach invoke's input is validated against it. A missing `required` property\nrefuses the call with 422 `tool_input_schema_validation_failed` (\"Tool input\nvalidation failed: must have required property '<name>'\"), and a failed\n`pattern` with 422 \"Tool input validation failed: <field>: must match pattern\n...\". A property made required later therefore breaks callers that don't send\nit.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; invocation input validates the same either way.\nUnset opens the visual editor, and an edit there rebuilds `schema` from the field list,\nkeeping only `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"transform":{"$ref":"#/components/schemas/Transform"},"traceKeyTemplate":{"type":"string","maxLength":1024,"description":"Handlebars template evaluated against the invocation input (`record`) to produce\nthe `traceKey` stored on each run-history entry, so an invocation can be found again\nwith the `traceKeyPrefix` filter on `GET /v1/tools/{_id}/invocations`. Omit it and\nentries carry an empty `traceKey`."},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Input stage until this time: while it lies in\nthe future, every record entering the tool is written to\n`GET /v1/tools/{_id}/tool_input/requests` with its input-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/input/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Sample input saved with the tool. `POST /v1/tools/{_id}/test/run` doesn't read it:\nsend it as that call's `input`, or the run starts from an empty record. Maximum\nsize: 1MB.\n","additionalProperties":true}}},"Transform":{"type":"object","description":"Configuration for transforming data during processing operations. This object enables\nreshaping of records.\n\n**Transformation capabilities**\n\nCeligo's transformation engine offers powerful features for data manipulation:\n- Precise field mapping with JSONPath expressions\n- Support for any level of nested arrays\n- Formula-based field value generation\n- Dynamic references to flow and integration settings\n\n**Implementation approaches**\n\nThere are two distinct transformation mechanisms available:\n\n**Rule-Based Transformation (`type: \"expression\"`)**\n- **Best For**: Most transformation scenarios from simple to complex\n- **Capabilities**: Field mapping, formula calculations, lookups, nested data handling\n- **Advantages**: Visual configuration, no coding required, intuitive interface\n- **Configuration**: Define rules in the `expression` object\n- **Use When**: You have clear mapping requirements or need to reshape data structure\n\n**Script-Based Transformation (`type: \"script\"`)**\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Capabilities**: Full programmatic control, custom processing, complex business rules\n- **Advantages**: Maximum flexibility, can implement any transformation logic\n- **Configuration**: Reference a script in the `script` object\n- **Use When**: Visual transformation tools aren't sufficient for your use case\n","properties":{"type":{"type":"string","description":"Determines which transformation mechanism to use. This choice affects which properties\nmust be configured and how transformation logic is implemented.\n\n**Available types**\n\n**Rule-Based Transformation (`\"expression\"`)**\n- **Required Config**: The `expression` object with mapping definitions\n- **Behavior**: Applies declarative rules to reshape data\n- **Best For**: Most transformation scenarios from simple to complex\n- **Advantages**: Visual configuration, no coding required\n\n**Script-Based Transformation (`\"script\"`)**\n- **Required Config**: The `script` object with _scriptId and function\n- **Behavior**: Executes custom JavaScript to transform data\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Advantages**: Maximum flexibility, can implement any logic\n\n**Implementation guidance**\n\n1. For standard data transformations, use `\"expression\"`\n2. For complex logic or specialized processing, use `\"script\"`\n3. When selecting a type, you must configure the corresponding object:\n    - `type: \"expression\"` requires the `expression` object\n    - `type: \"script\"` requires the `script` object\n","enum":["expression","script"]},"expression":{"type":"object","description":"Configuration for declarative rule-based transformations. This object enables reshaping data\nwithout requiring custom code.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"expression\" and should not be\nconfigured otherwise. It provides a standardized way to define transformation rules that\ncan map, modify, and generate data elements.\n\n**Implementation guidance**\n\nThe expression system uses a rule-based approach where:\n- Field mappings define how input data is transformed to target fields\n- Formulas can be used to calculate or generate new values\n- Lookups can enrich data by fetching related information\n- Mode determines how records are processed (create new or modify existing)\n","properties":{"version":{"type":"string","description":"Version of the expression format. Determines which rules\nproperty contains the transformation logic.\n","enum":["1","2"]},"rules":{"type":"array","description":"Transformation rules for version 1 expressions. An array of\nrule groups; each group is an array of field-mapping objects.\nMost transforms have a single group. Present when `version`\nis `\"1\"`. The output record contains ONLY the generated\nfields — every unmapped field is dropped (v1 has no\nequivalent of Transform 2.0's `modify` mode), and the\nrecord's trace key does not survive the rebuild.\n","items":{"type":"array","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path to read from. Supports multiple\nsyntaxes: bare field names (`id`), dot notation\n(`fulfillment.shipment_id`), slash-prefixed paths\nfor XML (`/FeedProcessingStatus`), wildcards (`*.id`,\n`*.[Internal ID]`), and array indexing (`SDF[0]`).\n"},"generate":{"type":"string","description":"Target field name to write to. Typically a bare name\n(`id`) or dot path (`SDF.Filter.ID`).\n"},"key":{"type":"string","description":"Auto-generated identifier for this rule, used by the\nUI to track individual rules for editing and reordering.\n"}},"required":["extract","generate"]}}},"rulesTwoDotZero":{"type":"object","description":"Configuration for version 2 transformation rules. This object contains the core logic\nfor how data is mapped, enriched, and transformed.\n\n**Capabilities**\n\nTransformation 2.0 provides:\n- Precise field mapping with JSONPath expressions\n- Support for deeply nested data structures\n- Formula-based field generation\n- Dynamic lookups for data enrichment\n- Multiple operating modes to fit different scenarios\n","properties":{"mode":{"type":"string","description":"Transformation mode that determines how records are handled during processing.\n\n**Available modes**\n\n**Create Mode (`\"create\"`)**\n- **Behavior**: Builds entirely new output records from inputs\n- **Use When**: Output structure differs significantly from input\n- **Advantage**: Clean slate approach, no field inheritance\n\n**Modify Mode (`\"modify\"`)**\n- **Behavior**: Makes targeted edits to existing records\n- **Use When**: Output structure should remain similar to input\n- **Advantage**: Preserves unmapped fields from the original record\n","enum":["create","modify"]},"mappings":{"$ref":"#/components/schemas/Mappings"},"lookups":{"allOf":[{"description":"Shared lookup tables used across all mappings defined in the transformation rules.\n\n**Purpose**\n\nLookups provide centralized value translation that can be referenced from any mapping\nin your transformation configuration. They enable consistent translation of codes, IDs,\nand values between systems without duplicating translation logic.\n\n**Usage in transformations**\n\nLookups are particularly valuable in transformations for:\n\n- **Data Normalization**: Standardizing values from diverse source systems\n- **Code Translation**: Converting between different coding systems (e.g., status codes)\n- **Field Enrichment**: Adding descriptive values based on ID or code lookups\n- **Cross-Reference Resolution**: Mapping identifiers between integrated systems\n\n**Implementation**\n\nLookups are defined once in this array and referenced by name in mappings:\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"statusMapping\",\n    \"map\": {\n      \"A\": \"Active\",\n      \"I\": \"Inactive\",\n      \"P\": \"Pending\"\n    },\n    \"default\": \"Unknown Status\"\n  }\n]\n```\n\nThen referenced in mappings using the lookupName property:\n\n```json\n{\n  \"generate\": \"status\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.statusCode\",\n  \"lookupName\": \"statusMapping\"\n}\n```\n\nThe system automatically applies the lookup during transformation processing.\n\nFor complete details on lookup properties and behavior, see the Lookups schema.\n"},{"$ref":"#/components/schemas/Lookups"}]},"inputContext":{"type":"string","enum":["record","envelope"],"description":"Controls the JSON shape the transformTwoDotZero processor\nevaluates `mappings[].extract` JSONPath values against at\nflow runtime. Applies only to Transform 2.0 (v2,\n`rulesTwoDotZero`); v1 transforms (the `rules` array on\n`transform.expression.rules`) and script-mode transforms\nignore this field.\n"}}}}},"script":{"type":"object","description":"Configuration for programmable script-based transformations. This object enables complex, custom\ntransformation logic beyond what expression-based transformations can provide.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"script\" and should not be configured\notherwise. It provides a way to execute custom JavaScript code to transform data according to\nspecialized business rules or complex algorithms.\n\n**Implementation approach**\n\nScript-based transformation works by:\n1. Executing the specified function from the referenced script\n2. Passing input data to the function\n3. Using the function's return value as the transformed output\n\n**Common use cases**\n\nScript transformation is ideal for:\n- Complex business logic that can't be expressed through mappings\n- Algorithmic transformations requiring computation\n- Dynamic transformations based on external factors\n- Legacy system data format compatibility\n- Multi-stage processing with intermediate steps\n\nOnly use script-based transformation when expression-based transformation is insufficient.\nScript transformation requires maintaining custom code, which adds complexity to the integration.\n","properties":{"_scriptId":{"type":"string","description":"Reference to a predefined script resource containing the transformation logic.\n\nThe referenced script should contain the function specified in the\n'function' property. It must be a script in this account: any other id fails the save\nwith 422 (\"Only a script owned by the user or a script shared by the Integration App can\nbe used at transform.script._scriptId.\").\n","format":"objectid"},"function":{"type":"string","description":"Name of the function within the script to execute for transformation. This function\nmust exist in the script referenced by _scriptId.\n"}}}}},"Mappings":{"type":"array","description":"Array of field mapping configurations for transforming data from one format into another.\n\n**Guidance**\n\nThis schema is designed around RECURSION as its core architectural principle. Understanding this recursive\nnature is essential for building effective mappings:\n\n1. The schema is self-referential by design - a mapping can contain nested mappings of the same structure\n2. Complex data structures (nested objects and arrays of objects, to any depth) are ALL\n   handled through this recursive pattern; an array of arrays is not (build one in a script hook)\n3. Each mapping handles one level of the data structure; deeper levels are handled by nested mappings\n\nWhen generating mappings programmatically:\n- For simple fields (string, number, boolean): Create single mapping objects\n- For objects: Create a parent mapping with nested 'mappings' array containing child field mappings\n- For arrays: Use 'buildArrayHelper' with extract paths defining array inputs and\n  recursive 'mappings' to define object structures\n\nThe system will process these nested structures recursively during runtime, ensuring proper construction\nof complex hierarchical data while maintaining excellent performance.\n\nOn save, a leaf row with neither a source (`extract`, `hardCodedValue`, `lookupName`) nor a\ndestination (`generate`) is dropped silently, at every nesting level, and so is an object or\nobject-array row left with no source, no destination and no remaining child `mappings` or\n`buildArrayHelper` entries; a row with only one of the two, and a source-less, destination-less\nwrapper that still has children, are kept.\n\nThe shape of the array is the mapper's **JSON structure**. Named top-level items produce an object\nrecord (`record { }`). Exactly one top-level item with `dataType: objectarray` and no `generate` is\nthe `rows [ ]` structure: the mapped record is an array — usually one element per element of the\nsource array that root's `buildArrayHelper` iterates — Handlebars templates such as `http.body`\nbind it as `rows` instead of `record`, and the mapping editor shows that row's destination disabled.\n","items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}}},"items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}},"Lookups":{"type":"array","description":"Configuration for value-to-value transformations using lookup tables.\n\n**Purpose**\n\nLookups provide a way to translate values from one system to another. They transform\ninput values into output values using either static mapping tables or\ndynamic lookup caches.\n\n**Lookup mechanisms**\n\nThere are two distinct lookup mechanisms available:\n\n1. **Static Lookups**: Define a simple key-value map object and store it as part of your resource\n   - Best for: Small, fixed sets of values that rarely change\n   - Implementation: Configure the `map` object with input-to-output value mappings\n   - Example: Country codes, status values, simple translations\n\n2. **Dynamic Lookups**: Reference an existing 'Lookup Cache' resource in your Celigo account\n   - Best for: Large datasets, frequently changing values, or complex reference data\n   - Implementation: Configure `_lookupCacheId` to reference cached data maintained independently\n   - Example: Product catalogs, customer databases, pricing information\n\n**Property usage**\n\nThere are two mutually exclusive ways to configure lookups, depending on which mechanism you choose:\n\n1. **For Static Mappings**: Configure the `map` property with a direct key-value object\n   ```json\n   \"map\": {\"US\": \"United States\", \"CA\": \"Canada\"}\n   ```\n\n2. **For Dynamic Lookups**: Configure the following properties:\n   - `_lookupCacheId`: Reference to the lookup cache resource\n   - `extract`: JSON path to extract specific value from the returned lookup object\n\n**When to use**\n\nLookups are ideal for:\n\n1. **Value Translation**: Mapping codes or IDs to human-readable values\n\n2. **Data Enrichment**: Adding related information to records during processing\n\n3. **Normalization**: Ensuring consistent formatting of values across systems\n\n**Implementation details**\n\nLookups can be referenced in:\n\n1. **Field Mappings**: Direct use in field transformation configurations\n\n2. **Handlebars Templates**: Use within templates with the syntax:\n   ```\n   {{lookup 'lookupName' record.fieldName}}\n   ```\n\n**Example usage**\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"countryCodeToName\",\n    \"map\": {\n      \"US\": \"United States\",\n      \"CA\": \"Canada\",\n      \"UK\": \"United Kingdom\"\n    },\n    \"default\": \"Unknown Country\",\n    \"allowFailures\": true\n  },\n  {\n    \"name\": \"productDetails\",\n    \"_lookupCacheId\": \"60a2c4e6f321d800129a1a3c\",\n    \"extract\": \"$.details.price\",\n    \"allowFailures\": false\n  }\n]\n```\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for the lookup table within this configuration.\n\nThis name must be unique within the scope where the lookup is defined and is used to reference\nthe lookup in handlebars templates with the syntax {{lookup 'name' value}}.\n\nChoose descriptive names that indicate the transformation purpose, such as:\n- \"countryCodeToName\" for country code to full name conversion\n- \"statusMapping\" for status code translations\n- \"departmentCodes\" for department code to name mapping\n"},"map":{"type":["object","null"],"description":"The lookup mapping table as key-value pairs. The platform stores `null`\nhere on dynamic lookups, which resolve values at runtime instead of\nfrom a static table.\n\nThis object contains the input values as keys and their corresponding\noutput values. When a input value matches a key in this object,\nit will be replaced with the corresponding value.\n\nThe map should be kept to a reasonable size (typically under 100 entries)\nfor optimal performance. For larger mapping requirements, consider using\ndynamic lookups instead.\n\nMaps can include:\n- Simple code to name conversions: {\"US\": \"United States\"}\n- Status transformations: {\"A\": \"Active\", \"I\": \"Inactive\"}\n- ID to name mappings: {\"100\": \"Marketing\", \"200\": \"Sales\"}\n\nValues can be strings, numbers, or booleans, but all are stored as strings\nin the configuration.\n"},"_lookupCacheId":{"type":"string","description":"Reference to a LookupCache resource that contains the reference data for the lookup.\n\n**Purpose**\n\nThis field connects the lookup to an external data source that has been cached in the system.\nUnlike static lookups that use the `map` property, dynamic lookups can reference large datasets\nor frequently changing information without requiring constant updates to the integration.\n\n**Implementation details**\n\nThe LookupCache resource referenced by this ID contains:\n- The data records to be used as a reference source\n- Configuration for how the data should be indexed and accessed\n- Caching parameters to balance performance with data freshness\n\n**Usage patterns**\n\nCommonly used to reference:\n- Product catalogs or SKU databases\n- Customer or account information\n- Pricing tables or discount rules\n- Complex business logic lookup tables\n\nFormat: 24-character hexadecimal string (MongoDB ObjectId)\n","format":"objectid"},"extract":{"type":"string","description":"JSON path expression that extracts a specific value from the cached lookup object.\n\n**Purpose**\n\nWhen using dynamic lookups with a LookupCache, this JSON path identifies which field to extract\nfrom the cached object after it has been retrieved using the lookup key.\n\n**Implementation details**\n\n- Must use JSON path syntax (similar to mapping extract fields)\n- Operates on the cached object returned by the lookup operation\n- Examples:\n  - \"$.name\" - Extract the name field from the top level\n  - \"$.details.price\" - Extract a nested price field\n  - \"$.attributes[0].value\" - Extract a value from the first element of an array\n\n**Usage scenario**\n\nWhen a lookup cache contains complex objects:\n```json\n// Cache entry for key \"PROD-123\":\n{\n  \"id\": \"PROD-123\",\n  \"name\": \"Premium Widget\",\n  \"details\": {\n    \"price\": 99.99,\n    \"currency\": \"USD\",\n    \"inStock\": true\n  }\n}\n```\n\nSetting extract to \"$.details.price\" would return 99.99 as the lookup result.\n\nIf no extract is provided, the entire cached object is returned as the lookup result.\n"},"default":{"type":["string","null"],"description":"Default value to use when the source value is not found in the lookup map.\nThe platform stores `null` here when no default is configured.\n\nThis value is used as a fallback when:\n1. The source value doesn't match any key in the map\n2. allowFailures is true or omitted\n\nSetting an appropriate default helps prevent flow failures due to unexpected\nvalues and provides predictable behavior for edge cases.\n\nCommon default patterns include:\n- Descriptive unknowns: \"Unknown Country\", \"Unspecified Status\"\n- Original value indicators: \"{Original Value}\", \"No mapping found\"\n- Neutral values: \"Other\", \"N/A\", \"Miscellaneous\"\n\nA save refuses a default together with an explicit `allowFailures: false`\n(422 `Lookups with default set can not have allowFailures explicitly set to\nfalse.`): a lookup either fails a miss (`allowFailures: false`, no default) or\nfalls back to its default.\n"},"allowFailures":{"type":["boolean","null"],"description":"When true, missing lookup values will use the default value rather than causing an error.\n\n**Behavior control**\n\nThis field determines how the system handles source values that don't exist in the map:\n\n- true: Use the default value for missing mappings and continue processing\n- false: Treat missing mappings as errors, failing the record (`lookup_error` for a\n  static map, `lookupcache_evaluation_failed` for a lookup cache)\n- omitted or null: the record doesn't fail either. On an import a static lookup writes\n  its default if it has one, and a static or cache lookup without one leaves the\n  mapped field out of the output, with no error\n\n**Recommendation**\n\nSet this to true when:\n- New source values might appear over time\n- Data quality issues could introduce unexpected values\n- Processing should continue even with imperfect mapping\n\nSet this to false when:\n- Complete data accuracy is critical\n- All possible source values are known and controlled\n- Missing mappings indicate serious data problems that should be addressed\n\nA default raises no error: records that miss go out with the default value and\nnothing reports them. Use true only when that fallback is acceptable at the\ndestination, and set false explicitly when a missing value must stop the record.\n"}}}},"Output":{"type":"object","description":"Configuration for the tool's output processing.\n\nDefines how the tool's results are mapped, transformed, and enriched\nbefore being returned. Supports field mappings, lookups for data\nenrichment, and custom script hooks for pre/post-mapping processing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the output configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the output data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the output data structure.\n\nUsed for documentation and validation of the tool's output.\nMust be a valid JSON Schema document.\n\nThe mapped output is validated against it. While the stored schema holds\n`additionalProperties: false`, `POST /v1/tools/{_id}/invoke` answers 422 \"Tool\noutput validation failed: must NOT have additional properties\" when the output\nmappings write a field the schema doesn't list.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; the output validates the same either way. Unset\nopens the visual editor, and an edit there rebuilds `schema` from the field list, keeping\nonly `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"mappings":{"description":"Field mappings to transform data into the output format.\n\nMaps data from processing results to the output structure.\nUses Celigo's standard mapping format with extract/generate field paths —\na flat array of mapping entries (each entry may recurse via its own\nnested ``mappings`` for object/array structures).\n","allOf":[{"$ref":"#/components/schemas/Mappings"}]},"lookups":{"type":"array","description":"Lookup tables for data enrichment during output processing.\n\nStatic key-value mappings used to translate values (e.g., status codes,\ncategory names) during output generation.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the lookup, used to reference it from mappings.\n"},"map":{"type":"object","description":"Key-value mapping object. Keys are the input values and\nvalues are the corresponding output values.\n","additionalProperties":true},"default":{"type":"string","description":"Default value returned when the input key is not found in the map.\n"},"allowFailures":{"type":"boolean","description":"Whether to continue processing if the lookup fails to find a match\nand no default is provided.\n"}}}},"hooks":{"type":"object","description":"Custom script hooks for pre- and post-mapping processing.\n\nAllows running custom JavaScript functions before and after\noutput mappings are applied.\n","properties":{"preMap":{"type":"object","description":"Script to run before applying output mappings.\n\nCan modify the data before it is mapped to the output structure.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}},"postMap":{"type":"object","description":"Script to run after applying output mappings.\n\nCan modify the final output data after mappings are applied.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}}}},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Output stage until this time: while it lies in\nthe future, every post-mapped record is written to\n`GET /v1/tools/{_id}/tool_output/requests` with its output-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/output/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Mock data for testing the tool's output processing.\n\nProvides sample data that would arrive from the routing/processing\nstage, used to test mapping and lookup logic. Maximum size: 1MB.\n","additionalProperties":true}}},"Router":{"type":"object","description":"Configuration for conditional routing within a tool.\n\nRouters evaluate input data and direct it to different processing branches\nbased on criteria. This enables complex business logic and conditional\nprocessing within the tool.\n\nUnlike flows, tools only support \"first_matching_branch\" routing strategy.\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal sink to exit the tool and return results.\n\nA router comes in two shapes, told apart by whether `routeRecordsTo` and\n`routeRecordsUsing` are set: a **branching router** sets both and has two or\nmore branches, each taking the inputs its `inputFilter` (or the router's\n`script`) matches — the node you see in the Tool builder; a **pass-through\nrouter** sets neither and has exactly one branch that every input goes\nstraight through — not drawn, it holds the steps before the first branching\nrouter and merges branches back into shared steps (branches rejoin only by\npointing `nextRouterId` at the same router).\n","properties":{"id":{"type":"string","maxLength":36,"pattern":"^[A-Za-z0-9_-]+$","description":"Unique identifier for this router within the tool.\n\nUsed to reference this router from other routers' branch `nextRouterId`.\nUse at most 36 letters, digits, underscores and hyphens: a longer id fails\nthe save with 422 (\"Router id is missing or contains more than 36 characters\nor invalid characters. Please correct the router id.\"), as on flow routers.\n"},"name":{"type":"string","maxLength":300,"description":"Human-readable name for the router.\n"},"routeRecordsTo":{"type":"string","enum":["first_matching_branch"],"description":"Routing strategy. Tools only support \"first_matching_branch\",\nwhich routes to the first branch whose criteria match the input.\nSet together with `routeRecordsUsing` on a branching router; leave\nboth unset on a one-branch pass-through router.\n"},"routeRecordsUsing":{"type":"string","enum":["input_filters","script"],"description":"How a branching router decides which branch an input matches. Set together\nwith `routeRecordsTo`; leave both unset on a one-branch pass-through router.\n"},"script":{"type":"object","description":"Script configuration when routeRecordsUsing is \"script\".\n\nThe function should return the name of the branch to route to.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name that returns the branch name"}}},"branches":{"type":"array","description":"List of branches defining different processing paths.\n\nEach branch has optional filter criteria and a set of processing steps.\nRecords are evaluated against branch criteria in order; the first\nmatching branch is selected.\n","items":{"type":"object","properties":{"name":{"type":"string","maxLength":300,"description":"Name of this branch.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of when and why this branch is selected.\n"},"branchId":{"type":"string","description":"Stable identifier for this branch within the tool, generated by\nthe builder. Used to reference the branch independently of its\nposition in the branches array (e.g., from step requests).\n"},"inputFilter":{"type":"object","description":"Filter criteria to determine if this branch should be selected.\n\nUses Celigo's expression-based filter format. Send `version` and `rules`\ndirectly on `inputFilter` — not inside the `{type: \"expression\", expression: {...}}`\nwrapper that export and import filters use. A wrapped filter is saved without an\nerror, but the branch is stored with no `inputFilter` and takes every input.\n","properties":{"version":{"type":"string","enum":["1"],"description":"Filter version"},"rules":{"type":"array","description":"Filter rules in Celigo expression-based filter format.\n\nArray-based DSL where the first element is an operator (e.g., \"equals\", \"and\", \"or\"),\nfollowed by operands which can be nested expressions.\n","items":{}}}},"nextRouterId":{"type":"string","description":"Identifier of the next router to chain to after this branch completes.\n\nUse \"outputRouter\" as a special terminal value to exit the tool\nand return the processing results.\n\nEvery branch needs one. A branch without it is saved, but every invoke is\nthen refused with 422 and every test run with 400 `tool_dangling_branch`\n(\"One or more branches are not merged back. Review the setup to ensure all\nbranches converge.\"), whichever branch the input takes. An id that names no\nrouter fails the save with 422 \"Invalid nextRouterId is defined on the branch.\nPlease correct the branch configuration.\"\n"},"pageProcessors":{"type":"array","description":"Processing steps to execute in this branch.\n\nEach processor references an export (lookup) or import resource\nfor data retrieval or submission. An export is a step at most once in a\ntool: using it again, on another branch or in the same one, fails the save\nwith 422 \"The export <id> is already included as a pageProcessor in your\nflow, and cannot be added again.\"\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["export","import"],"description":"Type of processor.\n\n- **export**: Retrieves data from an external system (lookup)\n- **import**: Sends data to an external system\n"},"_exportId":{"type":"string","format":"objectId","description":"Export resource reference (when type is \"export\")"},"_importId":{"type":"string","format":"objectId","description":"Import resource reference (when type is \"import\")"},"proceedOnFailure":{"type":"boolean","description":"When true, the later steps and the output stage still run after this\nprocessor fails; when false, they're skipped. Either way a direct\n`POST /v1/tools/{_id}/invoke` answers 422 with the failed step's error,\nwithout the output."},"setupInProgress":{"type":"boolean","description":"When true, the processor's configuration is still being\nset up in the UI and the step is not yet runnable.\n"},"responseMapping":{"type":"object","description":"Merges fields from this processor's response back onto the\nin-flight record so later processors and the tool's output\ncan read them. Extracts do NOT read the raw application\nresponse — they evaluate against the platform's canonical\nper-record envelope: for lookups (`type: \"export\"`) that is\n`{\"statusCode\", \"data\": [<result records>], \"errors\"}`\nwhere `data` holds the records AFTER the export's own\n`transform` and output `filter`, so paths must start from\n`data` and name transformed fields by their transformed\nname (e.g. `data[0].name`) — a lookup result field\nwritten without the `data` prefix resolves to nothing;\nfor imports it is `{\"id\", \"statusCode\", \"ignored\",\n\"_json\", ...<body fields flattened>}`, so use `id`,\n`_json.<path>`, or the body field itself at the root\n(`orderNumber` and `_json.orderNumber` read the same\nvalue). An extract may be a Handlebars expression over\nthe same envelope (`{{data.0.name}}`,\n`{{#if data.length}}found{{/if}}`; dot indexes only; a\nstring result); there is no `record` object in this\ncontext, so `{{record.…}}` renders nothing.\n","properties":{"fields":{"type":"array","description":"Simple field-level mappings","items":{"type":"object","properties":{"extract":{"type":"string","description":"Path within the canonical response envelope to\ncopy the value from (`data[0].x` / `data.0.x`\nfor lookups; `id`, `_json.<path>` or the\nflattened body field `<path>` for imports), or\na Handlebars expression over that envelope (dot\nindexes inside `{{ }}`, never a `record.` root).\n"},"generate":{"type":"string","description":"Field path on the in-flight record where the\nextracted value is stored (dot notation for\nnesting).\n"}}}},"lists":{"type":"array","description":"List-level mappings for array data","items":{"type":"object","properties":{"generate":{"type":"string","description":"Target list path"},"fields":{"type":"array","description":"Field-level mappings applied to each item in the list.","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path"},"generate":{"type":"string","description":"Target field path"}}}}}}}}},"hooks":{"type":"object","description":"Custom scripts for processing","properties":{"postResponseMap":{"type":"object","description":"Script to run after response mapping","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute"}}}}}}}}}}}}},"AIDescription":{"type":"object","description":"AI-generated descriptions and documentation for the resource.\n\nThis object contains automatically generated content that helps users\nunderstand the purpose, behavior, and configuration of the resource without\nrequiring them to analyze the technical details. The AI-generated content\nis sanitized and safe for display in the UI.\n","properties":{"summary":{"type":["string","null"],"description":"Brief AI-generated summary of the resource's purpose and functionality.\n\nThis concise description provides a quick overview of what the resource does,\nwhat systems it interacts with, and its primary role in the integration.\nThe summary is suitable for display in list views, dashboards, and other\ncontexts where space is limited.\n\nMaximum length: 10KB\n"},"detailed":{"type":["string","null"],"description":"Comprehensive AI-generated description of the resource's functionality.\n\nThis detailed explanation covers the resource's purpose, configuration details,\ndata flow patterns, filtering logic, and other technical aspects. It provides\nin-depth information suitable for documentation, tooltips, or detailed views\nin the administration interface.\n\nThe content may include HTML formatting for improved readability.\n\nMaximum length: 10KB\n"},"generatedOn":{"type":["string","null"],"format":"date-time","description":"Timestamp indicating when the AI description was generated.\n\nThis field helps track the freshness of the AI-generated content and\ndetermine when it might need to be regenerated due to changes in the\nresource's configuration or behavior.\n\nThe timestamp is recorded in ISO 8601 format with UTC timezone (Z suffix).\n"}}},"ResourceResponse":{"type":"object","description":"Response","properties":{"_id":{"type":"string","format":"objectId","readOnly":true,"description":"Unique identifier for the resource. Format is a 24-character hexadecimal string."},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was created. Set automatically and cannot be modified."},"lastModified":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was last updated. Changes whenever any property is modified."},"deletedAt":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the resource was soft-deleted. When null or absent, the resource is active."}},"required":["_id"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}}}},"paths":{"/v1/tools":{"get":{"summary":"List tools","description":"Returns all tools in the account. Filter by `_integrationId` to scope\nresults to a single integration.","operationId":"listTools","tags":["Tools"],"parameters":[{"name":"_integrationId","in":"query","description":"Filter tools by integration identifier","required":false,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Include"},{"$ref":"#/components/parameters/Exclude"}],"responses":{"200":{"description":"Successfully retrieved list of tools","headers":{"Link":{"description":"RFC-5988 pagination links. When more pages remain, includes a `<...>; rel=\"next\"` entry;\nabsent on the final page.\n","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Tool"}}}}},"204":{"description":"No tools exist in the account"},"401":{"$ref":"#/components/responses/401-unauthorized"}}}}}}
````

## Create a tool

> Creates a new tool within an integration. \`name\` and \`\_integrationId\`\
> are required. Routers use \`first\_matching\_branch\` strategy only, and each\
> branch's \`nextRouterId\` must point to another router's \`id\` or\
> \`"outputRouter"\` to exit the tool.

````json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Request":{"type":"object","description":"Request schema for creating or updating a tool. Tools are reusable processing\nunits that encapsulate input transformation, conditional routing, and output\nmapping logic within an integration.","required":["name","_integrationId"],"allOf":[{"$ref":"#/components/schemas/ToolBase"}]},"ToolBase":{"type":"object","description":"Writable tool fields shared by the request and response schemas.","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable name for the tool.\n\nDisplayed in the UI and used to identify the tool's purpose.\n"},"description":{"type":"string","maxLength":5120,"description":"Optional detailed description of what the tool does.\n\nUse this to document the tool's purpose, expected inputs/outputs,\nand any special considerations.\n"},"_integrationId":{"type":"string","format":"objectId","description":"Reference to the integration this tool belongs to.\n\nEvery tool must be associated with an integration. The integration\ndetermines the scope and access controls for the tool.\n"},"input":{"$ref":"#/components/schemas/Input"},"output":{"$ref":"#/components/schemas/Output"},"routers":{"type":"array","description":"Optional routers for conditional processing logic.\n\nRouters allow you to direct input data to different processing branches\nbased on filter criteria or script logic. Tools only support\n\"first_matching_branch\" routing strategy.\n\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal value to exit the tool.\n\nA tool saves without `routers`, but it can't run: `POST /v1/tools/{_id}/invoke`\nand a test run answer 400 `tool_empty_router` (\"Tool has no steps configured.\nAdd at least one step before executing the tool.\"). A full PUT that omits\n`routers` removes the steps the same way. Put linear steps in one pass-through\nrouter with a single branch.\n","items":{"$ref":"#/components/schemas/Router"}},"aiDescription":{"$ref":"#/components/schemas/AIDescription"},"draft":{"type":"boolean","description":"When true, this tool is a draft that auto-deletes when its expiry passes\n(`draftExpiresAt` in the response). Set at creation; an update can clear the\nflag but never set it."}}},"Input":{"type":"object","description":"Configuration for the tool's input processing.\n\nDefines the expected input structure, optional transformations to apply\nbefore routing, and mock data for testing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the input configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the expected input data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the expected input data structure.\n\nUsed for validation, documentation, and AI-assisted tooling.\nMust be a valid JSON Schema document. Leave `$schema` out or name draft-07: a\n`$schema` naming draft 2020-12 fails the save with 422 \"Invalid input.schema -\nno schema with key or ref ...\". An `$id` saves.\n\nEach invoke's input is validated against it. A missing `required` property\nrefuses the call with 422 `tool_input_schema_validation_failed` (\"Tool input\nvalidation failed: must have required property '<name>'\"), and a failed\n`pattern` with 422 \"Tool input validation failed: <field>: must match pattern\n...\". A property made required later therefore breaks callers that don't send\nit.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; invocation input validates the same either way.\nUnset opens the visual editor, and an edit there rebuilds `schema` from the field list,\nkeeping only `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"transform":{"$ref":"#/components/schemas/Transform"},"traceKeyTemplate":{"type":"string","maxLength":1024,"description":"Handlebars template evaluated against the invocation input (`record`) to produce\nthe `traceKey` stored on each run-history entry, so an invocation can be found again\nwith the `traceKeyPrefix` filter on `GET /v1/tools/{_id}/invocations`. Omit it and\nentries carry an empty `traceKey`."},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Input stage until this time: while it lies in\nthe future, every record entering the tool is written to\n`GET /v1/tools/{_id}/tool_input/requests` with its input-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/input/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Sample input saved with the tool. `POST /v1/tools/{_id}/test/run` doesn't read it:\nsend it as that call's `input`, or the run starts from an empty record. Maximum\nsize: 1MB.\n","additionalProperties":true}}},"Transform":{"type":"object","description":"Configuration for transforming data during processing operations. This object enables\nreshaping of records.\n\n**Transformation capabilities**\n\nCeligo's transformation engine offers powerful features for data manipulation:\n- Precise field mapping with JSONPath expressions\n- Support for any level of nested arrays\n- Formula-based field value generation\n- Dynamic references to flow and integration settings\n\n**Implementation approaches**\n\nThere are two distinct transformation mechanisms available:\n\n**Rule-Based Transformation (`type: \"expression\"`)**\n- **Best For**: Most transformation scenarios from simple to complex\n- **Capabilities**: Field mapping, formula calculations, lookups, nested data handling\n- **Advantages**: Visual configuration, no coding required, intuitive interface\n- **Configuration**: Define rules in the `expression` object\n- **Use When**: You have clear mapping requirements or need to reshape data structure\n\n**Script-Based Transformation (`type: \"script\"`)**\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Capabilities**: Full programmatic control, custom processing, complex business rules\n- **Advantages**: Maximum flexibility, can implement any transformation logic\n- **Configuration**: Reference a script in the `script` object\n- **Use When**: Visual transformation tools aren't sufficient for your use case\n","properties":{"type":{"type":"string","description":"Determines which transformation mechanism to use. This choice affects which properties\nmust be configured and how transformation logic is implemented.\n\n**Available types**\n\n**Rule-Based Transformation (`\"expression\"`)**\n- **Required Config**: The `expression` object with mapping definitions\n- **Behavior**: Applies declarative rules to reshape data\n- **Best For**: Most transformation scenarios from simple to complex\n- **Advantages**: Visual configuration, no coding required\n\n**Script-Based Transformation (`\"script\"`)**\n- **Required Config**: The `script` object with _scriptId and function\n- **Behavior**: Executes custom JavaScript to transform data\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Advantages**: Maximum flexibility, can implement any logic\n\n**Implementation guidance**\n\n1. For standard data transformations, use `\"expression\"`\n2. For complex logic or specialized processing, use `\"script\"`\n3. When selecting a type, you must configure the corresponding object:\n    - `type: \"expression\"` requires the `expression` object\n    - `type: \"script\"` requires the `script` object\n","enum":["expression","script"]},"expression":{"type":"object","description":"Configuration for declarative rule-based transformations. This object enables reshaping data\nwithout requiring custom code.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"expression\" and should not be\nconfigured otherwise. It provides a standardized way to define transformation rules that\ncan map, modify, and generate data elements.\n\n**Implementation guidance**\n\nThe expression system uses a rule-based approach where:\n- Field mappings define how input data is transformed to target fields\n- Formulas can be used to calculate or generate new values\n- Lookups can enrich data by fetching related information\n- Mode determines how records are processed (create new or modify existing)\n","properties":{"version":{"type":"string","description":"Version of the expression format. Determines which rules\nproperty contains the transformation logic.\n","enum":["1","2"]},"rules":{"type":"array","description":"Transformation rules for version 1 expressions. An array of\nrule groups; each group is an array of field-mapping objects.\nMost transforms have a single group. Present when `version`\nis `\"1\"`. The output record contains ONLY the generated\nfields — every unmapped field is dropped (v1 has no\nequivalent of Transform 2.0's `modify` mode), and the\nrecord's trace key does not survive the rebuild.\n","items":{"type":"array","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path to read from. Supports multiple\nsyntaxes: bare field names (`id`), dot notation\n(`fulfillment.shipment_id`), slash-prefixed paths\nfor XML (`/FeedProcessingStatus`), wildcards (`*.id`,\n`*.[Internal ID]`), and array indexing (`SDF[0]`).\n"},"generate":{"type":"string","description":"Target field name to write to. Typically a bare name\n(`id`) or dot path (`SDF.Filter.ID`).\n"},"key":{"type":"string","description":"Auto-generated identifier for this rule, used by the\nUI to track individual rules for editing and reordering.\n"}},"required":["extract","generate"]}}},"rulesTwoDotZero":{"type":"object","description":"Configuration for version 2 transformation rules. This object contains the core logic\nfor how data is mapped, enriched, and transformed.\n\n**Capabilities**\n\nTransformation 2.0 provides:\n- Precise field mapping with JSONPath expressions\n- Support for deeply nested data structures\n- Formula-based field generation\n- Dynamic lookups for data enrichment\n- Multiple operating modes to fit different scenarios\n","properties":{"mode":{"type":"string","description":"Transformation mode that determines how records are handled during processing.\n\n**Available modes**\n\n**Create Mode (`\"create\"`)**\n- **Behavior**: Builds entirely new output records from inputs\n- **Use When**: Output structure differs significantly from input\n- **Advantage**: Clean slate approach, no field inheritance\n\n**Modify Mode (`\"modify\"`)**\n- **Behavior**: Makes targeted edits to existing records\n- **Use When**: Output structure should remain similar to input\n- **Advantage**: Preserves unmapped fields from the original record\n","enum":["create","modify"]},"mappings":{"$ref":"#/components/schemas/Mappings"},"lookups":{"allOf":[{"description":"Shared lookup tables used across all mappings defined in the transformation rules.\n\n**Purpose**\n\nLookups provide centralized value translation that can be referenced from any mapping\nin your transformation configuration. They enable consistent translation of codes, IDs,\nand values between systems without duplicating translation logic.\n\n**Usage in transformations**\n\nLookups are particularly valuable in transformations for:\n\n- **Data Normalization**: Standardizing values from diverse source systems\n- **Code Translation**: Converting between different coding systems (e.g., status codes)\n- **Field Enrichment**: Adding descriptive values based on ID or code lookups\n- **Cross-Reference Resolution**: Mapping identifiers between integrated systems\n\n**Implementation**\n\nLookups are defined once in this array and referenced by name in mappings:\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"statusMapping\",\n    \"map\": {\n      \"A\": \"Active\",\n      \"I\": \"Inactive\",\n      \"P\": \"Pending\"\n    },\n    \"default\": \"Unknown Status\"\n  }\n]\n```\n\nThen referenced in mappings using the lookupName property:\n\n```json\n{\n  \"generate\": \"status\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.statusCode\",\n  \"lookupName\": \"statusMapping\"\n}\n```\n\nThe system automatically applies the lookup during transformation processing.\n\nFor complete details on lookup properties and behavior, see the Lookups schema.\n"},{"$ref":"#/components/schemas/Lookups"}]},"inputContext":{"type":"string","enum":["record","envelope"],"description":"Controls the JSON shape the transformTwoDotZero processor\nevaluates `mappings[].extract` JSONPath values against at\nflow runtime. Applies only to Transform 2.0 (v2,\n`rulesTwoDotZero`); v1 transforms (the `rules` array on\n`transform.expression.rules`) and script-mode transforms\nignore this field.\n"}}}}},"script":{"type":"object","description":"Configuration for programmable script-based transformations. This object enables complex, custom\ntransformation logic beyond what expression-based transformations can provide.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"script\" and should not be configured\notherwise. It provides a way to execute custom JavaScript code to transform data according to\nspecialized business rules or complex algorithms.\n\n**Implementation approach**\n\nScript-based transformation works by:\n1. Executing the specified function from the referenced script\n2. Passing input data to the function\n3. Using the function's return value as the transformed output\n\n**Common use cases**\n\nScript transformation is ideal for:\n- Complex business logic that can't be expressed through mappings\n- Algorithmic transformations requiring computation\n- Dynamic transformations based on external factors\n- Legacy system data format compatibility\n- Multi-stage processing with intermediate steps\n\nOnly use script-based transformation when expression-based transformation is insufficient.\nScript transformation requires maintaining custom code, which adds complexity to the integration.\n","properties":{"_scriptId":{"type":"string","description":"Reference to a predefined script resource containing the transformation logic.\n\nThe referenced script should contain the function specified in the\n'function' property. It must be a script in this account: any other id fails the save\nwith 422 (\"Only a script owned by the user or a script shared by the Integration App can\nbe used at transform.script._scriptId.\").\n","format":"objectid"},"function":{"type":"string","description":"Name of the function within the script to execute for transformation. This function\nmust exist in the script referenced by _scriptId.\n"}}}}},"Mappings":{"type":"array","description":"Array of field mapping configurations for transforming data from one format into another.\n\n**Guidance**\n\nThis schema is designed around RECURSION as its core architectural principle. Understanding this recursive\nnature is essential for building effective mappings:\n\n1. The schema is self-referential by design - a mapping can contain nested mappings of the same structure\n2. Complex data structures (nested objects and arrays of objects, to any depth) are ALL\n   handled through this recursive pattern; an array of arrays is not (build one in a script hook)\n3. Each mapping handles one level of the data structure; deeper levels are handled by nested mappings\n\nWhen generating mappings programmatically:\n- For simple fields (string, number, boolean): Create single mapping objects\n- For objects: Create a parent mapping with nested 'mappings' array containing child field mappings\n- For arrays: Use 'buildArrayHelper' with extract paths defining array inputs and\n  recursive 'mappings' to define object structures\n\nThe system will process these nested structures recursively during runtime, ensuring proper construction\nof complex hierarchical data while maintaining excellent performance.\n\nOn save, a leaf row with neither a source (`extract`, `hardCodedValue`, `lookupName`) nor a\ndestination (`generate`) is dropped silently, at every nesting level, and so is an object or\nobject-array row left with no source, no destination and no remaining child `mappings` or\n`buildArrayHelper` entries; a row with only one of the two, and a source-less, destination-less\nwrapper that still has children, are kept.\n\nThe shape of the array is the mapper's **JSON structure**. Named top-level items produce an object\nrecord (`record { }`). Exactly one top-level item with `dataType: objectarray` and no `generate` is\nthe `rows [ ]` structure: the mapped record is an array — usually one element per element of the\nsource array that root's `buildArrayHelper` iterates — Handlebars templates such as `http.body`\nbind it as `rows` instead of `record`, and the mapping editor shows that row's destination disabled.\n","items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}}},"items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}},"Lookups":{"type":"array","description":"Configuration for value-to-value transformations using lookup tables.\n\n**Purpose**\n\nLookups provide a way to translate values from one system to another. They transform\ninput values into output values using either static mapping tables or\ndynamic lookup caches.\n\n**Lookup mechanisms**\n\nThere are two distinct lookup mechanisms available:\n\n1. **Static Lookups**: Define a simple key-value map object and store it as part of your resource\n   - Best for: Small, fixed sets of values that rarely change\n   - Implementation: Configure the `map` object with input-to-output value mappings\n   - Example: Country codes, status values, simple translations\n\n2. **Dynamic Lookups**: Reference an existing 'Lookup Cache' resource in your Celigo account\n   - Best for: Large datasets, frequently changing values, or complex reference data\n   - Implementation: Configure `_lookupCacheId` to reference cached data maintained independently\n   - Example: Product catalogs, customer databases, pricing information\n\n**Property usage**\n\nThere are two mutually exclusive ways to configure lookups, depending on which mechanism you choose:\n\n1. **For Static Mappings**: Configure the `map` property with a direct key-value object\n   ```json\n   \"map\": {\"US\": \"United States\", \"CA\": \"Canada\"}\n   ```\n\n2. **For Dynamic Lookups**: Configure the following properties:\n   - `_lookupCacheId`: Reference to the lookup cache resource\n   - `extract`: JSON path to extract specific value from the returned lookup object\n\n**When to use**\n\nLookups are ideal for:\n\n1. **Value Translation**: Mapping codes or IDs to human-readable values\n\n2. **Data Enrichment**: Adding related information to records during processing\n\n3. **Normalization**: Ensuring consistent formatting of values across systems\n\n**Implementation details**\n\nLookups can be referenced in:\n\n1. **Field Mappings**: Direct use in field transformation configurations\n\n2. **Handlebars Templates**: Use within templates with the syntax:\n   ```\n   {{lookup 'lookupName' record.fieldName}}\n   ```\n\n**Example usage**\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"countryCodeToName\",\n    \"map\": {\n      \"US\": \"United States\",\n      \"CA\": \"Canada\",\n      \"UK\": \"United Kingdom\"\n    },\n    \"default\": \"Unknown Country\",\n    \"allowFailures\": true\n  },\n  {\n    \"name\": \"productDetails\",\n    \"_lookupCacheId\": \"60a2c4e6f321d800129a1a3c\",\n    \"extract\": \"$.details.price\",\n    \"allowFailures\": false\n  }\n]\n```\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for the lookup table within this configuration.\n\nThis name must be unique within the scope where the lookup is defined and is used to reference\nthe lookup in handlebars templates with the syntax {{lookup 'name' value}}.\n\nChoose descriptive names that indicate the transformation purpose, such as:\n- \"countryCodeToName\" for country code to full name conversion\n- \"statusMapping\" for status code translations\n- \"departmentCodes\" for department code to name mapping\n"},"map":{"type":["object","null"],"description":"The lookup mapping table as key-value pairs. The platform stores `null`\nhere on dynamic lookups, which resolve values at runtime instead of\nfrom a static table.\n\nThis object contains the input values as keys and their corresponding\noutput values. When a input value matches a key in this object,\nit will be replaced with the corresponding value.\n\nThe map should be kept to a reasonable size (typically under 100 entries)\nfor optimal performance. For larger mapping requirements, consider using\ndynamic lookups instead.\n\nMaps can include:\n- Simple code to name conversions: {\"US\": \"United States\"}\n- Status transformations: {\"A\": \"Active\", \"I\": \"Inactive\"}\n- ID to name mappings: {\"100\": \"Marketing\", \"200\": \"Sales\"}\n\nValues can be strings, numbers, or booleans, but all are stored as strings\nin the configuration.\n"},"_lookupCacheId":{"type":"string","description":"Reference to a LookupCache resource that contains the reference data for the lookup.\n\n**Purpose**\n\nThis field connects the lookup to an external data source that has been cached in the system.\nUnlike static lookups that use the `map` property, dynamic lookups can reference large datasets\nor frequently changing information without requiring constant updates to the integration.\n\n**Implementation details**\n\nThe LookupCache resource referenced by this ID contains:\n- The data records to be used as a reference source\n- Configuration for how the data should be indexed and accessed\n- Caching parameters to balance performance with data freshness\n\n**Usage patterns**\n\nCommonly used to reference:\n- Product catalogs or SKU databases\n- Customer or account information\n- Pricing tables or discount rules\n- Complex business logic lookup tables\n\nFormat: 24-character hexadecimal string (MongoDB ObjectId)\n","format":"objectid"},"extract":{"type":"string","description":"JSON path expression that extracts a specific value from the cached lookup object.\n\n**Purpose**\n\nWhen using dynamic lookups with a LookupCache, this JSON path identifies which field to extract\nfrom the cached object after it has been retrieved using the lookup key.\n\n**Implementation details**\n\n- Must use JSON path syntax (similar to mapping extract fields)\n- Operates on the cached object returned by the lookup operation\n- Examples:\n  - \"$.name\" - Extract the name field from the top level\n  - \"$.details.price\" - Extract a nested price field\n  - \"$.attributes[0].value\" - Extract a value from the first element of an array\n\n**Usage scenario**\n\nWhen a lookup cache contains complex objects:\n```json\n// Cache entry for key \"PROD-123\":\n{\n  \"id\": \"PROD-123\",\n  \"name\": \"Premium Widget\",\n  \"details\": {\n    \"price\": 99.99,\n    \"currency\": \"USD\",\n    \"inStock\": true\n  }\n}\n```\n\nSetting extract to \"$.details.price\" would return 99.99 as the lookup result.\n\nIf no extract is provided, the entire cached object is returned as the lookup result.\n"},"default":{"type":["string","null"],"description":"Default value to use when the source value is not found in the lookup map.\nThe platform stores `null` here when no default is configured.\n\nThis value is used as a fallback when:\n1. The source value doesn't match any key in the map\n2. allowFailures is true or omitted\n\nSetting an appropriate default helps prevent flow failures due to unexpected\nvalues and provides predictable behavior for edge cases.\n\nCommon default patterns include:\n- Descriptive unknowns: \"Unknown Country\", \"Unspecified Status\"\n- Original value indicators: \"{Original Value}\", \"No mapping found\"\n- Neutral values: \"Other\", \"N/A\", \"Miscellaneous\"\n\nA save refuses a default together with an explicit `allowFailures: false`\n(422 `Lookups with default set can not have allowFailures explicitly set to\nfalse.`): a lookup either fails a miss (`allowFailures: false`, no default) or\nfalls back to its default.\n"},"allowFailures":{"type":["boolean","null"],"description":"When true, missing lookup values will use the default value rather than causing an error.\n\n**Behavior control**\n\nThis field determines how the system handles source values that don't exist in the map:\n\n- true: Use the default value for missing mappings and continue processing\n- false: Treat missing mappings as errors, failing the record (`lookup_error` for a\n  static map, `lookupcache_evaluation_failed` for a lookup cache)\n- omitted or null: the record doesn't fail either. On an import a static lookup writes\n  its default if it has one, and a static or cache lookup without one leaves the\n  mapped field out of the output, with no error\n\n**Recommendation**\n\nSet this to true when:\n- New source values might appear over time\n- Data quality issues could introduce unexpected values\n- Processing should continue even with imperfect mapping\n\nSet this to false when:\n- Complete data accuracy is critical\n- All possible source values are known and controlled\n- Missing mappings indicate serious data problems that should be addressed\n\nA default raises no error: records that miss go out with the default value and\nnothing reports them. Use true only when that fallback is acceptable at the\ndestination, and set false explicitly when a missing value must stop the record.\n"}}}},"Output":{"type":"object","description":"Configuration for the tool's output processing.\n\nDefines how the tool's results are mapped, transformed, and enriched\nbefore being returned. Supports field mappings, lookups for data\nenrichment, and custom script hooks for pre/post-mapping processing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the output configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the output data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the output data structure.\n\nUsed for documentation and validation of the tool's output.\nMust be a valid JSON Schema document.\n\nThe mapped output is validated against it. While the stored schema holds\n`additionalProperties: false`, `POST /v1/tools/{_id}/invoke` answers 422 \"Tool\noutput validation failed: must NOT have additional properties\" when the output\nmappings write a field the schema doesn't list.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; the output validates the same either way. Unset\nopens the visual editor, and an edit there rebuilds `schema` from the field list, keeping\nonly `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"mappings":{"description":"Field mappings to transform data into the output format.\n\nMaps data from processing results to the output structure.\nUses Celigo's standard mapping format with extract/generate field paths —\na flat array of mapping entries (each entry may recurse via its own\nnested ``mappings`` for object/array structures).\n","allOf":[{"$ref":"#/components/schemas/Mappings"}]},"lookups":{"type":"array","description":"Lookup tables for data enrichment during output processing.\n\nStatic key-value mappings used to translate values (e.g., status codes,\ncategory names) during output generation.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the lookup, used to reference it from mappings.\n"},"map":{"type":"object","description":"Key-value mapping object. Keys are the input values and\nvalues are the corresponding output values.\n","additionalProperties":true},"default":{"type":"string","description":"Default value returned when the input key is not found in the map.\n"},"allowFailures":{"type":"boolean","description":"Whether to continue processing if the lookup fails to find a match\nand no default is provided.\n"}}}},"hooks":{"type":"object","description":"Custom script hooks for pre- and post-mapping processing.\n\nAllows running custom JavaScript functions before and after\noutput mappings are applied.\n","properties":{"preMap":{"type":"object","description":"Script to run before applying output mappings.\n\nCan modify the data before it is mapped to the output structure.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}},"postMap":{"type":"object","description":"Script to run after applying output mappings.\n\nCan modify the final output data after mappings are applied.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}}}},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Output stage until this time: while it lies in\nthe future, every post-mapped record is written to\n`GET /v1/tools/{_id}/tool_output/requests` with its output-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/output/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Mock data for testing the tool's output processing.\n\nProvides sample data that would arrive from the routing/processing\nstage, used to test mapping and lookup logic. Maximum size: 1MB.\n","additionalProperties":true}}},"Router":{"type":"object","description":"Configuration for conditional routing within a tool.\n\nRouters evaluate input data and direct it to different processing branches\nbased on criteria. This enables complex business logic and conditional\nprocessing within the tool.\n\nUnlike flows, tools only support \"first_matching_branch\" routing strategy.\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal sink to exit the tool and return results.\n\nA router comes in two shapes, told apart by whether `routeRecordsTo` and\n`routeRecordsUsing` are set: a **branching router** sets both and has two or\nmore branches, each taking the inputs its `inputFilter` (or the router's\n`script`) matches — the node you see in the Tool builder; a **pass-through\nrouter** sets neither and has exactly one branch that every input goes\nstraight through — not drawn, it holds the steps before the first branching\nrouter and merges branches back into shared steps (branches rejoin only by\npointing `nextRouterId` at the same router).\n","properties":{"id":{"type":"string","maxLength":36,"pattern":"^[A-Za-z0-9_-]+$","description":"Unique identifier for this router within the tool.\n\nUsed to reference this router from other routers' branch `nextRouterId`.\nUse at most 36 letters, digits, underscores and hyphens: a longer id fails\nthe save with 422 (\"Router id is missing or contains more than 36 characters\nor invalid characters. Please correct the router id.\"), as on flow routers.\n"},"name":{"type":"string","maxLength":300,"description":"Human-readable name for the router.\n"},"routeRecordsTo":{"type":"string","enum":["first_matching_branch"],"description":"Routing strategy. Tools only support \"first_matching_branch\",\nwhich routes to the first branch whose criteria match the input.\nSet together with `routeRecordsUsing` on a branching router; leave\nboth unset on a one-branch pass-through router.\n"},"routeRecordsUsing":{"type":"string","enum":["input_filters","script"],"description":"How a branching router decides which branch an input matches. Set together\nwith `routeRecordsTo`; leave both unset on a one-branch pass-through router.\n"},"script":{"type":"object","description":"Script configuration when routeRecordsUsing is \"script\".\n\nThe function should return the name of the branch to route to.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name that returns the branch name"}}},"branches":{"type":"array","description":"List of branches defining different processing paths.\n\nEach branch has optional filter criteria and a set of processing steps.\nRecords are evaluated against branch criteria in order; the first\nmatching branch is selected.\n","items":{"type":"object","properties":{"name":{"type":"string","maxLength":300,"description":"Name of this branch.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of when and why this branch is selected.\n"},"branchId":{"type":"string","description":"Stable identifier for this branch within the tool, generated by\nthe builder. Used to reference the branch independently of its\nposition in the branches array (e.g., from step requests).\n"},"inputFilter":{"type":"object","description":"Filter criteria to determine if this branch should be selected.\n\nUses Celigo's expression-based filter format. Send `version` and `rules`\ndirectly on `inputFilter` — not inside the `{type: \"expression\", expression: {...}}`\nwrapper that export and import filters use. A wrapped filter is saved without an\nerror, but the branch is stored with no `inputFilter` and takes every input.\n","properties":{"version":{"type":"string","enum":["1"],"description":"Filter version"},"rules":{"type":"array","description":"Filter rules in Celigo expression-based filter format.\n\nArray-based DSL where the first element is an operator (e.g., \"equals\", \"and\", \"or\"),\nfollowed by operands which can be nested expressions.\n","items":{}}}},"nextRouterId":{"type":"string","description":"Identifier of the next router to chain to after this branch completes.\n\nUse \"outputRouter\" as a special terminal value to exit the tool\nand return the processing results.\n\nEvery branch needs one. A branch without it is saved, but every invoke is\nthen refused with 422 and every test run with 400 `tool_dangling_branch`\n(\"One or more branches are not merged back. Review the setup to ensure all\nbranches converge.\"), whichever branch the input takes. An id that names no\nrouter fails the save with 422 \"Invalid nextRouterId is defined on the branch.\nPlease correct the branch configuration.\"\n"},"pageProcessors":{"type":"array","description":"Processing steps to execute in this branch.\n\nEach processor references an export (lookup) or import resource\nfor data retrieval or submission. An export is a step at most once in a\ntool: using it again, on another branch or in the same one, fails the save\nwith 422 \"The export <id> is already included as a pageProcessor in your\nflow, and cannot be added again.\"\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["export","import"],"description":"Type of processor.\n\n- **export**: Retrieves data from an external system (lookup)\n- **import**: Sends data to an external system\n"},"_exportId":{"type":"string","format":"objectId","description":"Export resource reference (when type is \"export\")"},"_importId":{"type":"string","format":"objectId","description":"Import resource reference (when type is \"import\")"},"proceedOnFailure":{"type":"boolean","description":"When true, the later steps and the output stage still run after this\nprocessor fails; when false, they're skipped. Either way a direct\n`POST /v1/tools/{_id}/invoke` answers 422 with the failed step's error,\nwithout the output."},"setupInProgress":{"type":"boolean","description":"When true, the processor's configuration is still being\nset up in the UI and the step is not yet runnable.\n"},"responseMapping":{"type":"object","description":"Merges fields from this processor's response back onto the\nin-flight record so later processors and the tool's output\ncan read them. Extracts do NOT read the raw application\nresponse — they evaluate against the platform's canonical\nper-record envelope: for lookups (`type: \"export\"`) that is\n`{\"statusCode\", \"data\": [<result records>], \"errors\"}`\nwhere `data` holds the records AFTER the export's own\n`transform` and output `filter`, so paths must start from\n`data` and name transformed fields by their transformed\nname (e.g. `data[0].name`) — a lookup result field\nwritten without the `data` prefix resolves to nothing;\nfor imports it is `{\"id\", \"statusCode\", \"ignored\",\n\"_json\", ...<body fields flattened>}`, so use `id`,\n`_json.<path>`, or the body field itself at the root\n(`orderNumber` and `_json.orderNumber` read the same\nvalue). An extract may be a Handlebars expression over\nthe same envelope (`{{data.0.name}}`,\n`{{#if data.length}}found{{/if}}`; dot indexes only; a\nstring result); there is no `record` object in this\ncontext, so `{{record.…}}` renders nothing.\n","properties":{"fields":{"type":"array","description":"Simple field-level mappings","items":{"type":"object","properties":{"extract":{"type":"string","description":"Path within the canonical response envelope to\ncopy the value from (`data[0].x` / `data.0.x`\nfor lookups; `id`, `_json.<path>` or the\nflattened body field `<path>` for imports), or\na Handlebars expression over that envelope (dot\nindexes inside `{{ }}`, never a `record.` root).\n"},"generate":{"type":"string","description":"Field path on the in-flight record where the\nextracted value is stored (dot notation for\nnesting).\n"}}}},"lists":{"type":"array","description":"List-level mappings for array data","items":{"type":"object","properties":{"generate":{"type":"string","description":"Target list path"},"fields":{"type":"array","description":"Field-level mappings applied to each item in the list.","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path"},"generate":{"type":"string","description":"Target field path"}}}}}}}}},"hooks":{"type":"object","description":"Custom scripts for processing","properties":{"postResponseMap":{"type":"object","description":"Script to run after response mapping","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute"}}}}}}}}}}}}},"AIDescription":{"type":"object","description":"AI-generated descriptions and documentation for the resource.\n\nThis object contains automatically generated content that helps users\nunderstand the purpose, behavior, and configuration of the resource without\nrequiring them to analyze the technical details. The AI-generated content\nis sanitized and safe for display in the UI.\n","properties":{"summary":{"type":["string","null"],"description":"Brief AI-generated summary of the resource's purpose and functionality.\n\nThis concise description provides a quick overview of what the resource does,\nwhat systems it interacts with, and its primary role in the integration.\nThe summary is suitable for display in list views, dashboards, and other\ncontexts where space is limited.\n\nMaximum length: 10KB\n"},"detailed":{"type":["string","null"],"description":"Comprehensive AI-generated description of the resource's functionality.\n\nThis detailed explanation covers the resource's purpose, configuration details,\ndata flow patterns, filtering logic, and other technical aspects. It provides\nin-depth information suitable for documentation, tooltips, or detailed views\nin the administration interface.\n\nThe content may include HTML formatting for improved readability.\n\nMaximum length: 10KB\n"},"generatedOn":{"type":["string","null"],"format":"date-time","description":"Timestamp indicating when the AI description was generated.\n\nThis field helps track the freshness of the AI-generated content and\ndetermine when it might need to be regenerated due to changes in the\nresource's configuration or behavior.\n\nThe timestamp is recorded in ISO 8601 format with UTC timezone (Z suffix).\n"}}},"Tool":{"type":"object","required":["_id","name","_integrationId","createdAt","lastModified"],"description":"Tool object as returned by the API.","allOf":[{"$ref":"#/components/schemas/ToolBase"},{"$ref":"#/components/schemas/ResourceResponse"},{"type":"object","properties":{"_sourceId":{"type":"string","format":"objectId","readOnly":true,"description":"Origin resource ID when this tool was created by cloning or installing a template."},"draftExpiresAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when a draft tool auto-deletes. Server-computed when `draft` is set at\ncreation."}}}]},"ResourceResponse":{"type":"object","description":"Response","properties":{"_id":{"type":"string","format":"objectId","readOnly":true,"description":"Unique identifier for the resource. Format is a 24-character hexadecimal string."},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was created. Set automatically and cannot be modified."},"lastModified":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was last updated. Changes whenever any property is modified."},"deletedAt":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the resource was soft-deleted. When null or absent, the resource is active."}},"required":["_id"]},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"400-bad-request":{"description":"Bad request. The server could not understand the request because of malformed syntax or invalid parameters.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}}}},"paths":{"/v1/tools":{"post":{"summary":"Create a tool","description":"Creates a new tool within an integration. `name` and `_integrationId`\nare required. Routers use `first_matching_branch` strategy only, and each\nbranch's `nextRouterId` must point to another router's `id` or\n`\"outputRouter\"` to exit the tool.","operationId":"createTool","tags":["Tools"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Request"}}}},"responses":{"201":{"description":"Tool created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"400":{"$ref":"#/components/responses/400-bad-request"},"401":{"$ref":"#/components/responses/401-unauthorized"},"422":{"description":"Validation failed. Notably, nesting tools inside tools beyond 5\nlevels is rejected with `tool_nesting_depth_exceeded` (\"Tool cannot\nbe added. It exceeds the maximum nesting depth of 5 levels.\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````

## Get a tool

> Returns the complete configuration of a specific tool.

````json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Tool":{"type":"object","required":["_id","name","_integrationId","createdAt","lastModified"],"description":"Tool object as returned by the API.","allOf":[{"$ref":"#/components/schemas/ToolBase"},{"$ref":"#/components/schemas/ResourceResponse"},{"type":"object","properties":{"_sourceId":{"type":"string","format":"objectId","readOnly":true,"description":"Origin resource ID when this tool was created by cloning or installing a template."},"draftExpiresAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when a draft tool auto-deletes. Server-computed when `draft` is set at\ncreation."}}}]},"ToolBase":{"type":"object","description":"Writable tool fields shared by the request and response schemas.","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable name for the tool.\n\nDisplayed in the UI and used to identify the tool's purpose.\n"},"description":{"type":"string","maxLength":5120,"description":"Optional detailed description of what the tool does.\n\nUse this to document the tool's purpose, expected inputs/outputs,\nand any special considerations.\n"},"_integrationId":{"type":"string","format":"objectId","description":"Reference to the integration this tool belongs to.\n\nEvery tool must be associated with an integration. The integration\ndetermines the scope and access controls for the tool.\n"},"input":{"$ref":"#/components/schemas/Input"},"output":{"$ref":"#/components/schemas/Output"},"routers":{"type":"array","description":"Optional routers for conditional processing logic.\n\nRouters allow you to direct input data to different processing branches\nbased on filter criteria or script logic. Tools only support\n\"first_matching_branch\" routing strategy.\n\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal value to exit the tool.\n\nA tool saves without `routers`, but it can't run: `POST /v1/tools/{_id}/invoke`\nand a test run answer 400 `tool_empty_router` (\"Tool has no steps configured.\nAdd at least one step before executing the tool.\"). A full PUT that omits\n`routers` removes the steps the same way. Put linear steps in one pass-through\nrouter with a single branch.\n","items":{"$ref":"#/components/schemas/Router"}},"aiDescription":{"$ref":"#/components/schemas/AIDescription"},"draft":{"type":"boolean","description":"When true, this tool is a draft that auto-deletes when its expiry passes\n(`draftExpiresAt` in the response). Set at creation; an update can clear the\nflag but never set it."}}},"Input":{"type":"object","description":"Configuration for the tool's input processing.\n\nDefines the expected input structure, optional transformations to apply\nbefore routing, and mock data for testing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the input configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the expected input data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the expected input data structure.\n\nUsed for validation, documentation, and AI-assisted tooling.\nMust be a valid JSON Schema document. Leave `$schema` out or name draft-07: a\n`$schema` naming draft 2020-12 fails the save with 422 \"Invalid input.schema -\nno schema with key or ref ...\". An `$id` saves.\n\nEach invoke's input is validated against it. A missing `required` property\nrefuses the call with 422 `tool_input_schema_validation_failed` (\"Tool input\nvalidation failed: must have required property '<name>'\"), and a failed\n`pattern` with 422 \"Tool input validation failed: <field>: must match pattern\n...\". A property made required later therefore breaks callers that don't send\nit.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; invocation input validates the same either way.\nUnset opens the visual editor, and an edit there rebuilds `schema` from the field list,\nkeeping only `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"transform":{"$ref":"#/components/schemas/Transform"},"traceKeyTemplate":{"type":"string","maxLength":1024,"description":"Handlebars template evaluated against the invocation input (`record`) to produce\nthe `traceKey` stored on each run-history entry, so an invocation can be found again\nwith the `traceKeyPrefix` filter on `GET /v1/tools/{_id}/invocations`. Omit it and\nentries carry an empty `traceKey`."},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Input stage until this time: while it lies in\nthe future, every record entering the tool is written to\n`GET /v1/tools/{_id}/tool_input/requests` with its input-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/input/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Sample input saved with the tool. `POST /v1/tools/{_id}/test/run` doesn't read it:\nsend it as that call's `input`, or the run starts from an empty record. Maximum\nsize: 1MB.\n","additionalProperties":true}}},"Transform":{"type":"object","description":"Configuration for transforming data during processing operations. This object enables\nreshaping of records.\n\n**Transformation capabilities**\n\nCeligo's transformation engine offers powerful features for data manipulation:\n- Precise field mapping with JSONPath expressions\n- Support for any level of nested arrays\n- Formula-based field value generation\n- Dynamic references to flow and integration settings\n\n**Implementation approaches**\n\nThere are two distinct transformation mechanisms available:\n\n**Rule-Based Transformation (`type: \"expression\"`)**\n- **Best For**: Most transformation scenarios from simple to complex\n- **Capabilities**: Field mapping, formula calculations, lookups, nested data handling\n- **Advantages**: Visual configuration, no coding required, intuitive interface\n- **Configuration**: Define rules in the `expression` object\n- **Use When**: You have clear mapping requirements or need to reshape data structure\n\n**Script-Based Transformation (`type: \"script\"`)**\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Capabilities**: Full programmatic control, custom processing, complex business rules\n- **Advantages**: Maximum flexibility, can implement any transformation logic\n- **Configuration**: Reference a script in the `script` object\n- **Use When**: Visual transformation tools aren't sufficient for your use case\n","properties":{"type":{"type":"string","description":"Determines which transformation mechanism to use. This choice affects which properties\nmust be configured and how transformation logic is implemented.\n\n**Available types**\n\n**Rule-Based Transformation (`\"expression\"`)**\n- **Required Config**: The `expression` object with mapping definitions\n- **Behavior**: Applies declarative rules to reshape data\n- **Best For**: Most transformation scenarios from simple to complex\n- **Advantages**: Visual configuration, no coding required\n\n**Script-Based Transformation (`\"script\"`)**\n- **Required Config**: The `script` object with _scriptId and function\n- **Behavior**: Executes custom JavaScript to transform data\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Advantages**: Maximum flexibility, can implement any logic\n\n**Implementation guidance**\n\n1. For standard data transformations, use `\"expression\"`\n2. For complex logic or specialized processing, use `\"script\"`\n3. When selecting a type, you must configure the corresponding object:\n    - `type: \"expression\"` requires the `expression` object\n    - `type: \"script\"` requires the `script` object\n","enum":["expression","script"]},"expression":{"type":"object","description":"Configuration for declarative rule-based transformations. This object enables reshaping data\nwithout requiring custom code.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"expression\" and should not be\nconfigured otherwise. It provides a standardized way to define transformation rules that\ncan map, modify, and generate data elements.\n\n**Implementation guidance**\n\nThe expression system uses a rule-based approach where:\n- Field mappings define how input data is transformed to target fields\n- Formulas can be used to calculate or generate new values\n- Lookups can enrich data by fetching related information\n- Mode determines how records are processed (create new or modify existing)\n","properties":{"version":{"type":"string","description":"Version of the expression format. Determines which rules\nproperty contains the transformation logic.\n","enum":["1","2"]},"rules":{"type":"array","description":"Transformation rules for version 1 expressions. An array of\nrule groups; each group is an array of field-mapping objects.\nMost transforms have a single group. Present when `version`\nis `\"1\"`. The output record contains ONLY the generated\nfields — every unmapped field is dropped (v1 has no\nequivalent of Transform 2.0's `modify` mode), and the\nrecord's trace key does not survive the rebuild.\n","items":{"type":"array","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path to read from. Supports multiple\nsyntaxes: bare field names (`id`), dot notation\n(`fulfillment.shipment_id`), slash-prefixed paths\nfor XML (`/FeedProcessingStatus`), wildcards (`*.id`,\n`*.[Internal ID]`), and array indexing (`SDF[0]`).\n"},"generate":{"type":"string","description":"Target field name to write to. Typically a bare name\n(`id`) or dot path (`SDF.Filter.ID`).\n"},"key":{"type":"string","description":"Auto-generated identifier for this rule, used by the\nUI to track individual rules for editing and reordering.\n"}},"required":["extract","generate"]}}},"rulesTwoDotZero":{"type":"object","description":"Configuration for version 2 transformation rules. This object contains the core logic\nfor how data is mapped, enriched, and transformed.\n\n**Capabilities**\n\nTransformation 2.0 provides:\n- Precise field mapping with JSONPath expressions\n- Support for deeply nested data structures\n- Formula-based field generation\n- Dynamic lookups for data enrichment\n- Multiple operating modes to fit different scenarios\n","properties":{"mode":{"type":"string","description":"Transformation mode that determines how records are handled during processing.\n\n**Available modes**\n\n**Create Mode (`\"create\"`)**\n- **Behavior**: Builds entirely new output records from inputs\n- **Use When**: Output structure differs significantly from input\n- **Advantage**: Clean slate approach, no field inheritance\n\n**Modify Mode (`\"modify\"`)**\n- **Behavior**: Makes targeted edits to existing records\n- **Use When**: Output structure should remain similar to input\n- **Advantage**: Preserves unmapped fields from the original record\n","enum":["create","modify"]},"mappings":{"$ref":"#/components/schemas/Mappings"},"lookups":{"allOf":[{"description":"Shared lookup tables used across all mappings defined in the transformation rules.\n\n**Purpose**\n\nLookups provide centralized value translation that can be referenced from any mapping\nin your transformation configuration. They enable consistent translation of codes, IDs,\nand values between systems without duplicating translation logic.\n\n**Usage in transformations**\n\nLookups are particularly valuable in transformations for:\n\n- **Data Normalization**: Standardizing values from diverse source systems\n- **Code Translation**: Converting between different coding systems (e.g., status codes)\n- **Field Enrichment**: Adding descriptive values based on ID or code lookups\n- **Cross-Reference Resolution**: Mapping identifiers between integrated systems\n\n**Implementation**\n\nLookups are defined once in this array and referenced by name in mappings:\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"statusMapping\",\n    \"map\": {\n      \"A\": \"Active\",\n      \"I\": \"Inactive\",\n      \"P\": \"Pending\"\n    },\n    \"default\": \"Unknown Status\"\n  }\n]\n```\n\nThen referenced in mappings using the lookupName property:\n\n```json\n{\n  \"generate\": \"status\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.statusCode\",\n  \"lookupName\": \"statusMapping\"\n}\n```\n\nThe system automatically applies the lookup during transformation processing.\n\nFor complete details on lookup properties and behavior, see the Lookups schema.\n"},{"$ref":"#/components/schemas/Lookups"}]},"inputContext":{"type":"string","enum":["record","envelope"],"description":"Controls the JSON shape the transformTwoDotZero processor\nevaluates `mappings[].extract` JSONPath values against at\nflow runtime. Applies only to Transform 2.0 (v2,\n`rulesTwoDotZero`); v1 transforms (the `rules` array on\n`transform.expression.rules`) and script-mode transforms\nignore this field.\n"}}}}},"script":{"type":"object","description":"Configuration for programmable script-based transformations. This object enables complex, custom\ntransformation logic beyond what expression-based transformations can provide.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"script\" and should not be configured\notherwise. It provides a way to execute custom JavaScript code to transform data according to\nspecialized business rules or complex algorithms.\n\n**Implementation approach**\n\nScript-based transformation works by:\n1. Executing the specified function from the referenced script\n2. Passing input data to the function\n3. Using the function's return value as the transformed output\n\n**Common use cases**\n\nScript transformation is ideal for:\n- Complex business logic that can't be expressed through mappings\n- Algorithmic transformations requiring computation\n- Dynamic transformations based on external factors\n- Legacy system data format compatibility\n- Multi-stage processing with intermediate steps\n\nOnly use script-based transformation when expression-based transformation is insufficient.\nScript transformation requires maintaining custom code, which adds complexity to the integration.\n","properties":{"_scriptId":{"type":"string","description":"Reference to a predefined script resource containing the transformation logic.\n\nThe referenced script should contain the function specified in the\n'function' property. It must be a script in this account: any other id fails the save\nwith 422 (\"Only a script owned by the user or a script shared by the Integration App can\nbe used at transform.script._scriptId.\").\n","format":"objectid"},"function":{"type":"string","description":"Name of the function within the script to execute for transformation. This function\nmust exist in the script referenced by _scriptId.\n"}}}}},"Mappings":{"type":"array","description":"Array of field mapping configurations for transforming data from one format into another.\n\n**Guidance**\n\nThis schema is designed around RECURSION as its core architectural principle. Understanding this recursive\nnature is essential for building effective mappings:\n\n1. The schema is self-referential by design - a mapping can contain nested mappings of the same structure\n2. Complex data structures (nested objects and arrays of objects, to any depth) are ALL\n   handled through this recursive pattern; an array of arrays is not (build one in a script hook)\n3. Each mapping handles one level of the data structure; deeper levels are handled by nested mappings\n\nWhen generating mappings programmatically:\n- For simple fields (string, number, boolean): Create single mapping objects\n- For objects: Create a parent mapping with nested 'mappings' array containing child field mappings\n- For arrays: Use 'buildArrayHelper' with extract paths defining array inputs and\n  recursive 'mappings' to define object structures\n\nThe system will process these nested structures recursively during runtime, ensuring proper construction\nof complex hierarchical data while maintaining excellent performance.\n\nOn save, a leaf row with neither a source (`extract`, `hardCodedValue`, `lookupName`) nor a\ndestination (`generate`) is dropped silently, at every nesting level, and so is an object or\nobject-array row left with no source, no destination and no remaining child `mappings` or\n`buildArrayHelper` entries; a row with only one of the two, and a source-less, destination-less\nwrapper that still has children, are kept.\n\nThe shape of the array is the mapper's **JSON structure**. Named top-level items produce an object\nrecord (`record { }`). Exactly one top-level item with `dataType: objectarray` and no `generate` is\nthe `rows [ ]` structure: the mapped record is an array — usually one element per element of the\nsource array that root's `buildArrayHelper` iterates — Handlebars templates such as `http.body`\nbind it as `rows` instead of `record`, and the mapping editor shows that row's destination disabled.\n","items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}}},"items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}},"Lookups":{"type":"array","description":"Configuration for value-to-value transformations using lookup tables.\n\n**Purpose**\n\nLookups provide a way to translate values from one system to another. They transform\ninput values into output values using either static mapping tables or\ndynamic lookup caches.\n\n**Lookup mechanisms**\n\nThere are two distinct lookup mechanisms available:\n\n1. **Static Lookups**: Define a simple key-value map object and store it as part of your resource\n   - Best for: Small, fixed sets of values that rarely change\n   - Implementation: Configure the `map` object with input-to-output value mappings\n   - Example: Country codes, status values, simple translations\n\n2. **Dynamic Lookups**: Reference an existing 'Lookup Cache' resource in your Celigo account\n   - Best for: Large datasets, frequently changing values, or complex reference data\n   - Implementation: Configure `_lookupCacheId` to reference cached data maintained independently\n   - Example: Product catalogs, customer databases, pricing information\n\n**Property usage**\n\nThere are two mutually exclusive ways to configure lookups, depending on which mechanism you choose:\n\n1. **For Static Mappings**: Configure the `map` property with a direct key-value object\n   ```json\n   \"map\": {\"US\": \"United States\", \"CA\": \"Canada\"}\n   ```\n\n2. **For Dynamic Lookups**: Configure the following properties:\n   - `_lookupCacheId`: Reference to the lookup cache resource\n   - `extract`: JSON path to extract specific value from the returned lookup object\n\n**When to use**\n\nLookups are ideal for:\n\n1. **Value Translation**: Mapping codes or IDs to human-readable values\n\n2. **Data Enrichment**: Adding related information to records during processing\n\n3. **Normalization**: Ensuring consistent formatting of values across systems\n\n**Implementation details**\n\nLookups can be referenced in:\n\n1. **Field Mappings**: Direct use in field transformation configurations\n\n2. **Handlebars Templates**: Use within templates with the syntax:\n   ```\n   {{lookup 'lookupName' record.fieldName}}\n   ```\n\n**Example usage**\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"countryCodeToName\",\n    \"map\": {\n      \"US\": \"United States\",\n      \"CA\": \"Canada\",\n      \"UK\": \"United Kingdom\"\n    },\n    \"default\": \"Unknown Country\",\n    \"allowFailures\": true\n  },\n  {\n    \"name\": \"productDetails\",\n    \"_lookupCacheId\": \"60a2c4e6f321d800129a1a3c\",\n    \"extract\": \"$.details.price\",\n    \"allowFailures\": false\n  }\n]\n```\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for the lookup table within this configuration.\n\nThis name must be unique within the scope where the lookup is defined and is used to reference\nthe lookup in handlebars templates with the syntax {{lookup 'name' value}}.\n\nChoose descriptive names that indicate the transformation purpose, such as:\n- \"countryCodeToName\" for country code to full name conversion\n- \"statusMapping\" for status code translations\n- \"departmentCodes\" for department code to name mapping\n"},"map":{"type":["object","null"],"description":"The lookup mapping table as key-value pairs. The platform stores `null`\nhere on dynamic lookups, which resolve values at runtime instead of\nfrom a static table.\n\nThis object contains the input values as keys and their corresponding\noutput values. When a input value matches a key in this object,\nit will be replaced with the corresponding value.\n\nThe map should be kept to a reasonable size (typically under 100 entries)\nfor optimal performance. For larger mapping requirements, consider using\ndynamic lookups instead.\n\nMaps can include:\n- Simple code to name conversions: {\"US\": \"United States\"}\n- Status transformations: {\"A\": \"Active\", \"I\": \"Inactive\"}\n- ID to name mappings: {\"100\": \"Marketing\", \"200\": \"Sales\"}\n\nValues can be strings, numbers, or booleans, but all are stored as strings\nin the configuration.\n"},"_lookupCacheId":{"type":"string","description":"Reference to a LookupCache resource that contains the reference data for the lookup.\n\n**Purpose**\n\nThis field connects the lookup to an external data source that has been cached in the system.\nUnlike static lookups that use the `map` property, dynamic lookups can reference large datasets\nor frequently changing information without requiring constant updates to the integration.\n\n**Implementation details**\n\nThe LookupCache resource referenced by this ID contains:\n- The data records to be used as a reference source\n- Configuration for how the data should be indexed and accessed\n- Caching parameters to balance performance with data freshness\n\n**Usage patterns**\n\nCommonly used to reference:\n- Product catalogs or SKU databases\n- Customer or account information\n- Pricing tables or discount rules\n- Complex business logic lookup tables\n\nFormat: 24-character hexadecimal string (MongoDB ObjectId)\n","format":"objectid"},"extract":{"type":"string","description":"JSON path expression that extracts a specific value from the cached lookup object.\n\n**Purpose**\n\nWhen using dynamic lookups with a LookupCache, this JSON path identifies which field to extract\nfrom the cached object after it has been retrieved using the lookup key.\n\n**Implementation details**\n\n- Must use JSON path syntax (similar to mapping extract fields)\n- Operates on the cached object returned by the lookup operation\n- Examples:\n  - \"$.name\" - Extract the name field from the top level\n  - \"$.details.price\" - Extract a nested price field\n  - \"$.attributes[0].value\" - Extract a value from the first element of an array\n\n**Usage scenario**\n\nWhen a lookup cache contains complex objects:\n```json\n// Cache entry for key \"PROD-123\":\n{\n  \"id\": \"PROD-123\",\n  \"name\": \"Premium Widget\",\n  \"details\": {\n    \"price\": 99.99,\n    \"currency\": \"USD\",\n    \"inStock\": true\n  }\n}\n```\n\nSetting extract to \"$.details.price\" would return 99.99 as the lookup result.\n\nIf no extract is provided, the entire cached object is returned as the lookup result.\n"},"default":{"type":["string","null"],"description":"Default value to use when the source value is not found in the lookup map.\nThe platform stores `null` here when no default is configured.\n\nThis value is used as a fallback when:\n1. The source value doesn't match any key in the map\n2. allowFailures is true or omitted\n\nSetting an appropriate default helps prevent flow failures due to unexpected\nvalues and provides predictable behavior for edge cases.\n\nCommon default patterns include:\n- Descriptive unknowns: \"Unknown Country\", \"Unspecified Status\"\n- Original value indicators: \"{Original Value}\", \"No mapping found\"\n- Neutral values: \"Other\", \"N/A\", \"Miscellaneous\"\n\nA save refuses a default together with an explicit `allowFailures: false`\n(422 `Lookups with default set can not have allowFailures explicitly set to\nfalse.`): a lookup either fails a miss (`allowFailures: false`, no default) or\nfalls back to its default.\n"},"allowFailures":{"type":["boolean","null"],"description":"When true, missing lookup values will use the default value rather than causing an error.\n\n**Behavior control**\n\nThis field determines how the system handles source values that don't exist in the map:\n\n- true: Use the default value for missing mappings and continue processing\n- false: Treat missing mappings as errors, failing the record (`lookup_error` for a\n  static map, `lookupcache_evaluation_failed` for a lookup cache)\n- omitted or null: the record doesn't fail either. On an import a static lookup writes\n  its default if it has one, and a static or cache lookup without one leaves the\n  mapped field out of the output, with no error\n\n**Recommendation**\n\nSet this to true when:\n- New source values might appear over time\n- Data quality issues could introduce unexpected values\n- Processing should continue even with imperfect mapping\n\nSet this to false when:\n- Complete data accuracy is critical\n- All possible source values are known and controlled\n- Missing mappings indicate serious data problems that should be addressed\n\nA default raises no error: records that miss go out with the default value and\nnothing reports them. Use true only when that fallback is acceptable at the\ndestination, and set false explicitly when a missing value must stop the record.\n"}}}},"Output":{"type":"object","description":"Configuration for the tool's output processing.\n\nDefines how the tool's results are mapped, transformed, and enriched\nbefore being returned. Supports field mappings, lookups for data\nenrichment, and custom script hooks for pre/post-mapping processing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the output configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the output data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the output data structure.\n\nUsed for documentation and validation of the tool's output.\nMust be a valid JSON Schema document.\n\nThe mapped output is validated against it. While the stored schema holds\n`additionalProperties: false`, `POST /v1/tools/{_id}/invoke` answers 422 \"Tool\noutput validation failed: must NOT have additional properties\" when the output\nmappings write a field the schema doesn't list.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; the output validates the same either way. Unset\nopens the visual editor, and an edit there rebuilds `schema` from the field list, keeping\nonly `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"mappings":{"description":"Field mappings to transform data into the output format.\n\nMaps data from processing results to the output structure.\nUses Celigo's standard mapping format with extract/generate field paths —\na flat array of mapping entries (each entry may recurse via its own\nnested ``mappings`` for object/array structures).\n","allOf":[{"$ref":"#/components/schemas/Mappings"}]},"lookups":{"type":"array","description":"Lookup tables for data enrichment during output processing.\n\nStatic key-value mappings used to translate values (e.g., status codes,\ncategory names) during output generation.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the lookup, used to reference it from mappings.\n"},"map":{"type":"object","description":"Key-value mapping object. Keys are the input values and\nvalues are the corresponding output values.\n","additionalProperties":true},"default":{"type":"string","description":"Default value returned when the input key is not found in the map.\n"},"allowFailures":{"type":"boolean","description":"Whether to continue processing if the lookup fails to find a match\nand no default is provided.\n"}}}},"hooks":{"type":"object","description":"Custom script hooks for pre- and post-mapping processing.\n\nAllows running custom JavaScript functions before and after\noutput mappings are applied.\n","properties":{"preMap":{"type":"object","description":"Script to run before applying output mappings.\n\nCan modify the data before it is mapped to the output structure.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}},"postMap":{"type":"object","description":"Script to run after applying output mappings.\n\nCan modify the final output data after mappings are applied.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}}}},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Output stage until this time: while it lies in\nthe future, every post-mapped record is written to\n`GET /v1/tools/{_id}/tool_output/requests` with its output-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/output/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Mock data for testing the tool's output processing.\n\nProvides sample data that would arrive from the routing/processing\nstage, used to test mapping and lookup logic. Maximum size: 1MB.\n","additionalProperties":true}}},"Router":{"type":"object","description":"Configuration for conditional routing within a tool.\n\nRouters evaluate input data and direct it to different processing branches\nbased on criteria. This enables complex business logic and conditional\nprocessing within the tool.\n\nUnlike flows, tools only support \"first_matching_branch\" routing strategy.\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal sink to exit the tool and return results.\n\nA router comes in two shapes, told apart by whether `routeRecordsTo` and\n`routeRecordsUsing` are set: a **branching router** sets both and has two or\nmore branches, each taking the inputs its `inputFilter` (or the router's\n`script`) matches — the node you see in the Tool builder; a **pass-through\nrouter** sets neither and has exactly one branch that every input goes\nstraight through — not drawn, it holds the steps before the first branching\nrouter and merges branches back into shared steps (branches rejoin only by\npointing `nextRouterId` at the same router).\n","properties":{"id":{"type":"string","maxLength":36,"pattern":"^[A-Za-z0-9_-]+$","description":"Unique identifier for this router within the tool.\n\nUsed to reference this router from other routers' branch `nextRouterId`.\nUse at most 36 letters, digits, underscores and hyphens: a longer id fails\nthe save with 422 (\"Router id is missing or contains more than 36 characters\nor invalid characters. Please correct the router id.\"), as on flow routers.\n"},"name":{"type":"string","maxLength":300,"description":"Human-readable name for the router.\n"},"routeRecordsTo":{"type":"string","enum":["first_matching_branch"],"description":"Routing strategy. Tools only support \"first_matching_branch\",\nwhich routes to the first branch whose criteria match the input.\nSet together with `routeRecordsUsing` on a branching router; leave\nboth unset on a one-branch pass-through router.\n"},"routeRecordsUsing":{"type":"string","enum":["input_filters","script"],"description":"How a branching router decides which branch an input matches. Set together\nwith `routeRecordsTo`; leave both unset on a one-branch pass-through router.\n"},"script":{"type":"object","description":"Script configuration when routeRecordsUsing is \"script\".\n\nThe function should return the name of the branch to route to.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name that returns the branch name"}}},"branches":{"type":"array","description":"List of branches defining different processing paths.\n\nEach branch has optional filter criteria and a set of processing steps.\nRecords are evaluated against branch criteria in order; the first\nmatching branch is selected.\n","items":{"type":"object","properties":{"name":{"type":"string","maxLength":300,"description":"Name of this branch.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of when and why this branch is selected.\n"},"branchId":{"type":"string","description":"Stable identifier for this branch within the tool, generated by\nthe builder. Used to reference the branch independently of its\nposition in the branches array (e.g., from step requests).\n"},"inputFilter":{"type":"object","description":"Filter criteria to determine if this branch should be selected.\n\nUses Celigo's expression-based filter format. Send `version` and `rules`\ndirectly on `inputFilter` — not inside the `{type: \"expression\", expression: {...}}`\nwrapper that export and import filters use. A wrapped filter is saved without an\nerror, but the branch is stored with no `inputFilter` and takes every input.\n","properties":{"version":{"type":"string","enum":["1"],"description":"Filter version"},"rules":{"type":"array","description":"Filter rules in Celigo expression-based filter format.\n\nArray-based DSL where the first element is an operator (e.g., \"equals\", \"and\", \"or\"),\nfollowed by operands which can be nested expressions.\n","items":{}}}},"nextRouterId":{"type":"string","description":"Identifier of the next router to chain to after this branch completes.\n\nUse \"outputRouter\" as a special terminal value to exit the tool\nand return the processing results.\n\nEvery branch needs one. A branch without it is saved, but every invoke is\nthen refused with 422 and every test run with 400 `tool_dangling_branch`\n(\"One or more branches are not merged back. Review the setup to ensure all\nbranches converge.\"), whichever branch the input takes. An id that names no\nrouter fails the save with 422 \"Invalid nextRouterId is defined on the branch.\nPlease correct the branch configuration.\"\n"},"pageProcessors":{"type":"array","description":"Processing steps to execute in this branch.\n\nEach processor references an export (lookup) or import resource\nfor data retrieval or submission. An export is a step at most once in a\ntool: using it again, on another branch or in the same one, fails the save\nwith 422 \"The export <id> is already included as a pageProcessor in your\nflow, and cannot be added again.\"\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["export","import"],"description":"Type of processor.\n\n- **export**: Retrieves data from an external system (lookup)\n- **import**: Sends data to an external system\n"},"_exportId":{"type":"string","format":"objectId","description":"Export resource reference (when type is \"export\")"},"_importId":{"type":"string","format":"objectId","description":"Import resource reference (when type is \"import\")"},"proceedOnFailure":{"type":"boolean","description":"When true, the later steps and the output stage still run after this\nprocessor fails; when false, they're skipped. Either way a direct\n`POST /v1/tools/{_id}/invoke` answers 422 with the failed step's error,\nwithout the output."},"setupInProgress":{"type":"boolean","description":"When true, the processor's configuration is still being\nset up in the UI and the step is not yet runnable.\n"},"responseMapping":{"type":"object","description":"Merges fields from this processor's response back onto the\nin-flight record so later processors and the tool's output\ncan read them. Extracts do NOT read the raw application\nresponse — they evaluate against the platform's canonical\nper-record envelope: for lookups (`type: \"export\"`) that is\n`{\"statusCode\", \"data\": [<result records>], \"errors\"}`\nwhere `data` holds the records AFTER the export's own\n`transform` and output `filter`, so paths must start from\n`data` and name transformed fields by their transformed\nname (e.g. `data[0].name`) — a lookup result field\nwritten without the `data` prefix resolves to nothing;\nfor imports it is `{\"id\", \"statusCode\", \"ignored\",\n\"_json\", ...<body fields flattened>}`, so use `id`,\n`_json.<path>`, or the body field itself at the root\n(`orderNumber` and `_json.orderNumber` read the same\nvalue). An extract may be a Handlebars expression over\nthe same envelope (`{{data.0.name}}`,\n`{{#if data.length}}found{{/if}}`; dot indexes only; a\nstring result); there is no `record` object in this\ncontext, so `{{record.…}}` renders nothing.\n","properties":{"fields":{"type":"array","description":"Simple field-level mappings","items":{"type":"object","properties":{"extract":{"type":"string","description":"Path within the canonical response envelope to\ncopy the value from (`data[0].x` / `data.0.x`\nfor lookups; `id`, `_json.<path>` or the\nflattened body field `<path>` for imports), or\na Handlebars expression over that envelope (dot\nindexes inside `{{ }}`, never a `record.` root).\n"},"generate":{"type":"string","description":"Field path on the in-flight record where the\nextracted value is stored (dot notation for\nnesting).\n"}}}},"lists":{"type":"array","description":"List-level mappings for array data","items":{"type":"object","properties":{"generate":{"type":"string","description":"Target list path"},"fields":{"type":"array","description":"Field-level mappings applied to each item in the list.","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path"},"generate":{"type":"string","description":"Target field path"}}}}}}}}},"hooks":{"type":"object","description":"Custom scripts for processing","properties":{"postResponseMap":{"type":"object","description":"Script to run after response mapping","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute"}}}}}}}}}}}}},"AIDescription":{"type":"object","description":"AI-generated descriptions and documentation for the resource.\n\nThis object contains automatically generated content that helps users\nunderstand the purpose, behavior, and configuration of the resource without\nrequiring them to analyze the technical details. The AI-generated content\nis sanitized and safe for display in the UI.\n","properties":{"summary":{"type":["string","null"],"description":"Brief AI-generated summary of the resource's purpose and functionality.\n\nThis concise description provides a quick overview of what the resource does,\nwhat systems it interacts with, and its primary role in the integration.\nThe summary is suitable for display in list views, dashboards, and other\ncontexts where space is limited.\n\nMaximum length: 10KB\n"},"detailed":{"type":["string","null"],"description":"Comprehensive AI-generated description of the resource's functionality.\n\nThis detailed explanation covers the resource's purpose, configuration details,\ndata flow patterns, filtering logic, and other technical aspects. It provides\nin-depth information suitable for documentation, tooltips, or detailed views\nin the administration interface.\n\nThe content may include HTML formatting for improved readability.\n\nMaximum length: 10KB\n"},"generatedOn":{"type":["string","null"],"format":"date-time","description":"Timestamp indicating when the AI description was generated.\n\nThis field helps track the freshness of the AI-generated content and\ndetermine when it might need to be regenerated due to changes in the\nresource's configuration or behavior.\n\nThe timestamp is recorded in ISO 8601 format with UTC timezone (Z suffix).\n"}}},"ResourceResponse":{"type":"object","description":"Response","properties":{"_id":{"type":"string","format":"objectId","readOnly":true,"description":"Unique identifier for the resource. Format is a 24-character hexadecimal string."},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was created. Set automatically and cannot be modified."},"lastModified":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was last updated. Changes whenever any property is modified."},"deletedAt":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the resource was soft-deleted. When null or absent, the resource is active."}},"required":["_id"]},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}":{"get":{"summary":"Get a tool","description":"Returns the complete configuration of a specific tool.","operationId":"getToolById","tags":["Tools"],"parameters":[{"name":"_id","in":"path","description":"The unique identifier of the tool","required":true,"schema":{"type":"string","format":"objectId"}}],"responses":{"200":{"description":"Tool retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"}}}}}}
````

## Update a tool

> Replaces the tool configuration — GET the tool first, modify the fields\
> you need, then PUT the full object back. Omitting \`description\`,\
> \`routers\`, or \`aiDescription\` removes them. \`input\` and \`output\` are the\
> exception: omitting either object keeps its stored fields (only its\
> \`mockInput\` is cleared), while sending it replaces the object wholesale,\
> so include every sub-field you want to keep. \`name\` and \`\_integrationId\`\
> are required on every PUT.

````json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Request":{"type":"object","description":"Request schema for creating or updating a tool. Tools are reusable processing\nunits that encapsulate input transformation, conditional routing, and output\nmapping logic within an integration.","required":["name","_integrationId"],"allOf":[{"$ref":"#/components/schemas/ToolBase"}]},"ToolBase":{"type":"object","description":"Writable tool fields shared by the request and response schemas.","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable name for the tool.\n\nDisplayed in the UI and used to identify the tool's purpose.\n"},"description":{"type":"string","maxLength":5120,"description":"Optional detailed description of what the tool does.\n\nUse this to document the tool's purpose, expected inputs/outputs,\nand any special considerations.\n"},"_integrationId":{"type":"string","format":"objectId","description":"Reference to the integration this tool belongs to.\n\nEvery tool must be associated with an integration. The integration\ndetermines the scope and access controls for the tool.\n"},"input":{"$ref":"#/components/schemas/Input"},"output":{"$ref":"#/components/schemas/Output"},"routers":{"type":"array","description":"Optional routers for conditional processing logic.\n\nRouters allow you to direct input data to different processing branches\nbased on filter criteria or script logic. Tools only support\n\"first_matching_branch\" routing strategy.\n\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal value to exit the tool.\n\nA tool saves without `routers`, but it can't run: `POST /v1/tools/{_id}/invoke`\nand a test run answer 400 `tool_empty_router` (\"Tool has no steps configured.\nAdd at least one step before executing the tool.\"). A full PUT that omits\n`routers` removes the steps the same way. Put linear steps in one pass-through\nrouter with a single branch.\n","items":{"$ref":"#/components/schemas/Router"}},"aiDescription":{"$ref":"#/components/schemas/AIDescription"},"draft":{"type":"boolean","description":"When true, this tool is a draft that auto-deletes when its expiry passes\n(`draftExpiresAt` in the response). Set at creation; an update can clear the\nflag but never set it."}}},"Input":{"type":"object","description":"Configuration for the tool's input processing.\n\nDefines the expected input structure, optional transformations to apply\nbefore routing, and mock data for testing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the input configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the expected input data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the expected input data structure.\n\nUsed for validation, documentation, and AI-assisted tooling.\nMust be a valid JSON Schema document. Leave `$schema` out or name draft-07: a\n`$schema` naming draft 2020-12 fails the save with 422 \"Invalid input.schema -\nno schema with key or ref ...\". An `$id` saves.\n\nEach invoke's input is validated against it. A missing `required` property\nrefuses the call with 422 `tool_input_schema_validation_failed` (\"Tool input\nvalidation failed: must have required property '<name>'\"), and a failed\n`pattern` with 422 \"Tool input validation failed: <field>: must match pattern\n...\". A property made required later therefore breaks callers that don't send\nit.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; invocation input validates the same either way.\nUnset opens the visual editor, and an edit there rebuilds `schema` from the field list,\nkeeping only `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"transform":{"$ref":"#/components/schemas/Transform"},"traceKeyTemplate":{"type":"string","maxLength":1024,"description":"Handlebars template evaluated against the invocation input (`record`) to produce\nthe `traceKey` stored on each run-history entry, so an invocation can be found again\nwith the `traceKeyPrefix` filter on `GET /v1/tools/{_id}/invocations`. Omit it and\nentries carry an empty `traceKey`."},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Input stage until this time: while it lies in\nthe future, every record entering the tool is written to\n`GET /v1/tools/{_id}/tool_input/requests` with its input-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/input/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Sample input saved with the tool. `POST /v1/tools/{_id}/test/run` doesn't read it:\nsend it as that call's `input`, or the run starts from an empty record. Maximum\nsize: 1MB.\n","additionalProperties":true}}},"Transform":{"type":"object","description":"Configuration for transforming data during processing operations. This object enables\nreshaping of records.\n\n**Transformation capabilities**\n\nCeligo's transformation engine offers powerful features for data manipulation:\n- Precise field mapping with JSONPath expressions\n- Support for any level of nested arrays\n- Formula-based field value generation\n- Dynamic references to flow and integration settings\n\n**Implementation approaches**\n\nThere are two distinct transformation mechanisms available:\n\n**Rule-Based Transformation (`type: \"expression\"`)**\n- **Best For**: Most transformation scenarios from simple to complex\n- **Capabilities**: Field mapping, formula calculations, lookups, nested data handling\n- **Advantages**: Visual configuration, no coding required, intuitive interface\n- **Configuration**: Define rules in the `expression` object\n- **Use When**: You have clear mapping requirements or need to reshape data structure\n\n**Script-Based Transformation (`type: \"script\"`)**\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Capabilities**: Full programmatic control, custom processing, complex business rules\n- **Advantages**: Maximum flexibility, can implement any transformation logic\n- **Configuration**: Reference a script in the `script` object\n- **Use When**: Visual transformation tools aren't sufficient for your use case\n","properties":{"type":{"type":"string","description":"Determines which transformation mechanism to use. This choice affects which properties\nmust be configured and how transformation logic is implemented.\n\n**Available types**\n\n**Rule-Based Transformation (`\"expression\"`)**\n- **Required Config**: The `expression` object with mapping definitions\n- **Behavior**: Applies declarative rules to reshape data\n- **Best For**: Most transformation scenarios from simple to complex\n- **Advantages**: Visual configuration, no coding required\n\n**Script-Based Transformation (`\"script\"`)**\n- **Required Config**: The `script` object with _scriptId and function\n- **Behavior**: Executes custom JavaScript to transform data\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Advantages**: Maximum flexibility, can implement any logic\n\n**Implementation guidance**\n\n1. For standard data transformations, use `\"expression\"`\n2. For complex logic or specialized processing, use `\"script\"`\n3. When selecting a type, you must configure the corresponding object:\n    - `type: \"expression\"` requires the `expression` object\n    - `type: \"script\"` requires the `script` object\n","enum":["expression","script"]},"expression":{"type":"object","description":"Configuration for declarative rule-based transformations. This object enables reshaping data\nwithout requiring custom code.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"expression\" and should not be\nconfigured otherwise. It provides a standardized way to define transformation rules that\ncan map, modify, and generate data elements.\n\n**Implementation guidance**\n\nThe expression system uses a rule-based approach where:\n- Field mappings define how input data is transformed to target fields\n- Formulas can be used to calculate or generate new values\n- Lookups can enrich data by fetching related information\n- Mode determines how records are processed (create new or modify existing)\n","properties":{"version":{"type":"string","description":"Version of the expression format. Determines which rules\nproperty contains the transformation logic.\n","enum":["1","2"]},"rules":{"type":"array","description":"Transformation rules for version 1 expressions. An array of\nrule groups; each group is an array of field-mapping objects.\nMost transforms have a single group. Present when `version`\nis `\"1\"`. The output record contains ONLY the generated\nfields — every unmapped field is dropped (v1 has no\nequivalent of Transform 2.0's `modify` mode), and the\nrecord's trace key does not survive the rebuild.\n","items":{"type":"array","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path to read from. Supports multiple\nsyntaxes: bare field names (`id`), dot notation\n(`fulfillment.shipment_id`), slash-prefixed paths\nfor XML (`/FeedProcessingStatus`), wildcards (`*.id`,\n`*.[Internal ID]`), and array indexing (`SDF[0]`).\n"},"generate":{"type":"string","description":"Target field name to write to. Typically a bare name\n(`id`) or dot path (`SDF.Filter.ID`).\n"},"key":{"type":"string","description":"Auto-generated identifier for this rule, used by the\nUI to track individual rules for editing and reordering.\n"}},"required":["extract","generate"]}}},"rulesTwoDotZero":{"type":"object","description":"Configuration for version 2 transformation rules. This object contains the core logic\nfor how data is mapped, enriched, and transformed.\n\n**Capabilities**\n\nTransformation 2.0 provides:\n- Precise field mapping with JSONPath expressions\n- Support for deeply nested data structures\n- Formula-based field generation\n- Dynamic lookups for data enrichment\n- Multiple operating modes to fit different scenarios\n","properties":{"mode":{"type":"string","description":"Transformation mode that determines how records are handled during processing.\n\n**Available modes**\n\n**Create Mode (`\"create\"`)**\n- **Behavior**: Builds entirely new output records from inputs\n- **Use When**: Output structure differs significantly from input\n- **Advantage**: Clean slate approach, no field inheritance\n\n**Modify Mode (`\"modify\"`)**\n- **Behavior**: Makes targeted edits to existing records\n- **Use When**: Output structure should remain similar to input\n- **Advantage**: Preserves unmapped fields from the original record\n","enum":["create","modify"]},"mappings":{"$ref":"#/components/schemas/Mappings"},"lookups":{"allOf":[{"description":"Shared lookup tables used across all mappings defined in the transformation rules.\n\n**Purpose**\n\nLookups provide centralized value translation that can be referenced from any mapping\nin your transformation configuration. They enable consistent translation of codes, IDs,\nand values between systems without duplicating translation logic.\n\n**Usage in transformations**\n\nLookups are particularly valuable in transformations for:\n\n- **Data Normalization**: Standardizing values from diverse source systems\n- **Code Translation**: Converting between different coding systems (e.g., status codes)\n- **Field Enrichment**: Adding descriptive values based on ID or code lookups\n- **Cross-Reference Resolution**: Mapping identifiers between integrated systems\n\n**Implementation**\n\nLookups are defined once in this array and referenced by name in mappings:\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"statusMapping\",\n    \"map\": {\n      \"A\": \"Active\",\n      \"I\": \"Inactive\",\n      \"P\": \"Pending\"\n    },\n    \"default\": \"Unknown Status\"\n  }\n]\n```\n\nThen referenced in mappings using the lookupName property:\n\n```json\n{\n  \"generate\": \"status\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.statusCode\",\n  \"lookupName\": \"statusMapping\"\n}\n```\n\nThe system automatically applies the lookup during transformation processing.\n\nFor complete details on lookup properties and behavior, see the Lookups schema.\n"},{"$ref":"#/components/schemas/Lookups"}]},"inputContext":{"type":"string","enum":["record","envelope"],"description":"Controls the JSON shape the transformTwoDotZero processor\nevaluates `mappings[].extract` JSONPath values against at\nflow runtime. Applies only to Transform 2.0 (v2,\n`rulesTwoDotZero`); v1 transforms (the `rules` array on\n`transform.expression.rules`) and script-mode transforms\nignore this field.\n"}}}}},"script":{"type":"object","description":"Configuration for programmable script-based transformations. This object enables complex, custom\ntransformation logic beyond what expression-based transformations can provide.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"script\" and should not be configured\notherwise. It provides a way to execute custom JavaScript code to transform data according to\nspecialized business rules or complex algorithms.\n\n**Implementation approach**\n\nScript-based transformation works by:\n1. Executing the specified function from the referenced script\n2. Passing input data to the function\n3. Using the function's return value as the transformed output\n\n**Common use cases**\n\nScript transformation is ideal for:\n- Complex business logic that can't be expressed through mappings\n- Algorithmic transformations requiring computation\n- Dynamic transformations based on external factors\n- Legacy system data format compatibility\n- Multi-stage processing with intermediate steps\n\nOnly use script-based transformation when expression-based transformation is insufficient.\nScript transformation requires maintaining custom code, which adds complexity to the integration.\n","properties":{"_scriptId":{"type":"string","description":"Reference to a predefined script resource containing the transformation logic.\n\nThe referenced script should contain the function specified in the\n'function' property. It must be a script in this account: any other id fails the save\nwith 422 (\"Only a script owned by the user or a script shared by the Integration App can\nbe used at transform.script._scriptId.\").\n","format":"objectid"},"function":{"type":"string","description":"Name of the function within the script to execute for transformation. This function\nmust exist in the script referenced by _scriptId.\n"}}}}},"Mappings":{"type":"array","description":"Array of field mapping configurations for transforming data from one format into another.\n\n**Guidance**\n\nThis schema is designed around RECURSION as its core architectural principle. Understanding this recursive\nnature is essential for building effective mappings:\n\n1. The schema is self-referential by design - a mapping can contain nested mappings of the same structure\n2. Complex data structures (nested objects and arrays of objects, to any depth) are ALL\n   handled through this recursive pattern; an array of arrays is not (build one in a script hook)\n3. Each mapping handles one level of the data structure; deeper levels are handled by nested mappings\n\nWhen generating mappings programmatically:\n- For simple fields (string, number, boolean): Create single mapping objects\n- For objects: Create a parent mapping with nested 'mappings' array containing child field mappings\n- For arrays: Use 'buildArrayHelper' with extract paths defining array inputs and\n  recursive 'mappings' to define object structures\n\nThe system will process these nested structures recursively during runtime, ensuring proper construction\nof complex hierarchical data while maintaining excellent performance.\n\nOn save, a leaf row with neither a source (`extract`, `hardCodedValue`, `lookupName`) nor a\ndestination (`generate`) is dropped silently, at every nesting level, and so is an object or\nobject-array row left with no source, no destination and no remaining child `mappings` or\n`buildArrayHelper` entries; a row with only one of the two, and a source-less, destination-less\nwrapper that still has children, are kept.\n\nThe shape of the array is the mapper's **JSON structure**. Named top-level items produce an object\nrecord (`record { }`). Exactly one top-level item with `dataType: objectarray` and no `generate` is\nthe `rows [ ]` structure: the mapped record is an array — usually one element per element of the\nsource array that root's `buildArrayHelper` iterates — Handlebars templates such as `http.body`\nbind it as `rows` instead of `record`, and the mapping editor shows that row's destination disabled.\n","items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}}},"items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}},"Lookups":{"type":"array","description":"Configuration for value-to-value transformations using lookup tables.\n\n**Purpose**\n\nLookups provide a way to translate values from one system to another. They transform\ninput values into output values using either static mapping tables or\ndynamic lookup caches.\n\n**Lookup mechanisms**\n\nThere are two distinct lookup mechanisms available:\n\n1. **Static Lookups**: Define a simple key-value map object and store it as part of your resource\n   - Best for: Small, fixed sets of values that rarely change\n   - Implementation: Configure the `map` object with input-to-output value mappings\n   - Example: Country codes, status values, simple translations\n\n2. **Dynamic Lookups**: Reference an existing 'Lookup Cache' resource in your Celigo account\n   - Best for: Large datasets, frequently changing values, or complex reference data\n   - Implementation: Configure `_lookupCacheId` to reference cached data maintained independently\n   - Example: Product catalogs, customer databases, pricing information\n\n**Property usage**\n\nThere are two mutually exclusive ways to configure lookups, depending on which mechanism you choose:\n\n1. **For Static Mappings**: Configure the `map` property with a direct key-value object\n   ```json\n   \"map\": {\"US\": \"United States\", \"CA\": \"Canada\"}\n   ```\n\n2. **For Dynamic Lookups**: Configure the following properties:\n   - `_lookupCacheId`: Reference to the lookup cache resource\n   - `extract`: JSON path to extract specific value from the returned lookup object\n\n**When to use**\n\nLookups are ideal for:\n\n1. **Value Translation**: Mapping codes or IDs to human-readable values\n\n2. **Data Enrichment**: Adding related information to records during processing\n\n3. **Normalization**: Ensuring consistent formatting of values across systems\n\n**Implementation details**\n\nLookups can be referenced in:\n\n1. **Field Mappings**: Direct use in field transformation configurations\n\n2. **Handlebars Templates**: Use within templates with the syntax:\n   ```\n   {{lookup 'lookupName' record.fieldName}}\n   ```\n\n**Example usage**\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"countryCodeToName\",\n    \"map\": {\n      \"US\": \"United States\",\n      \"CA\": \"Canada\",\n      \"UK\": \"United Kingdom\"\n    },\n    \"default\": \"Unknown Country\",\n    \"allowFailures\": true\n  },\n  {\n    \"name\": \"productDetails\",\n    \"_lookupCacheId\": \"60a2c4e6f321d800129a1a3c\",\n    \"extract\": \"$.details.price\",\n    \"allowFailures\": false\n  }\n]\n```\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for the lookup table within this configuration.\n\nThis name must be unique within the scope where the lookup is defined and is used to reference\nthe lookup in handlebars templates with the syntax {{lookup 'name' value}}.\n\nChoose descriptive names that indicate the transformation purpose, such as:\n- \"countryCodeToName\" for country code to full name conversion\n- \"statusMapping\" for status code translations\n- \"departmentCodes\" for department code to name mapping\n"},"map":{"type":["object","null"],"description":"The lookup mapping table as key-value pairs. The platform stores `null`\nhere on dynamic lookups, which resolve values at runtime instead of\nfrom a static table.\n\nThis object contains the input values as keys and their corresponding\noutput values. When a input value matches a key in this object,\nit will be replaced with the corresponding value.\n\nThe map should be kept to a reasonable size (typically under 100 entries)\nfor optimal performance. For larger mapping requirements, consider using\ndynamic lookups instead.\n\nMaps can include:\n- Simple code to name conversions: {\"US\": \"United States\"}\n- Status transformations: {\"A\": \"Active\", \"I\": \"Inactive\"}\n- ID to name mappings: {\"100\": \"Marketing\", \"200\": \"Sales\"}\n\nValues can be strings, numbers, or booleans, but all are stored as strings\nin the configuration.\n"},"_lookupCacheId":{"type":"string","description":"Reference to a LookupCache resource that contains the reference data for the lookup.\n\n**Purpose**\n\nThis field connects the lookup to an external data source that has been cached in the system.\nUnlike static lookups that use the `map` property, dynamic lookups can reference large datasets\nor frequently changing information without requiring constant updates to the integration.\n\n**Implementation details**\n\nThe LookupCache resource referenced by this ID contains:\n- The data records to be used as a reference source\n- Configuration for how the data should be indexed and accessed\n- Caching parameters to balance performance with data freshness\n\n**Usage patterns**\n\nCommonly used to reference:\n- Product catalogs or SKU databases\n- Customer or account information\n- Pricing tables or discount rules\n- Complex business logic lookup tables\n\nFormat: 24-character hexadecimal string (MongoDB ObjectId)\n","format":"objectid"},"extract":{"type":"string","description":"JSON path expression that extracts a specific value from the cached lookup object.\n\n**Purpose**\n\nWhen using dynamic lookups with a LookupCache, this JSON path identifies which field to extract\nfrom the cached object after it has been retrieved using the lookup key.\n\n**Implementation details**\n\n- Must use JSON path syntax (similar to mapping extract fields)\n- Operates on the cached object returned by the lookup operation\n- Examples:\n  - \"$.name\" - Extract the name field from the top level\n  - \"$.details.price\" - Extract a nested price field\n  - \"$.attributes[0].value\" - Extract a value from the first element of an array\n\n**Usage scenario**\n\nWhen a lookup cache contains complex objects:\n```json\n// Cache entry for key \"PROD-123\":\n{\n  \"id\": \"PROD-123\",\n  \"name\": \"Premium Widget\",\n  \"details\": {\n    \"price\": 99.99,\n    \"currency\": \"USD\",\n    \"inStock\": true\n  }\n}\n```\n\nSetting extract to \"$.details.price\" would return 99.99 as the lookup result.\n\nIf no extract is provided, the entire cached object is returned as the lookup result.\n"},"default":{"type":["string","null"],"description":"Default value to use when the source value is not found in the lookup map.\nThe platform stores `null` here when no default is configured.\n\nThis value is used as a fallback when:\n1. The source value doesn't match any key in the map\n2. allowFailures is true or omitted\n\nSetting an appropriate default helps prevent flow failures due to unexpected\nvalues and provides predictable behavior for edge cases.\n\nCommon default patterns include:\n- Descriptive unknowns: \"Unknown Country\", \"Unspecified Status\"\n- Original value indicators: \"{Original Value}\", \"No mapping found\"\n- Neutral values: \"Other\", \"N/A\", \"Miscellaneous\"\n\nA save refuses a default together with an explicit `allowFailures: false`\n(422 `Lookups with default set can not have allowFailures explicitly set to\nfalse.`): a lookup either fails a miss (`allowFailures: false`, no default) or\nfalls back to its default.\n"},"allowFailures":{"type":["boolean","null"],"description":"When true, missing lookup values will use the default value rather than causing an error.\n\n**Behavior control**\n\nThis field determines how the system handles source values that don't exist in the map:\n\n- true: Use the default value for missing mappings and continue processing\n- false: Treat missing mappings as errors, failing the record (`lookup_error` for a\n  static map, `lookupcache_evaluation_failed` for a lookup cache)\n- omitted or null: the record doesn't fail either. On an import a static lookup writes\n  its default if it has one, and a static or cache lookup without one leaves the\n  mapped field out of the output, with no error\n\n**Recommendation**\n\nSet this to true when:\n- New source values might appear over time\n- Data quality issues could introduce unexpected values\n- Processing should continue even with imperfect mapping\n\nSet this to false when:\n- Complete data accuracy is critical\n- All possible source values are known and controlled\n- Missing mappings indicate serious data problems that should be addressed\n\nA default raises no error: records that miss go out with the default value and\nnothing reports them. Use true only when that fallback is acceptable at the\ndestination, and set false explicitly when a missing value must stop the record.\n"}}}},"Output":{"type":"object","description":"Configuration for the tool's output processing.\n\nDefines how the tool's results are mapped, transformed, and enriched\nbefore being returned. Supports field mappings, lookups for data\nenrichment, and custom script hooks for pre/post-mapping processing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the output configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the output data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the output data structure.\n\nUsed for documentation and validation of the tool's output.\nMust be a valid JSON Schema document.\n\nThe mapped output is validated against it. While the stored schema holds\n`additionalProperties: false`, `POST /v1/tools/{_id}/invoke` answers 422 \"Tool\noutput validation failed: must NOT have additional properties\" when the output\nmappings write a field the schema doesn't list.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; the output validates the same either way. Unset\nopens the visual editor, and an edit there rebuilds `schema` from the field list, keeping\nonly `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"mappings":{"description":"Field mappings to transform data into the output format.\n\nMaps data from processing results to the output structure.\nUses Celigo's standard mapping format with extract/generate field paths —\na flat array of mapping entries (each entry may recurse via its own\nnested ``mappings`` for object/array structures).\n","allOf":[{"$ref":"#/components/schemas/Mappings"}]},"lookups":{"type":"array","description":"Lookup tables for data enrichment during output processing.\n\nStatic key-value mappings used to translate values (e.g., status codes,\ncategory names) during output generation.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the lookup, used to reference it from mappings.\n"},"map":{"type":"object","description":"Key-value mapping object. Keys are the input values and\nvalues are the corresponding output values.\n","additionalProperties":true},"default":{"type":"string","description":"Default value returned when the input key is not found in the map.\n"},"allowFailures":{"type":"boolean","description":"Whether to continue processing if the lookup fails to find a match\nand no default is provided.\n"}}}},"hooks":{"type":"object","description":"Custom script hooks for pre- and post-mapping processing.\n\nAllows running custom JavaScript functions before and after\noutput mappings are applied.\n","properties":{"preMap":{"type":"object","description":"Script to run before applying output mappings.\n\nCan modify the data before it is mapped to the output structure.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}},"postMap":{"type":"object","description":"Script to run after applying output mappings.\n\nCan modify the final output data after mappings are applied.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}}}},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Output stage until this time: while it lies in\nthe future, every post-mapped record is written to\n`GET /v1/tools/{_id}/tool_output/requests` with its output-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/output/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Mock data for testing the tool's output processing.\n\nProvides sample data that would arrive from the routing/processing\nstage, used to test mapping and lookup logic. Maximum size: 1MB.\n","additionalProperties":true}}},"Router":{"type":"object","description":"Configuration for conditional routing within a tool.\n\nRouters evaluate input data and direct it to different processing branches\nbased on criteria. This enables complex business logic and conditional\nprocessing within the tool.\n\nUnlike flows, tools only support \"first_matching_branch\" routing strategy.\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal sink to exit the tool and return results.\n\nA router comes in two shapes, told apart by whether `routeRecordsTo` and\n`routeRecordsUsing` are set: a **branching router** sets both and has two or\nmore branches, each taking the inputs its `inputFilter` (or the router's\n`script`) matches — the node you see in the Tool builder; a **pass-through\nrouter** sets neither and has exactly one branch that every input goes\nstraight through — not drawn, it holds the steps before the first branching\nrouter and merges branches back into shared steps (branches rejoin only by\npointing `nextRouterId` at the same router).\n","properties":{"id":{"type":"string","maxLength":36,"pattern":"^[A-Za-z0-9_-]+$","description":"Unique identifier for this router within the tool.\n\nUsed to reference this router from other routers' branch `nextRouterId`.\nUse at most 36 letters, digits, underscores and hyphens: a longer id fails\nthe save with 422 (\"Router id is missing or contains more than 36 characters\nor invalid characters. Please correct the router id.\"), as on flow routers.\n"},"name":{"type":"string","maxLength":300,"description":"Human-readable name for the router.\n"},"routeRecordsTo":{"type":"string","enum":["first_matching_branch"],"description":"Routing strategy. Tools only support \"first_matching_branch\",\nwhich routes to the first branch whose criteria match the input.\nSet together with `routeRecordsUsing` on a branching router; leave\nboth unset on a one-branch pass-through router.\n"},"routeRecordsUsing":{"type":"string","enum":["input_filters","script"],"description":"How a branching router decides which branch an input matches. Set together\nwith `routeRecordsTo`; leave both unset on a one-branch pass-through router.\n"},"script":{"type":"object","description":"Script configuration when routeRecordsUsing is \"script\".\n\nThe function should return the name of the branch to route to.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name that returns the branch name"}}},"branches":{"type":"array","description":"List of branches defining different processing paths.\n\nEach branch has optional filter criteria and a set of processing steps.\nRecords are evaluated against branch criteria in order; the first\nmatching branch is selected.\n","items":{"type":"object","properties":{"name":{"type":"string","maxLength":300,"description":"Name of this branch.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of when and why this branch is selected.\n"},"branchId":{"type":"string","description":"Stable identifier for this branch within the tool, generated by\nthe builder. Used to reference the branch independently of its\nposition in the branches array (e.g., from step requests).\n"},"inputFilter":{"type":"object","description":"Filter criteria to determine if this branch should be selected.\n\nUses Celigo's expression-based filter format. Send `version` and `rules`\ndirectly on `inputFilter` — not inside the `{type: \"expression\", expression: {...}}`\nwrapper that export and import filters use. A wrapped filter is saved without an\nerror, but the branch is stored with no `inputFilter` and takes every input.\n","properties":{"version":{"type":"string","enum":["1"],"description":"Filter version"},"rules":{"type":"array","description":"Filter rules in Celigo expression-based filter format.\n\nArray-based DSL where the first element is an operator (e.g., \"equals\", \"and\", \"or\"),\nfollowed by operands which can be nested expressions.\n","items":{}}}},"nextRouterId":{"type":"string","description":"Identifier of the next router to chain to after this branch completes.\n\nUse \"outputRouter\" as a special terminal value to exit the tool\nand return the processing results.\n\nEvery branch needs one. A branch without it is saved, but every invoke is\nthen refused with 422 and every test run with 400 `tool_dangling_branch`\n(\"One or more branches are not merged back. Review the setup to ensure all\nbranches converge.\"), whichever branch the input takes. An id that names no\nrouter fails the save with 422 \"Invalid nextRouterId is defined on the branch.\nPlease correct the branch configuration.\"\n"},"pageProcessors":{"type":"array","description":"Processing steps to execute in this branch.\n\nEach processor references an export (lookup) or import resource\nfor data retrieval or submission. An export is a step at most once in a\ntool: using it again, on another branch or in the same one, fails the save\nwith 422 \"The export <id> is already included as a pageProcessor in your\nflow, and cannot be added again.\"\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["export","import"],"description":"Type of processor.\n\n- **export**: Retrieves data from an external system (lookup)\n- **import**: Sends data to an external system\n"},"_exportId":{"type":"string","format":"objectId","description":"Export resource reference (when type is \"export\")"},"_importId":{"type":"string","format":"objectId","description":"Import resource reference (when type is \"import\")"},"proceedOnFailure":{"type":"boolean","description":"When true, the later steps and the output stage still run after this\nprocessor fails; when false, they're skipped. Either way a direct\n`POST /v1/tools/{_id}/invoke` answers 422 with the failed step's error,\nwithout the output."},"setupInProgress":{"type":"boolean","description":"When true, the processor's configuration is still being\nset up in the UI and the step is not yet runnable.\n"},"responseMapping":{"type":"object","description":"Merges fields from this processor's response back onto the\nin-flight record so later processors and the tool's output\ncan read them. Extracts do NOT read the raw application\nresponse — they evaluate against the platform's canonical\nper-record envelope: for lookups (`type: \"export\"`) that is\n`{\"statusCode\", \"data\": [<result records>], \"errors\"}`\nwhere `data` holds the records AFTER the export's own\n`transform` and output `filter`, so paths must start from\n`data` and name transformed fields by their transformed\nname (e.g. `data[0].name`) — a lookup result field\nwritten without the `data` prefix resolves to nothing;\nfor imports it is `{\"id\", \"statusCode\", \"ignored\",\n\"_json\", ...<body fields flattened>}`, so use `id`,\n`_json.<path>`, or the body field itself at the root\n(`orderNumber` and `_json.orderNumber` read the same\nvalue). An extract may be a Handlebars expression over\nthe same envelope (`{{data.0.name}}`,\n`{{#if data.length}}found{{/if}}`; dot indexes only; a\nstring result); there is no `record` object in this\ncontext, so `{{record.…}}` renders nothing.\n","properties":{"fields":{"type":"array","description":"Simple field-level mappings","items":{"type":"object","properties":{"extract":{"type":"string","description":"Path within the canonical response envelope to\ncopy the value from (`data[0].x` / `data.0.x`\nfor lookups; `id`, `_json.<path>` or the\nflattened body field `<path>` for imports), or\na Handlebars expression over that envelope (dot\nindexes inside `{{ }}`, never a `record.` root).\n"},"generate":{"type":"string","description":"Field path on the in-flight record where the\nextracted value is stored (dot notation for\nnesting).\n"}}}},"lists":{"type":"array","description":"List-level mappings for array data","items":{"type":"object","properties":{"generate":{"type":"string","description":"Target list path"},"fields":{"type":"array","description":"Field-level mappings applied to each item in the list.","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path"},"generate":{"type":"string","description":"Target field path"}}}}}}}}},"hooks":{"type":"object","description":"Custom scripts for processing","properties":{"postResponseMap":{"type":"object","description":"Script to run after response mapping","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute"}}}}}}}}}}}}},"AIDescription":{"type":"object","description":"AI-generated descriptions and documentation for the resource.\n\nThis object contains automatically generated content that helps users\nunderstand the purpose, behavior, and configuration of the resource without\nrequiring them to analyze the technical details. The AI-generated content\nis sanitized and safe for display in the UI.\n","properties":{"summary":{"type":["string","null"],"description":"Brief AI-generated summary of the resource's purpose and functionality.\n\nThis concise description provides a quick overview of what the resource does,\nwhat systems it interacts with, and its primary role in the integration.\nThe summary is suitable for display in list views, dashboards, and other\ncontexts where space is limited.\n\nMaximum length: 10KB\n"},"detailed":{"type":["string","null"],"description":"Comprehensive AI-generated description of the resource's functionality.\n\nThis detailed explanation covers the resource's purpose, configuration details,\ndata flow patterns, filtering logic, and other technical aspects. It provides\nin-depth information suitable for documentation, tooltips, or detailed views\nin the administration interface.\n\nThe content may include HTML formatting for improved readability.\n\nMaximum length: 10KB\n"},"generatedOn":{"type":["string","null"],"format":"date-time","description":"Timestamp indicating when the AI description was generated.\n\nThis field helps track the freshness of the AI-generated content and\ndetermine when it might need to be regenerated due to changes in the\nresource's configuration or behavior.\n\nThe timestamp is recorded in ISO 8601 format with UTC timezone (Z suffix).\n"}}},"Tool":{"type":"object","required":["_id","name","_integrationId","createdAt","lastModified"],"description":"Tool object as returned by the API.","allOf":[{"$ref":"#/components/schemas/ToolBase"},{"$ref":"#/components/schemas/ResourceResponse"},{"type":"object","properties":{"_sourceId":{"type":"string","format":"objectId","readOnly":true,"description":"Origin resource ID when this tool was created by cloning or installing a template."},"draftExpiresAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when a draft tool auto-deletes. Server-computed when `draft` is set at\ncreation."}}}]},"ResourceResponse":{"type":"object","description":"Response","properties":{"_id":{"type":"string","format":"objectId","readOnly":true,"description":"Unique identifier for the resource. Format is a 24-character hexadecimal string."},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was created. Set automatically and cannot be modified."},"lastModified":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was last updated. Changes whenever any property is modified."},"deletedAt":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the resource was soft-deleted. When null or absent, the resource is active."}},"required":["_id"]},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"400-bad-request":{"description":"Bad request. The server could not understand the request because of malformed syntax or invalid parameters.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}":{"put":{"summary":"Update a tool","description":"Replaces the tool configuration — GET the tool first, modify the fields\nyou need, then PUT the full object back. Omitting `description`,\n`routers`, or `aiDescription` removes them. `input` and `output` are the\nexception: omitting either object keeps its stored fields (only its\n`mockInput` is cleared), while sending it replaces the object wholesale,\nso include every sub-field you want to keep. `name` and `_integrationId`\nare required on every PUT.","operationId":"updateTool","tags":["Tools"],"parameters":[{"name":"_id","in":"path","description":"The unique identifier of the tool","required":true,"schema":{"type":"string","format":"objectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Request"}}}},"responses":{"200":{"description":"Tool updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"400":{"$ref":"#/components/responses/400-bad-request"},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"description":"Validation failed. Notably, nesting tools inside tools beyond 5\nlevels is rejected with `tool_nesting_depth_exceeded` (\"Tool cannot\nbe added. It exceeds the maximum nesting depth of 5 levels.\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
````

## Delete a tool

> Deletes a tool. Soft-deleted and retained in the recycle bin for 30 days.\
> Fails with 422 if other resources (MCP servers, access tokens, or a\
> \`ToolImport\` import whose \`tool.\_toolId\` names it: "import \<id> must be\
> deleted before you can delete tool \<id>") still reference this tool — call\
> \`GET /v1/tools/{\_id}/dependencies\` first to check; it lists those imports.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422-dependency-conflict":{"description":"The resource has dependents that must be deleted first. Each entry\nin the `errors` array names one blocking resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}}},"paths":{"/v1/tools/{_id}":{"delete":{"summary":"Delete a tool","description":"Deletes a tool. Soft-deleted and retained in the recycle bin for 30 days.\nFails with 422 if other resources (MCP servers, access tokens, or a\n`ToolImport` import whose `tool._toolId` names it: \"import <id> must be\ndeleted before you can delete tool <id>\") still reference this tool — call\n`GET /v1/tools/{_id}/dependencies` first to check; it lists those imports.","operationId":"deleteTool","tags":["Tools"],"parameters":[{"name":"_id","in":"path","description":"The unique identifier of the tool","required":true,"schema":{"type":"string","format":"objectId"}}],"responses":{"204":{"description":"Tool deleted successfully"},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"$ref":"#/components/responses/422-dependency-conflict"}}}}}}
```

## Patch a tool

> Partially updates a tool using a JSON Patch document (RFC 6902).\
> The \`add\`, \`remove\`, \`replace\`, and \`move\` operations are supported\
> (\`copy\` and \`test\` are rejected), and only on the following whitelisted\
> paths:\
> \
> \| Path | Description |\
> \|------|-------------|\
> \| \`/name\` | Tool display name |\
> \| \`/description\` | Tool description |\
> \| \`/aiDescription\` | AI-generated description object |\
> \| \`/input/debugUntil\` | Debug-capture expiry for the Tool Input stage (ISO-8601, at most 3600 seconds from now) |\
> \| \`/output/debugUntil\` | Debug-capture expiry for the Tool Output stage (ISO-8601, at most 3600 seconds from now) |\
> \
> All other paths are rejected with 422 (\`not a whitelisted property\`). A\
> \`debugUntil\` more than 3600 seconds ahead is rejected with 422\
> \`update\_not\_allowed\` and nothing is saved; \`remove\` on an absent\
> \`debugUntil\` succeeds (204). Changing a \`debugUntil\` needs the Tool\
> update permission on the Tool's integration, like every other path.\
> \
> PATCH is the intended way to update these fields without accidentally\
> resetting other tool configuration via \`PUT\`.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"JsonPatchRequest":{"type":"array","description":"A JSON Patch document (RFC 6902). Send an array of patch\noperations on whitelisted fields — all other paths are rejected\nwith 422.","minItems":1,"items":{"$ref":"#/components/schemas/JsonPatchOperation"}},"JsonPatchOperation":{"type":"object","description":"A single JSON Patch operation (RFC 6902).","required":["op","path"],"properties":{"op":{"type":"string","enum":["replace","add","remove"],"description":"The operation to perform."},"path":{"type":"string","description":"JSON Pointer (RFC 6901) to the field to patch. Only\nwhitelisted paths are accepted — unlisted paths return\n`422` with `\"<path> is not a whitelisted property\"`."},"value":{"description":"The new value to set. Required for `replace` and `add`, omit for `remove`."}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422-unprocessable-entity":{"description":"Unprocessable entity. The request was well-formed but was unable to be followed due to semantic errors.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}":{"patch":{"summary":"Patch a tool","description":"Partially updates a tool using a JSON Patch document (RFC 6902).\nThe `add`, `remove`, `replace`, and `move` operations are supported\n(`copy` and `test` are rejected), and only on the following whitelisted\npaths:\n\n| Path | Description |\n|------|-------------|\n| `/name` | Tool display name |\n| `/description` | Tool description |\n| `/aiDescription` | AI-generated description object |\n| `/input/debugUntil` | Debug-capture expiry for the Tool Input stage (ISO-8601, at most 3600 seconds from now) |\n| `/output/debugUntil` | Debug-capture expiry for the Tool Output stage (ISO-8601, at most 3600 seconds from now) |\n\nAll other paths are rejected with 422 (`not a whitelisted property`). A\n`debugUntil` more than 3600 seconds ahead is rejected with 422\n`update_not_allowed` and nothing is saved; `remove` on an absent\n`debugUntil` succeeds (204). Changing a `debugUntil` needs the Tool\nupdate permission on the Tool's integration, like every other path.\n\nPATCH is the intended way to update these fields without accidentally\nresetting other tool configuration via `PUT`.","operationId":"patchTool","tags":["Tools"],"parameters":[{"name":"_id","in":"path","description":"The unique identifier of the tool","required":true,"schema":{"type":"string","format":"objectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonPatchRequest"}}}},"responses":{"204":{"description":"Tool patched successfully."},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"$ref":"#/components/responses/422-unprocessable-entity"}}}}}}
```

## List connections a tool depends on

> Returns the full Connection resources the tool references — both directly\
> (via \`\_connectionId\` fields on its steps) and transitively through\
> descendant resources (inner tools, lookups, imports, exports).\
> \
> Useful for discovering what systems a tool talks to before cloning, moving,\
> or evaluating the blast radius of a connection change. For the full\
> dependency tree (imports, exports, nested tools), use\
> \`GET /v1/tools/{\_id}/descendants\` instead.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Connection":{"type":"object","required":["_id","name","type","createdAt","lastModified"],"description":"Connection object as returned by the API.","allOf":[{"$ref":"#/components/schemas/ResponseBase"},{"type":"object","properties":{"netsuite":{"description":"Present when `type` is `netsuite`.","allOf":[{"$ref":"#/components/schemas/NetSuite"},{"type":"object","properties":{"suiteAppInstalled":{"type":"boolean","readOnly":true,"description":"When true, the Celigo integrator.io SuiteApp (SuiteScript 2.x) was detected in\nthe connected NetSuite account the last time the connection authenticated. It\nsays nothing about the legacy Celigo bundle; for a live check of both, use\n`GET /v1/connections/{_id}/distributedApps`."}}}]},"salesforce":{"description":"Present when `type` is `salesforce`.","allOf":[{"$ref":"#/components/schemas/Salesforce"},{"type":"object","properties":{"info":{"type":"object","readOnly":true,"description":"Salesforce org and user metadata populated by the server after a successful\nauthentication (the OpenID Connect userinfo of the authorizing user). Read-only.","properties":{"user_id":{"type":"string","readOnly":true,"description":"Salesforce user ID of the authorizing user."},"organization_id":{"type":"string","readOnly":true,"description":"Salesforce organization (org) ID this connection authenticates to."},"email":{"type":"string","format":"email","readOnly":true,"description":"Email address of the authorizing Salesforce user."},"preferred_username":{"type":"string","readOnly":true,"description":"Salesforce login username of the authorizing user."},"name":{"type":"string","readOnly":true,"description":"Full display name of the authorizing user."},"is_salesforce_integration_user":{"type":"boolean","readOnly":true,"description":"When true, the authorizing user is a Salesforce Integration User license."}}}}}]},"ftp":{"description":"Present when `type` is `ftp`.","allOf":[{"$ref":"#/components/schemas/FTP"}]},"s3":{"description":"Present when `type` is `s3`.","allOf":[{"$ref":"#/components/schemas/S3"}]},"http":{"description":"Present when `type` is `http`.","allOf":[{"$ref":"#/components/schemas/HTTP"}]},"rdbms":{"description":"Present when `type` is `rdbms`.","allOf":[{"$ref":"#/components/schemas/RDBMS"}]},"mongodb":{"description":"Present when `type` is `mongodb`.","allOf":[{"$ref":"#/components/schemas/MongoDB"}]},"as2":{"description":"Present when `type` is `as2`.","allOf":[{"$ref":"#/components/schemas/AS2"}]},"filesystem":{"description":"Present when `type` is `filesystem`.","allOf":[{"$ref":"#/components/schemas/Filesystem"}]},"mcp":{"description":"Present when `type` is `mcp`.","allOf":[{"$ref":"#/components/schemas/MCP"}]},"dynamodb":{"description":"Present when `type` is `dynamodb`.","allOf":[{"$ref":"#/components/schemas/DynamoDB"}]},"jdbc":{"description":"Present when `type` is `jdbc`.","allOf":[{"$ref":"#/components/schemas/JDBC"}]},"van":{"description":"Present when `type` is `van`.","allOf":[{"$ref":"#/components/schemas/VAN"}]},"wrapper":{"description":"Present when `type` is `wrapper`.","allOf":[{"$ref":"#/components/schemas/Wrapper"}]}}}],"if":{"properties":{"type":{"const":"netsuite"}},"required":["type"]},"then":{"required":["netsuite"]},"else":{"if":{"properties":{"type":{"const":"salesforce"}},"required":["type"]},"then":{"required":["salesforce"]},"else":{"if":{"properties":{"type":{"const":"ftp"}},"required":["type"]},"then":{"required":["ftp"]},"else":{"if":{"properties":{"type":{"const":"s3"}},"required":["type"]},"then":{"required":["s3"]},"else":{"if":{"properties":{"type":{"const":"http"}},"required":["type"]},"then":{"required":["http"]},"else":{"if":{"properties":{"type":{"const":"rdbms"}},"required":["type"]},"then":{"required":["rdbms"]},"else":{"if":{"properties":{"type":{"const":"mongodb"}},"required":["type"]},"then":{"required":["mongodb"]},"else":{"if":{"properties":{"type":{"const":"as2"}},"required":["type"]},"then":{"required":["as2"]},"else":{"if":{"properties":{"type":{"const":"filesystem"}},"required":["type"]},"then":{"required":["filesystem"]},"else":{"if":{"properties":{"type":{"const":"mcp"}},"required":["type"]},"then":{"required":["mcp"]},"else":{"if":{"properties":{"type":{"const":"dynamodb"}},"required":["type"]},"then":{"required":["dynamodb"]},"else":{"if":{"properties":{"type":{"const":"jdbc"}},"required":["type"]},"then":{"required":["jdbc"]},"else":{"if":{"properties":{"type":{"const":"van"}},"required":["type"]},"then":{"required":["van"]},"else":{"if":{"properties":{"type":{"const":"wrapper"}},"required":["type"]},"then":{"required":["wrapper"]},"else":{"if":{"properties":{"type":{"const":"rest"}},"required":["type"]},"then":{"required":["rest"]}}}}}}}}}}}}}}}},"ResponseBase":{"type":"object","description":"Shared properties present on every connection response regardless of type.","allOf":[{"$ref":"#/components/schemas/ResourceResponse"},{"$ref":"#/components/schemas/IAResourceResponse"},{"type":"object","properties":{"name":{"type":"string","description":"Display name for the connection.","maxLength":100},"type":{"type":"string","description":"The type of connection determining which authentication and connectivity options are available","enum":["netsuite","salesforce","ftp","s3","wrapper","http","rdbms","mongodb","as2","filesystem","mcp","dynamodb","jdbc","van","rest"]},"externalId":{"type":"string","description":"External identifier for the connection, often used for integration with third-party systems"},"assistant":{"type":"string","description":"Application name in lowercase for HTTP connections to systems with integrator.io adaptors.\nUsed to identify the target application being connected to.\nExamples - Shopify: \"shopify\", eBay: \"ebay\".\nOnly applicable for HTTP connection types.\n"},"_agentId":{"type":"string","format":"objectId","description":"Reference to a Celigo on-premise Agent. Required for connection types that need\nlocal network or filesystem access (JDBC, filesystem, Oracle RDBMS, and on-premise MongoDB).\nThe agent establishes a secure tunnel between the on-premise environment and integrator.io.\n"},"_borrowConcurrencyFromConnectionId":{"type":"string","format":"objectId","description":"Reference to another connection to share concurrency limits with. When set, this\nconnection's traffic counts against the referenced connection's limit instead of its own,\nand its own concurrency and auto-recovery settings have no effect. One level only: the\nreferenced connection may not itself borrow, and a connection that others borrow from\ncannot start borrowing. A connection cannot reference itself (422)."},"debugDate":{"type":"string","format":"date-time","description":"Date until which debug logging is enabled for this connection"},"settingsForm":{"type":"object","description":"Dynamic form configuration for connection-specific settings"},"settings":{"type":"object","description":"Connection-specific settings and configurations"},"pgp":{"description":"Present for file-based connections (ftp, s3, filesystem, …) that encrypt or decrypt files with PGP.","allOf":[{"$ref":"#/components/schemas/PGP"}]},"ssl":{"$ref":"#/components/schemas/SSL"},"aiDescription":{"$ref":"#/components/schemas/AIDescription"},"offline":{"type":"boolean","description":"When true, the connection has been taken offline and is skipped during flow execution.\nIt is set by the connection test a create or `PUT` runs: the result arrives in the\nwrite's response or, when the target is slow to answer, shortly afterwards (the audit\nlog records a `system` change; `lastModified` doesn't move), so read it again a few\nseconds after the write. On an `http` connection the write runs the test only when\n`http.ping` is configured, and an `http` connection that was never tested has no\n`offline` key.","readOnly":true},"needsAuthorization":{"type":"boolean","readOnly":true,"description":"OAuth handshake state. Celigo's OAuth callback sets it to `false` once the user consents\nand the tokens are stored (see `GET /v1/connection/{_id}/oauth2`). A connection that\nwas never authorized usually has no `needsAuthorization` key, so its absence does not\nmean the connection is authorized."},"_sourceId":{"type":"string","format":"objectId","description":"Source connection this was cloned from.","readOnly":true},"connectionRole":{"type":"string","enum":["standard","mcpChild"],"readOnly":true,"description":"Distinguishes account-owned connections from the per-end-user credential copies\nMCP servers collect. Absent on connections last saved before the field existed,\nwhich behave as `standard`. Sending `mcpChild` on `POST` or `PUT /v1/connections`\nfails with 422 `mcp_child_generic_route_not_allowed`, and `/connectionRole` is not a\npatchable path; child connections are written only by the MCP end-user setup page,\nnever through `/v1`."},"endUserProvidedFields":{"type":"array","items":{"type":"string"},"description":"Dot-paths of this connection's fields (for example `http.encrypted.apiKey` or\n`rdbms.user`) that each MCP end user must supply on their own `mcpChild` copy instead\nof inheriting the parent's value. Only returned on a `standard` connection that has\npaths set; never returned on a child. Writable through `POST`, `PUT`, and `PATCH`;\nby the platform contract, a write that changes the list invalidates the children end\nusers already hold against this parent."},"_parentConnectionId":{"type":"string","format":"objectId","readOnly":true,"description":"Parent connection this child copies its non-credential configuration from. Present\nonly when `connectionRole` is `mcpChild`; set by the end-user setup page and ignored\nwhen sent on a standard connection."},"_mcpOwnerId":{"type":"string","format":"objectId","readOnly":true,"description":"Account owner whose parent connection and MCP servers this child belongs to. Present\nonly when `connectionRole` is `mcpChild`; on a child, `_userId` is the end user and\nthis field is the account."},"_parentSyncedAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the child's non-credential fields were last synced from the parent\nconnection. Present only when `connectionRole` is `mcpChild`."},"_userId":{"type":"string","format":"objectId","description":"End user who owns this `mcpChild` connection's credentials. Present only when\n`connectionRole` is `mcpChild`; a standard connection belongs to the account and\nomits this field.","readOnly":true},"debugUntil":{"type":"string","format":"date-time","description":"Debug logging is active until this timestamp. Absent or in the past means debug is off.","readOnly":true},"encrypted":{"type":"string","description":"Masked placeholder for encrypted credential fields. Always returns `\"******\"`.","readOnly":true},"isHTTP":{"type":"boolean","description":"When true, the connection uses the HTTP adaptor internally, even when `type` is `wrapper`.","readOnly":true},"autoRecoverRateLimitErrors":{"type":"boolean","default":true,"description":"When true, rate-limit errors from the target system trigger automatic recovery: concurrency\ndrops to 1, the rate-limited requests are retried with doubling waits (1 to 1024 minutes,\nafter which the flag switches itself off), and concurrency climbs back toward\n`targetConcurrencyLevel`. While true, `targetConcurrencyLevel` is the throughput lever: a\n`concurrencyLevel` sent in a PUT is ignored, and one sent in a PATCH is stored but the\nrecovery machinery moves it back toward the target. Recovery never runs on `ftp`\nconnections: FTP errors aren't treated as rate limits."},"enableMicroBatchForOneToMany":{"type":"boolean","default":true,"description":"When true, enables micro-batching for one-to-many data flows through this connection."},"enableCsvObjectParsing":{"type":"boolean","default":true,"description":"When true, enables CSV-to-object parsing for data received through this connection."},"microServices":{"type":"object","description":"Per-adaptor feature flags controlling which microservice workers handle this connection's traffic.","readOnly":true,"properties":{"disableHttp":{"type":"boolean","description":"When true, the HTTP microservice worker is disabled for this connection."},"disableNetSuiteDistributed":{"type":"boolean","description":"When true, the NetSuite distributed-processing worker is disabled for this connection."},"disableNetSuiteProxy":{"type":"boolean","description":"When true, the NetSuite proxy worker is disabled for this connection."},"disableNetSuiteWebservices":{"type":"boolean","description":"When true, the NetSuite SuiteTalk Web Services worker is disabled for this connection."},"disableRdbms":{"type":"boolean","description":"When true, the RDBMS worker is disabled for this connection."},"disableAs2":{"type":"boolean","description":"When true, the AS2 worker is disabled for this connection."},"disableAs2File":{"type":"boolean","description":"When true, the AS2 file-processing worker is disabled for this connection."},"disableDataLoaderFile":{"type":"boolean","description":"When true, the data-loader file worker is disabled for this connection."},"disableFtp":{"type":"boolean","description":"When true, the FTP worker is disabled for this connection."},"disableS3":{"type":"boolean","description":"When true, the S3 worker is disabled for this connection."},"disableSalesforce":{"type":"boolean","description":"When true, the Salesforce worker is disabled for this connection."},"disableFile":{"type":"boolean","description":"When true, the generic file worker is disabled for this connection."},"disableNsFile":{"type":"boolean","description":"When true, the NetSuite file worker is disabled for this connection."},"workerGroup":{"type":"string","description":"Name of the worker group that processes this connection's traffic."}}},"queues":{"type":"array","description":"Message queue sizes for this connection. Always present on GET-by-ID; on the list endpoint only when `fetchQueueSize=true`.","readOnly":true,"items":{"type":"object","properties":{"name":{"type":"string","description":"Queue identifier (typically the connection ID)."},"size":{"type":"integer","description":"Number of messages currently in the queue."}}}}}}]},"ResourceResponse":{"type":"object","description":"Response","properties":{"_id":{"type":"string","format":"objectId","readOnly":true,"description":"Unique identifier for the resource. Format is a 24-character hexadecimal string."},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was created. Set automatically and cannot be modified."},"lastModified":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was last updated. Changes whenever any property is modified."},"deletedAt":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the resource was soft-deleted. When null or absent, the resource is active."}},"required":["_id"]},"IAResourceResponse":{"type":"object","description":"Integration app response fields for resources that are part of integration apps","properties":{"_integrationId":{"type":"string","format":"objectId","readOnly":true,"description":"Reference to the specific integration instance that contains this resource.\n\nThis field is only populated for resources that are part of an integration app\ninstallation. It contains the unique identifier (_id) of the integration\nresource that was installed in the account.\n\nThe integration instance represents a specific installed instance of an\nintegration app, with its own configuration, settings, and runtime environment.\n\nThis reference enables:\n- Tracing the resource back to its parent integration instance\n- Permission and access control based on integration ownership\n- Lifecycle management (enabling/disabling, updating, or uninstalling)\n"},"_connectorId":{"type":"string","format":"objectId","readOnly":true,"description":"Integration App (connector) that defines this resource. Present only on resources an\nIntegration App owns. The app locks parts of these documents, and a PUT that changes a\nlocked field answers 200 but keeps the stored value, so read the document back to confirm\na change."}}},"PGP":{"type":"object","description":"PGP encryption settings for file-based connections (ftp, s3, and similar). When set, files are\nPGP-encrypted before upload and/or decrypted after download. Supply at least one of `publicKey`\n(to encrypt outbound files) or `privateKey` (to decrypt inbound files); set `passphrase` whenever\n`privateKey` is provided.","properties":{"publicKey":{"type":"string","description":"ASCII-armored PGP public key used to encrypt outbound files before upload.\nSet this when the connection sends encrypted files; omit it for decrypt-only connections."},"privateKey":{"type":"string","writeOnly":true,"description":"ASCII-armored PGP private key used to decrypt inbound files after download (encrypted at rest;\nreturned masked as `\"******\"`). Set this when the connection receives encrypted files."},"passphrase":{"type":"string","writeOnly":true,"description":"Passphrase that unlocks `privateKey` (encrypted at rest; returned masked as `\"******\"`). Set this whenever `privateKey` is provided."},"compressionAlgorithm":{"type":"string","enum":["zip","zlib"],"description":"Compression applied to the message payload before PGP encryption. Match the algorithm the recipient expects; leave unset to use the server default."},"asciiArmored":{"type":"boolean","default":true,"description":"When true, produces ASCII-armored (text) PGP output. Set to false only when the recipient requires binary PGP output."}},"if":{"required":["privateKey"]},"then":{"required":["passphrase"]}},"SSL":{"type":"object","description":"SSL/TLS certificate configuration for database connections that use client certificate\n(mTLS) authentication or connect to servers with private CA-signed certificates. Provide\ncert and key together for mTLS, ca for a private CA, and passphrase only when the private\nkey is encrypted; cert/key and ca can be combined.","properties":{"ca":{"type":"string","description":"Certificate Authority certificate in PEM format. Set when the database server uses a\ncertificate signed by a private CA not in the system's default trust store.","writeOnly":true},"key":{"type":"string","description":"Client private key in PEM format, paired with cert for mTLS authentication.\nCannot be provided without cert.","writeOnly":true},"passphrase":{"type":"string","description":"Passphrase that decrypts the private key in the key field, when that key is password-protected.","writeOnly":true},"cert":{"type":"string","description":"Client certificate in PEM format, paired with key for mTLS authentication.\nCannot be provided without key.","writeOnly":true}}},"AIDescription":{"type":"object","description":"AI-generated descriptions and documentation for the resource.\n\nThis object contains automatically generated content that helps users\nunderstand the purpose, behavior, and configuration of the resource without\nrequiring them to analyze the technical details. The AI-generated content\nis sanitized and safe for display in the UI.\n","properties":{"summary":{"type":["string","null"],"description":"Brief AI-generated summary of the resource's purpose and functionality.\n\nThis concise description provides a quick overview of what the resource does,\nwhat systems it interacts with, and its primary role in the integration.\nThe summary is suitable for display in list views, dashboards, and other\ncontexts where space is limited.\n\nMaximum length: 10KB\n"},"detailed":{"type":["string","null"],"description":"Comprehensive AI-generated description of the resource's functionality.\n\nThis detailed explanation covers the resource's purpose, configuration details,\ndata flow patterns, filtering logic, and other technical aspects. It provides\nin-depth information suitable for documentation, tooltips, or detailed views\nin the administration interface.\n\nThe content may include HTML formatting for improved readability.\n\nMaximum length: 10KB\n"},"generatedOn":{"type":["string","null"],"format":"date-time","description":"Timestamp indicating when the AI description was generated.\n\nThis field helps track the freshness of the AI-generated content and\ndetermine when it might need to be regenerated due to changes in the\nresource's configuration or behavior.\n\nThe timestamp is recorded in ISO 8601 format with UTC timezone (Z suffix).\n"}}},"NetSuite":{"type":"object","description":"Configuration for NetSuite ERP connections. Used when the connection type is \"netsuite\".\nThe authType field selects the authentication method; token-based authentication (TBA) is\nrecommended for production.","required":["authType"],"properties":{"authType":{"type":"string","enum":["token","token-auto"],"description":"Authentication method for the NetSuite connection. token-auto delegates the token\nlifecycle to an iClient; token uses a manually-supplied tokenId/tokenSecret pair."},"account":{"type":"string","description":"NetSuite account ID (automatically uppercased), required for token and token-auto authentication.\nFound in NetSuite under Setup > Company > Company Information. Non-production accounts\ncarry an environment suffix (e.g. `123456_SB1`); production and beta use the bare id."},"environment":{"type":"string","enum":["production","sandbox","sandbox2.0","beta"],"description":"NetSuite environment to connect to. Defaults to production when not specified."},"tokenId":{"type":"string","description":"NetSuite TBA token ID (encrypted at rest). Required when authType is \"token\".\n\nGenerated in NetSuite under Setup > Users/Roles > Access Tokens.\nMust be paired with the corresponding tokenSecret.\n","writeOnly":true},"tokenSecret":{"type":"string","description":"NetSuite TBA token secret (encrypted at rest). Required when authType is \"token\".\n\nGenerated alongside the tokenId in NetSuite. Treat as a sensitive credential.\n","writeOnly":true},"entityId":{"type":"string","description":"NetSuite entity/user ID associated with the token."},"tokenName":{"type":"string","description":"Human-readable name of the NetSuite access token for identification purposes."},"roleId":{"type":"string","description":"NetSuite role ID that determines the permissions for this connection.\n\nThe role controls which records, fields, and operations are accessible.\nMust match the role associated with the access token in NetSuite.\n"},"requestLevelCredentials":{"type":"boolean","default":false,"description":"When true, authentication credentials are sent with each individual API request\nrather than maintaining a persistent session. The connection form sets this to true\nfor token (manual TBA) authentication.\n"},"dataCenterURLs":{"type":"object","description":"NetSuite data center URLs for the account, auto-discovered from the account ID.\nThese are populated by the server after a successful connection.\n","readOnly":true,"properties":{"restDomain":{"type":"string","description":"Base URL for the account's RESTlet and REST API endpoints."},"webservicesDomain":{"type":"string","description":"Base URL for the account's SuiteTalk (SOAP) web services endpoints."},"systemDomain":{"type":"string","description":"Base URL for the account's NetSuite UI/system endpoints."}}},"accountName":{"type":"string","description":"Human-readable NetSuite account name (display purposes only)."},"roleName":{"type":"string","description":"Human-readable name of the NetSuite role (display purposes only)."},"wsdlVersion":{"type":"string","description":"SuiteTalk Web Services WSDL version. The API stores only `latest` or `next`; any other value sent\non create or update is normalized to one of these (the connection form's \"2025.1\" maps to `latest`\nand \"2023.1\" maps to `next`; unrecognized version strings fall back to `latest`). Defaults to\n`latest`, which requires Token-Based Authentication.","enum":["latest","next"],"default":"latest"},"applicationId":{"type":"string","description":"NetSuite application ID from the integration record.\nRequired for some authentication configurations to identify the calling application.\n"},"concurrencyLevel":{"type":"integer","description":"Maximum number of requests this connection sends to NetSuite in parallel, across SOAP,\nREST and RESTlet calls. Send no more than the integrator.io license ceiling (25 standard,\n50 with an Environments license). NetSuite separately enforces one\nconcurrency limit per NetSuite account that every caller of its SOAP, REST and RESTlet\nAPIs shares — all integrator.io connections to that account and every other client\ncalling those APIs from outside integrator.io (other integrations, third-party tools).","minimum":1,"maximum":50,"default":5},"targetConcurrencyLevel":{"type":"integer","description":"Target concurrency level for auto-scaling. The system adjusts concurrency\nbetween 1 and this value based on rate limit and governance feedback.","minimum":1,"maximum":50,"default":5},"_iClientId":{"type":"string","format":"objectId","description":"ID of the iClient used for token-based authentication."}}},"Salesforce":{"type":"object","description":"Configuration for Salesforce CRM connections. Used when the connection type is \"salesforce\".\nAuthenticates via OAuth 2.0: oauth2FlowType \"jwtBearerToken\" does server-to-server auth\nthrough a Connected App (recommended for automation), while \"refreshToken\" uses an\ninteractive browser authorization for user-context integrations.","required":["oauth2FlowType","sandbox"],"properties":{"sandbox":{"type":"boolean","description":"Whether this connection targets a Salesforce non-production (sandbox) org. When true,\nauthenticates against test.salesforce.com instead of login.salesforce.com. Must match\nthe actual Salesforce org type or authentication fails. This is the form's\nproduction-vs-sandbox selector (distinct from the connection-level `sandbox` flag).","default":false},"oauth2FlowType":{"type":"string","enum":["jwtBearerToken","refreshToken"],"description":"OAuth 2.0 authentication flow type. Use jwtBearerToken for automated server-to-server\nintegrations (requires a Connected App with a digital certificate and the username field).\nUse refreshToken for integrations that operate in a specific user's context. Defaults to\nrefreshToken when omitted.","default":"refreshToken"},"username":{"type":"string","description":"Salesforce login username (email) of the user whose permissions the integration operates\nunder. Used by the jwtBearerToken flow to identify the subject of the assertion."},"_iClientId":{"type":"string","format":"objectId","description":"Reference to the iClient holding your own Salesforce Connected App's client ID and secret.\nSet this to authenticate through a custom Connected App; omit it to use Celigo's\npre-configured Connected App."},"baseURI":{"type":"string","format":"uri","description":"Salesforce instance URL for this org (e.g. \"https://mycompany.my.salesforce.com\").\nAuto-discovered and populated during OAuth authentication; set it explicitly only to\nforce a specific instance URL."},"bearerToken":{"type":"string","writeOnly":true,"description":"OAuth access token for Salesforce API calls (encrypted at rest; returned masked as\n`\"******\"`). Auto-managed by the system during the OAuth flow; rarely set manually."},"refreshToken":{"type":"string","writeOnly":true,"description":"OAuth refresh token used to mint new access tokens (encrypted at rest; returned masked as\n`\"******\"`). Obtained during the initial browser-based authorization of the refreshToken\nflow; auto-managed by the system."},"packagedOAuth":{"type":"boolean","description":"When true, the connection uses Celigo's pre-configured (packaged) Connected App. Set by\nthe system based on whether a custom `_iClientId` is supplied and the OAuth flow completed;\nnot reliably honored when supplied in the request body."},"scope":{"type":"array","items":{"type":"string"},"description":"OAuth scopes requested during authorization. Scope values are defined by Salesforce — `full`\ngrants complete API access and `refresh_token` enables long-lived refresh tokens. Defaults\nto `full` and `refresh_token` when omitted. A saved empty array is the normal stored state:\nthe connection form does not expose this field, and every save from the connection editor\nresets values written through the API.","default":["full","refresh_token"]},"concurrencyLevel":{"type":"integer","description":"Maximum number of concurrent API requests to Salesforce. Salesforce enforces per-org API\nrequest limits, so setting this high consumes the org's API call allocation faster. Send no\nmore than the account's licensed ceiling.","minimum":1,"maximum":50,"default":5},"targetConcurrencyLevel":{"type":"integer","description":"Upper bound for auto-scaling concurrency. When automatic rate-limit recovery is enabled, the\nsystem adjusts concurrency between 1 and this value based on rate-limit feedback from\nSalesforce. Send no more than the account's licensed ceiling.","minimum":1,"maximum":50,"default":5}},"if":{"properties":{"oauth2FlowType":{"const":"jwtBearerToken"}},"required":["oauth2FlowType"]},"then":{"required":["username"]}},"FTP":{"type":"object","description":"Configuration for FTP/SFTP/FTPS file transfer connections. Used when the connection type is \"ftp\".\nThe type field selects the transfer protocol, which determines port defaults and available auth methods.","required":["type","hostURI","username"],"properties":{"type":{"type":"string","enum":["ftp","sftp","ftps"],"description":"File transfer protocol type. Prefer sftp for security. Use ftps when the\nserver requires TLS. Only use ftp for legacy systems without encryption support."},"hostURI":{"type":"string","description":"FTP server hostname or IP address.\nDo NOT include the protocol prefix (e.g., use \"ftp.example.com\", not \"sftp://ftp.example.com\").\n"},"port":{"type":"number","description":"Server port number. When omitted, defaults to the standard port for the\nprotocol: 21 for ftp, 22 for sftp, 21 for explicit ftps, and 990 for implicit ftps.\n","minimum":0,"maximum":65535},"username":{"type":"string","description":"Username for server authentication."},"password":{"type":"string","writeOnly":true,"description":"Password for server authentication (encrypted at rest; returned masked as `\"******\"`).\nFor sftp, supply either password or authKey (SSH key)."},"authKey":{"type":"string","description":"SSH private key for SFTP key-based authentication (encrypted at rest; returned masked as `\"******\"`).\nOnly used when type is \"sftp\"; can be used alone or alongside a password. Must be an RSA (SSH-2) key\nin PEM format, supplied whole from its BEGIN line to its END line — OpenSSH-format keys (a first\nline that names an OPENSSH PRIVATE KEY), passphrase-protected keys, SSH-1 keys and PuTTY `.ppk` files\nare not supported and must be converted to an unencrypted PEM key first.","writeOnly":true},"usePassiveMode":{"type":"boolean","description":"When true, uses passive mode for FTP/FTPS data connections.\nIn passive mode, the client initiates both control and data connections,\nwhich works better through firewalls and NAT. Enable for most scenarios.\n","default":true},"enableHostVerification":{"type":"boolean","description":"When true, verifies the server's SSH host key (sftp) or TLS certificate (ftps).\nDisable only for development/testing with self-signed certificates.\n"},"userDirectoryIsRoot":{"type":"boolean","description":"When true, treats the user's home directory as the root directory.\nAll paths are relative to the user's home directory rather than the server root.\n","default":false},"useImplicitFtps":{"type":"boolean","description":"When true, uses implicit FTPS (TLS connection established immediately on port 990).\nWhen false, uses explicit FTPS (starts as FTP on port 21, upgrades to TLS via STARTTLS).\nOnly applies when type is \"ftps\".\n","default":false},"requireSocketReUse":{"type":"boolean","description":"When true, requires the data connection to reuse the same TLS session as the control connection.\nSome FTPS servers require this for security. Only applies to FTPS connections.\n","default":false},"entryParser":{"type":"string","enum":["UNIX","UNIX-TRIM","VMS","WINDOWS","OS/2","OS/400","AS/400","MVS","UNKNOWN-TYPE","NETWARE","MACOS-PETER"],"description":"File listing format parser. Controls how directory listings from the server are interpreted.\nMost servers use UNIX format. Only change this if directory listings appear garbled."},"tradingPartner":{"type":"boolean","readOnly":true,"description":"When true, this connection is designated a B2B/EDI trading partner. Read-only on the connection\nbody — POST/PUT of this field are silently ignored; it is toggled via\n`PUT /connections/{_id}/tradingPartner` (used by the EDI B2B Manager).\n"},"_tpConnectorId":{"type":"string","format":"objectId","description":"Reference to the trading partner connector this connection belongs to. Must reference an\nexisting, published trading partner connector; the API rejects an unknown or unpublished ID\nwith 422 `tpconnector_not_found`.\n"},"concurrencyLevel":{"type":"integer","description":"Maximum number of concurrent file transfer operations.\nFTP servers often have low connection limits — keep this value conservative.\nSend no more than the account's licensed maximum, the license's `concurrency` (25 standard,\n50 with an Environments license).\nWhile `autoRecoverRateLimitErrors` is true, which a create gets unless it sends `false`, a create\nignores the value sent here and sets this field to `targetConcurrencyLevel`, or to 1 when no\ntarget is sent. To start higher, send `targetConcurrencyLevel` too, or create with\n`autoRecoverRateLimitErrors: false`.\nUnlike the rest of the `ftp` object, a `PUT` that omits this field keeps the stored value instead of clearing it.\n","minimum":1,"maximum":50,"default":1},"targetConcurrencyLevel":{"type":"integer","description":"While `autoRecoverRateLimitErrors` is true, a create sets `concurrencyLevel` to this value, and a\ncreate that omits it gets 1 for both. Auto-recovery never runs on FTP connections (FTP errors\naren't treated as rate limits), so nothing climbs back to it.\nKept on a `PUT` that omits it, like `concurrencyLevel`; unset by the save while\n`autoRecoverRateLimitErrors` is false.\n","minimum":1,"maximum":50},"multiThreadCount":{"type":"integer","minimum":1,"description":"Number of parallel transfer threads for this FTP connection. Must be a\nwhole number — decimals are rejected on save. Send no more than the\naccount's licensed concurrency maximum, the same as for `concurrencyLevel`. On\nsave the value is mirrored into `userConcurrencyLevel` (and into\n`userTargetConcurrencyLevel` when auto-recovery of rate-limit errors\nis active with `targetConcurrencyLevel` set); clearing it clears the\nmirrors. Not preserved across a `PUT` — re-send it with the rest of\nthe `ftp` object or it is cleared."},"userConcurrencyLevel":{"type":"integer","readOnly":true,"description":"Server-maintained mirror of `multiThreadCount`. Present when\n`multiThreadCount` is set; do not send it — it is recomputed on save."},"userTargetConcurrencyLevel":{"type":"integer","readOnly":true,"description":"Server-maintained mirror of `multiThreadCount`, present while\n`autoRecoverRateLimitErrors` is true and `targetConcurrencyLevel` is\nset; recomputed on save."}},"if":{"properties":{"type":{"const":"sftp"}},"required":["type"]},"then":{"anyOf":[{"required":["password"]},{"required":["authKey"]}]},"else":{"required":["password"]}},"S3":{"type":"object","description":"Configuration for Amazon S3 connections. Used when the connection type is \"s3\". Provides\nupload, download, list, and delete access to S3 buckets. Authenticate with a static IAM\naccess key pair (authType `accesskey`) or by referencing an iClient that holds AWS IAM role\ncredentials (authType `awsIam`). Set pingBucket to an accessible bucket so Celigo can validate\nthe credentials.","properties":{"authType":{"type":"string","enum":["accesskey","awsIam"],"default":"accesskey","description":"Authentication method for the S3 bucket. Defaults to `accesskey` when omitted. Use `awsIam`\nto delegate authentication to an iClient that holds an AWS IAM role instead of embedding a\nstatic key pair."},"accessKeyId":{"type":"string","description":"AWS access key ID for IAM authentication. From an IAM user or role with S3 permissions\n(s3:GetObject, s3:PutObject, s3:ListBucket, etc.). Used when authType is `accesskey`."},"secretAccessKey":{"type":"string","writeOnly":true,"description":"AWS secret access key, paired with accessKeyId. Used when authType is `accesskey`. Encrypted at rest; returned masked as `\"******\"`."},"_iClientId":{"type":"string","format":"objectId","description":"Reference to an iClient that holds AWS IAM role credentials. Required when authType is\n`awsIam`; ignored otherwise. The iClient must carry `awsIam.roleArn`, and that role's name\nmust start with `celigo-file-adaptor-` — the S3 service assumes only roles in that family,\nso a role created for an HTTP or Redshift connection cannot be reused here."},"pingBucket":{"type":"string","description":"S3 bucket name used for connection health checks (ping). The system performs a HEAD request\non this bucket to verify credentials. Must be a bucket the credentials have access to."},"concurrencyLevel":{"type":"integer","description":"Maximum number of concurrent S3 operations. Applies when `autoRecoverRateLimitErrors` is\nfalse; when auto-recover is on (the default) concurrency auto-scales up to\n`targetConcurrencyLevel` and a `concurrencyLevel` sent on its own is reconciled back. Send no\nmore than the account's licensed maximum.","minimum":1,"maximum":50,"default":5},"targetConcurrencyLevel":{"type":"integer","description":"Upper bound for auto-scaled concurrency when `autoRecoverRateLimitErrors` is true (the\ndefault). The system raises and lowers concurrency between 1 and this value based on\nrate-limit feedback from S3.","minimum":1,"maximum":50,"default":5}},"if":{"properties":{"authType":{"const":"awsIam"}},"required":["authType"]},"then":{"required":["_iClientId"]},"else":{"required":["accessKeyId","secretAccessKey"]}},"HTTP":{"type":"object","description":"Configuration for HTTP/REST API connections. Used when the connection type is \"http\".\nThis is the most versatile connection type in Celigo, supporting any REST, SOAP, or generic HTTP API.\nThe auth.type field selects the authentication strategy; each type requires specific sub-fields.","required":["baseURI","mediaType"],"properties":{"mediaType":{"type":"string","enum":["xml","json","urlencoded","form-data","plaintext"],"description":"Default content type for outbound HTTP request bodies.\nControls the Content-Type header and how request bodies are serialized."},"successMediaType":{"type":"string","enum":["xml","csv","json","plaintext"],"description":"Expected content type of successful API responses. Controls how response bodies are parsed.\nIf omitted, the system infers the format from the response Content-Type header."},"errorMediaType":{"type":"string","enum":["xml","json","plaintext"],"description":"Expected content type of error responses from the API. Controls how error response bodies are parsed for extracting error messages.\n\nIf omitted, defaults to the same format as successMediaType.\n"},"baseURI":{"type":"string","description":"Base URL for all API requests made through this connection. Required.\n\nAll relative URIs in exports and imports are appended to this base URL.\nMust be an absolute URL (e.g., \"https://api.example.com/v2\").\nHandlebars expressions are supported for dynamic URLs — e.g.\n\"https://{{{connection.settings.subdomain}}}.example.com\".\n\nDo NOT include trailing slashes — relative URIs in exports/imports should start with \"/\".\n\nUse an HTTPS URL whenever possible. HTTP is not considered a secure protocol; data sent\nto an HTTP endpoint is not encrypted and may be intercepted or modified in transit.\n"},"disableStrictSSL":{"type":"boolean","description":"When true, disables strict SSL/TLS certificate validation for API requests.\n\nOnly set to true for development/testing with self-signed certificates.\nNEVER disable in production — it removes protection against man-in-the-middle attacks.\n","default":false},"concurrencyLevel":{"type":"integer","description":"Maximum number of concurrent HTTP requests this connection can make simultaneously.\n\nHigher values increase throughput but may trigger API rate limits.\nSet this based on the target API's rate limit documentation.\nSend no more than the account's licensed maximum (25 standard, 50 with an Environments license).\nA decimal is rejected with 422 `invalid_field_value` on every write (the same rule applies to every\nadaptor's `concurrencyLevel` and `targetConcurrencyLevel`, and to `ftp.multiThreadCount`).\nPlatform-managed while `autoRecoverRateLimitErrors` is true — a create or PUT ignores the value (a create without `targetConcurrencyLevel` stores 25 where the license's `concurrency` is at least 25) and the recovery machinery moves it toward the target; set `targetConcurrencyLevel` instead.\nAt level 1 an import's pages run one at a time, so a record's existence lookup and write finish before the next record's start (measured on a single import); the import's `idLockTemplate` serializes only same-key records and leaves the rest parallel.\n","minimum":1,"maximum":50,"default":25},"targetConcurrencyLevel":{"type":"integer","description":"Ceiling the platform climbs back to after rate-limit recovery, in steps of 25% of this value.\nWriting it also sets `concurrencyLevel` to the same value and stops any recovery in progress.\n\nHonored only while `autoRecoverRateLimitErrors` is true; ignored, and unset, when it is false.\n","minimum":1,"maximum":50},"retryHeader":{"type":"string","description":"HTTP response header name that contains the retry delay (in seconds) when rate-limited.\n\nDefaults to \"Retry-After\" which is the HTTP standard. Only change this if the API\nuses a non-standard header name for retry-after values.\n","default":"Retry-After"},"formType":{"type":"string","enum":["assistant","rest","http","graph_ql","assistant_graphql"],"description":"Controls the UI form layout for configuring this connection. Determines which\nfields are shown and how they are organized in the Celigo UI.\nFor programmatic creation, http is the most common choice."},"type":{"type":"string","enum":["Amazon-SP-API","Amazon-Hybrid","vendor_central"],"description":"Specific API type for Amazon integrations. Only set this for Amazon connections."},"clientCertificates":{"type":"object","description":"Client certificate configuration for mutual TLS (mTLS) authentication.\n\nUse when the API server requires a client certificate to establish the TLS connection.\nYou can provide either a PEM cert/key pair OR a PFX bundle, but not both.\n","properties":{"cert":{"type":"string","description":"Client certificate in PEM format. Must be paired with the key field.\nCannot be used together with pfx.\n"},"key":{"type":"string","description":"Private key for the client certificate in PEM format (encrypted at rest).","writeOnly":true},"ca":{"type":"string","description":"Certificate Authority (CA) certificate in PEM format.\nUse when the server's certificate is signed by a private CA not in the default trust store.\n"},"passphrase":{"type":"string","description":"Passphrase to decrypt an encrypted private key or PFX bundle (encrypted at rest).","writeOnly":true},"pfx":{"type":"string","description":"PKCS#12 (.pfx/.p12) bundle containing both the certificate and private key (encrypted at rest).\nCannot be used together with cert/key.\n","writeOnly":true}}},"ping":{"type":"object","description":"Connection health check (ping) configuration. Defines how Celigo tests\nwhether this connection is alive and authenticated.\n\nWhen configured, Celigo sends an HTTP request to the specified endpoint and\nevaluates the response to determine connection health. The ping runs when\ntesting the connection in the UI and periodically during flow execution.\n","properties":{"relativeURI":{"type":["string","null"],"description":"Relative URI appended to baseURI for the ping request.\nShould be a lightweight, fast endpoint (e.g., \"/me\", \"/health\", \"/api/v1/status\").\nMay be null when no ping endpoint is configured.\n"},"method":{"type":"string","enum":["GET","POST","PUT","HEAD"],"description":"HTTP method for the ping request. Defaults to GET.\nUse POST only if the health endpoint requires it.\n","default":"GET"},"body":{"type":"string","description":"Request body for the ping request. Only used when method is POST or PUT.\nFor form-data mediaType, must be valid multipart form data.\n"},"successPath":{"type":"string","description":"JSON path or XPath expression to extract a success indicator from the ping response.\nIf the value at this path matches one of the successValues, the ping succeeds.\nIf omitted, any 2xx response is considered successful.\n"},"successValues":{"type":"array","items":{"type":"string"},"description":"Values that indicate a successful ping when found at successPath.\nRequires successPath to be set.\n"},"allowArrayforSuccessPath":{"type":"boolean","description":"When true, the value at successPath can be an array and any matching element counts as success."},"failPath":{"type":"string","description":"JSON path or XPath expression to extract a failure indicator from the ping response.\nIf the value at this path matches one of the failValues, the ping fails even if the HTTP status is 2xx.\n"},"failValues":{"type":"array","items":{"type":"string"},"description":"Values that indicate a failed ping when found at failPath.\nRequires failPath to be set.\n"},"errorPath":{"type":"string","description":"JSON path or XPath expression to extract a human-readable error message from\na failed ping response. The extracted message is shown to the user in the UI.\n"}}},"auth":{"type":"object","description":"Authentication configuration for the API connection.\n\nThe auth.type field selects the authentication strategy. Each type requires\nspecific sub-fields — see the type field description for details.\n","properties":{"type":{"type":"string","enum":["custom","basic","token","oauth","wsse","cookie","digest","oauth1","jwtbearer","awsiam"],"description":"Authentication method for this connection. Determines which auth sub-fields are required.\nEach type has its own credential fields; see the x-enumDescriptions for details."},"failStatusCode":{"type":"number","description":"HTTP status code that indicates an authentication failure (e.g., 401, 403).\nWhen this status code is received, the system triggers re-authentication\nbefore retrying the request.\n"},"failPath":{"type":"string","description":"JSON path or XPath expression to check in response bodies for authentication failure indicators.\nUsed when APIs return 200 OK but embed auth errors in the response body.\n"},"failValues":{"type":"array","items":{"type":"string"},"description":"Values at failPath that indicate an authentication failure.\nRequires failPath to be set.\n"},"failures":{"type":"array","description":"HTTP status codes that indicate an authentication failure and should trigger\nre-authentication. An alternative to failStatusCode that accepts multiple codes.\n","items":{"type":"object","properties":{"statusCode":{"type":"number","description":"HTTP status code that signals an authentication failure (e.g., 403)."}}}},"skipFollowAuthorizationHeader":{"type":"boolean","description":"When true, the Authorization header is NOT forwarded on HTTP redirects.\nEnable this for APIs that redirect to a different domain after authentication.\n"},"basic":{"type":"object","description":"Basic authentication credentials. Required when auth.type is \"basic\", \"wsse\", or \"digest\".\n","properties":{"username":{"type":"string","description":"Username for Basic/Digest/WSSE authentication."},"password":{"type":"string","description":"Password for Basic/Digest/WSSE authentication (encrypted at rest). On update, the\nmasked `\"******\"` sent back keeps the stored password; a new value replaces it.","writeOnly":true}}},"token":{"type":"object","description":"Token-based authentication configuration. Required when auth.type is \"token\".\n\nSupports static API keys/bearer tokens and automatic token refresh flows.\nThe token can be sent in a header (Authorization), query parameter, or request body.\n","properties":{"token":{"type":"string","description":"The API key or bearer token value (encrypted at rest); returned masked as `\"******\"`.\nOn update, the mask sent back keeps the stored token; a new value replaces it.\nRequired unless automatic token refresh is configured: a token block without a\ntoken must carry a `refreshMethod`, so a create or update that sends neither fails\nwith 422 `Expected field: refreshMethod to be present.` To create a static-token\nconnection before the real token is available, send a placeholder value and\nreplace it later.\n"},"location":{"type":"string","enum":["url","header","body"],"description":"Where to include the token in outbound requests.\nMost APIs expect header. Use headerName and scheme to control the header format,\nor paramName when sending as a URL query parameter."},"headerName":{"type":"string","description":"HTTP header name for the token when location is \"header\".\nDefaults to \"Authorization\" if omitted.\n"},"scheme":{"type":"string","description":"Token scheme/prefix when sent in a header. Prepended before the token value.\nCommon values: \"Bearer\", \"Token\", \"Basic\".\nExample: scheme \"Bearer\" produces header \"Authorization: Bearer <token>\".\n"},"paramName":{"type":"string","description":"Query parameter name for the token when location is \"url\".\nExample: paramName \"api_key\" produces URL \"?api_key=<token>\".\n"},"refreshMethod":{"type":"string","enum":["GET","POST","PUT"],"description":"HTTP method for automatic token refresh requests.\nRequired when no static token is provided (refresh-based auth flow).\n","default":"POST"},"refreshRelativeURI":{"type":"string","description":"Relative URI (appended to baseURI) for the token refresh endpoint.\nThe system calls this endpoint to obtain a new token when the current one expires.\n"},"refreshBody":{"type":"string","description":"Request body to send with the token refresh request."},"refreshMediaType":{"type":"string","enum":["json","urlencoded","xml","plaintext"],"description":"Content type for the token refresh request body.\n","default":"urlencoded"},"refreshResponseMediaType":{"type":"string","enum":["json","xml"],"description":"Expected content type of the token refresh response."},"refreshTokenPath":{"type":"string","description":"JSON path to extract the new token from the refresh response.\nExample: \"access_token\" or \"data.token\".\n"},"refreshToken":{"type":"string","description":"Refresh token used to obtain a new access token (encrypted at rest).","writeOnly":true},"refreshTokenLocation":{"type":"string","enum":["header","body"],"description":"Where to include the refresh token in refresh requests."},"refreshHeaders":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Header name to send with the token-refresh request."},"value":{"type":"string","description":"Header value to send with the token-refresh request."}}},"description":"Additional headers to include in token refresh requests."},"tokenPaths":{"type":"array","items":{"type":"string"},"description":"JSON paths to extract multiple token values from the refresh response.\nUse when the refresh response contains tokens at different paths that need\nto be stored for subsequent requests.\n"},"revoke":{"type":"object","description":"Token-revocation request configuration. When set, the system calls this\nendpoint to revoke the current token (e.g. when the connection is deleted or\nre-authenticated). Used mainly by pre-built HTTP Connector templates.\n","properties":{"uri":{"type":"string","description":"Absolute URL of the token-revocation endpoint."},"body":{"type":"string","description":"Request body to send with the token-revocation request."},"headers":{"type":"array","description":"Additional headers to include in the token-revocation request.","items":{"type":"object","properties":{"name":{"type":"string","description":"Header name to send with the revocation request."},"value":{"type":"string","description":"Header value to send with the revocation request."}}}}}}}},"oauth":{"$ref":"#/components/schemas/OAuth"},"cookie":{"type":"object","description":"Cookie-based session authentication. Required when auth.type is \"cookie\".\n\nThe system authenticates by sending a request to the login URI, captures the\nsession cookies from the response, and includes them in all subsequent API requests.\n","properties":{"uri":{"type":"string","description":"Login endpoint URI for cookie authentication. Required.\nThe system sends a request to this URI to obtain session cookies.\n\nUse an HTTPS URL whenever possible. HTTP is not considered a secure protocol;\ndata sent to an HTTP endpoint is not encrypted and may be intercepted or\nmodified in transit.\n"},"body":{"type":"string","description":"Request body for the login request (e.g., JSON with username/password)."},"method":{"type":"string","description":"HTTP method for the login request (typically POST)."},"successStatusCode":{"type":"number","description":"HTTP status code that confirms successful authentication.\nIf the login response returns this status code, the session cookies are captured.\n"}}},"jwt":{"$ref":"#/components/schemas/JWT"}},"if":{"properties":{"type":{"enum":["basic","digest","wsse"]}},"required":["type"]},"then":{"required":["basic"],"properties":{"basic":{"required":["username","password"]}}},"else":{"if":{"properties":{"type":{"const":"token"}},"required":["type"]},"then":{"required":["token"],"properties":{"token":{"required":["token","location"]}}},"else":{"if":{"properties":{"type":{"const":"cookie"}},"required":["type"]},"then":{"required":["cookie"],"properties":{"cookie":{"required":["uri"]}}},"else":{"if":{"properties":{"type":{"const":"jwtbearer"}},"required":["type"]},"then":{"required":["jwt"],"properties":{"jwt":{"required":["signatureMethod","payload"]}},"if":{"properties":{"jwt":{"properties":{"signatureMethod":{"enum":["hmac-sha256","hmac-sha384","hmac-sha512"]}},"required":["signatureMethod"]}},"required":["jwt"]},"then":{"properties":{"jwt":{"required":["signatureMethod","payload","secret"]}}},"else":{"properties":{"jwt":{"required":["signatureMethod","payload","privateKey"]}}}},"else":{"if":{"properties":{"type":{"const":"oauth1"}},"required":["type"]},"then":{"required":["oauth"],"properties":{"oauth":{"required":["oauth1"],"properties":{"oauth1":{"required":["signatureMethod","consumerKey","accessToken"]}}}},"if":{"properties":{"oauth":{"properties":{"oauth1":{"properties":{"signatureMethod":{"enum":["rsa-sha1","rsa-sha256","rsa-sha512"]}},"required":["signatureMethod"]}},"required":["oauth1"]}},"required":["oauth"]},"then":{"properties":{"oauth":{"properties":{"oauth1":{"required":["signatureMethod","consumerKey","accessToken","consumerPrivateKey"]}}}}},"else":{"properties":{"oauth":{"properties":{"oauth1":{"required":["signatureMethod","consumerKey","accessToken","consumerSecret","tokenSecret"]}}}}}}}}}}},"rateLimit":{"type":"object","description":"Rate limiting configuration. Defines how the system detects and handles\nAPI rate limit responses.\n\nWhen rate limiting is detected, the system pauses requests and waits for\nthe retry-after period before resuming. The retryHeader field on the parent\nHTTP object controls which response header contains the wait time.\n","properties":{"failStatusCode":{"type":"number","description":"HTTP status code that indicates the API is rate-limiting requests.\nDefaults to 429 (Too Many Requests) which is the HTTP standard.\nChange only if the API uses a non-standard status code for rate limits.\n","default":429},"failPath":{"type":"string","description":"JSON path or XPath to check in response bodies for rate limit indicators.\nUsed when APIs return 200 OK but embed rate limit errors in the response body.\n"},"failValues":{"type":"array","items":{"type":"string"},"description":"Values at failPath that indicate rate limiting. Requires failPath to be set.\n"},"limit":{"type":"number","minimum":1,"description":"Maximum number of requests per rate-limit window. When set, the connection's\neffective concurrency level must be 1 to ensure proper rate limit enforcement.\n"}}},"headers":{"type":"array","description":"Default HTTP headers included in every request made through this connection.\nUse for API keys in custom headers, content negotiation, or any headers the API requires on all requests.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Header name (e.g., \"X-API-Key\", \"Accept\")."},"value":{"type":"string","description":"Header value. Supports handlebars expressions for dynamic values."}},"required":["name","value"]}},"encrypted":{"type":["object","string"],"description":"Encrypted custom fields for storing sensitive configuration values (API secrets,\nprivate keys, etc.) that don't fit standard auth fields. Sent as an object of\nfield-name/value pairs; values are encrypted at rest. Returned as the masked\nstring `\"******\"` on responses; on update, that mask sent back keeps the stored values,\nand a new object replaces them. Field definitions are specified in encryptedFields.\n"},"encryptedFields":{"type":"array","description":"Metadata defining the encrypted custom fields available on this connection.\nEach entry describes a field in the encrypted object — its ID, label, and UI position.\n","items":{"type":"object","properties":{"id":{"type":"string","description":"Field identifier — matches the key in the encrypted object."},"label":{"type":"string","description":"Human-readable label shown in the UI."},"required":{"type":"boolean","default":false,"description":"When true, the custom field defined by this entry is mandatory — a value for it must be supplied before the connection can be saved."},"position":{"type":"number","description":"Display order in the UI form."},"helpText":{"type":"string","description":"Tooltip or help text shown next to the field in the UI."}}}},"unencrypted":{"type":"object","description":"Unencrypted custom fields for non-sensitive configuration values.\nField definitions are specified in unencryptedFields.","properties":{"marketplaceId":{"type":"string","description":"Amazon marketplace identifier for Amazon connection types."},"sellingRegion":{"type":"string","description":"Amazon selling region for the marketplace."},"googleProjectId":{"type":"string","description":"Google Cloud project id for Google Cloud Storage connections\n(assistant `googlecloudstorage`, or a `baseURI` pointing at\n`storage.googleapis.com`)."}}},"unencryptedFields":{"type":"array","description":"Metadata defining the unencrypted custom fields available on this connection.\nEach entry describes a field in the unencrypted object.\n","items":{"type":"object","properties":{"id":{"type":"string","description":"Field identifier — matches the key in the unencrypted object."},"label":{"type":"string","description":"Human-readable label shown in the UI."},"required":{"type":"boolean","default":false,"description":"When true, the custom field defined by this entry is mandatory — a value for it must be supplied before the connection can be saved."},"position":{"type":"number","description":"Display order in the UI form."},"helpText":{"type":"string","description":"Tooltip or help text shown next to the field in the UI."},"type":{"type":"string","description":"Field type hint for the UI (e.g., \"text\", \"select\")."}}}},"_iClientId":{"type":"string","format":"objectId","description":"iClient that holds the credentials this connection signs with: the OAuth 2.0 client for\n`auth.type` `oauth` (including the `azureserviceprincipal` service principal an Azure\nData Lake Storage Gen2 connection authenticates with and the `googleserviceaccount` key\na Google Cloud Storage connection uses), or the `awsIam` role for `auth.type` `awsiam`.\nMust belong to the same account as the connection. Omit for every other auth type."},"_httpConnectorId":{"type":"string","format":"objectId","description":"HTTP connector (pre-built application template) this connection is linked to. Linking\nlets exports and imports on the connection pick endpoints from the connector's catalog and\ndrives the `assistant` form in the UI. The link is resolved server-side on create and on\nany update that changes `assistant` or a connector id, and the result overwrites\n`_httpConnectorId`, `_httpConnectorApiId` and `_httpConnectorVersionId`: an id that does\nnot resolve to an accessible connector is dropped rather than rejected, and when the\ntop-level `assistant` names an application with a legacy adaptor (the connector's\n`legacyId`, e.g. `shopify`) the connector resolved from that slug replaces the id you sent\nand a sent `_httpConnectorVersionId` is dropped. Read the response back to confirm the\nlink took."},"_httpConnectorApiId":{"type":"string","format":"objectId","description":"API within the linked HTTP connector that this connection targets, for connectors that\npublish more than one API. Only meaningful together with `_httpConnectorId`; an id that is\nnot one of that connector's APIs is dropped on save, not rejected."},"_httpConnectorVersionId":{"type":"string","format":"objectId","description":"Version entry of the linked HTTP connector (an item of the connector's `versions` array)\nwhose endpoint definitions this connection uses. Only meaningful together with\n`_httpConnectorId`; an id that is not one of that connector's published versions is\nreplaced by a published version of the connector on create and dropped on update, never\nrejected."},"isRest":{"type":"boolean","description":"When true, this HTTP connection uses REST-style semantics (created from a REST connector template)."},"useNewAuthFailSchema":{"type":"boolean","description":"When true, the connection uses the newer authentication-failure detection schema for refresh/retry handling."}},"if":{"properties":{"type":{"enum":["Amazon-SP-API","Amazon-Hybrid","vendor_central"]}},"required":["type"]},"then":{"required":["unencrypted"],"properties":{"unencrypted":{"required":["marketplaceId","sellingRegion"]}}},"else":{"if":{"properties":{"auth":{"properties":{"type":{"const":"awsiam"}},"required":["type"]}},"required":["auth"]},"then":{"required":["_iClientId"]}}},"OAuth":{"type":"object","description":"OAuth 2.0 and OAuth 1.0a authentication configuration.\nUsed as a sub-object within HTTP and REST connection auth configurations.\nThe grantType field selects the OAuth 2.0 flow; for legacy OAuth 1.0a APIs, use the oauth1 sub-object.","properties":{"type":{"type":"string","enum":["custom","assistant"],"description":"OAuth configuration mode. Controls whether settings are user-configured or\npre-populated by an application assistant connector."},"grantType":{"type":"string","enum":["authorizecode","clientcredentials","password"],"description":"OAuth 2.0 grant type that determines the authentication flow.\nUse authorizecode for user-context integrations, clientcredentials for\nserver-to-server, and password only when the API does not support other flows.","default":"authorizecode"},"authURI":{"type":"string","description":"OAuth 2.0 authorization endpoint URL.\nRequired for \"authorizecode\" grant type. The user is redirected to this URL\nto authorize the application.\nHandlebars expressions are supported for tenant-specific endpoints — e.g.\n\"https://{{{connection.settings.storeName}}}.myshopify.com/admin/oauth/authorize\".\n\nUse an HTTPS URL whenever possible. HTTP is not considered a secure protocol; data sent\nto an HTTP endpoint is not encrypted and may be intercepted or modified in transit.\n"},"tokenURI":{"type":"string","description":"OAuth 2.0 token endpoint URL.\nRequired for \"authorizecode\", \"clientcredentials\", and \"password\" grant types.\nThe system exchanges credentials or authorization codes for access tokens at this URL.\nHandlebars expressions are supported for tenant-specific endpoints.\n\nUse an HTTPS URL whenever possible. HTTP is not considered a secure protocol; data sent\nto an HTTP endpoint is not encrypted and may be intercepted or modified in transit.\n"},"skipOauthValidations":{"type":"boolean","description":"When true, skips Celigo's built-in OAuth configuration validation.\nUse when the API has non-standard OAuth requirements that conflict with validation rules.\n","default":false},"scope":{"type":"array","items":{"type":"string"},"description":"OAuth scopes to request during authorization. Controls the level of API access.\nScope values are API-specific (e.g., \"read\", \"write\", \"admin\").\n"},"scopeDelimiter":{"type":"string","description":"Delimiter between multiple scope values. Defaults to a space (\" \") per the\nOAuth 2.0 spec. Some APIs use commas or other delimiters.\n","default":" "},"clientId":{"type":"string","description":"OAuth client ID (application ID) registered with the API provider. An `http`\nconnection takes the app from a `custom_oauth2` iClient referenced by `http._iClientId`\n(an `mcp` connection from `mcp.http._iClientId`) and doesn't store this field: a create\nthat sends `clientId` and `clientSecret` here answers 201 without either (and stores\n`useIClientFields: true` unless `false` is sent).\n"},"clientSecret":{"type":"string","description":"OAuth client secret (encrypted at rest). Not stored on an `http` connection: the secret\nbelongs on the iClient (see `clientId`).\n","writeOnly":true},"username":{"type":"string","description":"Resource owner username. Required when grantType is \"password\".\n"},"password":{"type":"string","description":"Resource owner password (encrypted at rest). Required when grantType is \"password\".\n","writeOnly":true},"clientCredentialsLocation":{"type":"string","enum":["basicauthheader","body"],"description":"Where to send client credentials in token requests. Defaults to basicauthheader\n(HTTP Basic Auth), which is recommended by the OAuth spec. Use body when\nthe API does not support Basic Auth for client credentials.","default":"basicauthheader"},"accessTokenPath":{"type":"string","description":"JSON path to extract the access token from the token endpoint response.\nDefaults to \"access_token\" per the OAuth 2.0 spec.\nChange only if the API returns the token at a non-standard path.\n"},"accessTokenHeaders":{"type":"array","description":"Additional HTTP headers to include in token endpoint requests.\nUse for APIs that require custom headers beyond the standard OAuth parameters.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Header name to send with the token request."},"value":{"type":"string","description":"Header value to send with the token request."}}}},"accessTokenBody":{"type":"string","description":"Additional body content to include in token endpoint requests.\nAppended to the standard OAuth parameters.\n"},"oauth2RedirectUrl":{"type":"string","description":"OAuth 2.0 redirect URI (callback URL) registered with the API provider.\nMust exactly match the redirect URI configured in the OAuth application registration.\n"},"useIClientFields":{"type":"boolean","description":"When true, uses iClient-managed OAuth credentials (clientId/clientSecret)\ninstead of the values in this configuration. The iClient is the connection's\n`_iClientId` — on an HTTP connection `http._iClientId`, beside `auth`, not inside it —\nand it also supplies the grant type and the authorization and token endpoints, so a\ncreate needs only `auth.type: \"oauth\"`, `auth.oauth.useIClientFields: true` and the\niClient id. The connection holds no tokens until a person completes the authorization\nin a browser.\n"},"oauth1":{"type":"object","description":"OAuth 1.0a configuration for legacy APIs that use the older OAuth protocol.\nAlways needs consumerKey and accessToken; HMAC signature methods also need\nconsumerSecret and tokenSecret, while RSA methods need consumerPrivateKey.","properties":{"consumerKey":{"type":"string","description":"OAuth 1.0a consumer key (API key).\nIdentifies the application making the request.\n"},"consumerSecret":{"type":"string","description":"OAuth 1.0a consumer secret (encrypted at rest).\nRequired for HMAC and PLAINTEXT signature methods.\n","writeOnly":true},"accessToken":{"type":"string","description":"OAuth 1.0a access token (encrypted at rest).\nRepresents the user's authorization for the application to access their data.\n","writeOnly":true},"tokenSecret":{"type":"string","description":"OAuth 1.0a token secret (encrypted at rest).\nRequired for HMAC and PLAINTEXT signature methods.\n","writeOnly":true},"signatureMethod":{"type":"string","enum":["hmac-sha1","hmac-sha256","hmac-sha512","rsa-sha1","rsa-sha256","rsa-sha512","plaintext"],"description":"OAuth 1.0a request signing method. HMAC methods require consumerSecret and\ntokenSecret; RSA methods require consumerPrivateKey. PLAINTEXT offers no\ncryptographic signing and should only be used for testing over HTTPS."},"consumerPrivateKey":{"type":"string","description":"RSA private key for OAuth 1.0a RSA signature methods (encrypted at rest).\nRequired when signatureMethod is RSA-SHA1, RSA-SHA256, or RSA-SHA512.\n","writeOnly":true},"realm":{"type":"string","description":"OAuth realm value included in the Authorization header.\nSome APIs require this to identify the authentication domain.\n"}}},"pkceCodeVerifier":{"type":"string","description":"PKCE (Proof Key for Code Exchange) code verifier for enhanced OAuth 2.0 security.\nManaged internally by the system during authorization code flows.\n","writeOnly":true}},"if":{"properties":{"grantType":{"const":"authorizecode"}},"required":["grantType"]},"then":{"required":["authURI","tokenURI"]},"else":{"if":{"properties":{"grantType":{"const":"clientcredentials"}},"required":["grantType"]},"then":{"required":["tokenURI"]},"else":{"if":{"properties":{"grantType":{"const":"password"}},"required":["grantType"]},"then":{"required":["tokenURI","username","password"]}}}},"JWT":{"type":"object","description":"JWT Bearer authentication configuration. Used when auth.type is \"jwtbearer\" on HTTP\nconnections. The connection builds a JWT from payload and headers, signs it using\nsignatureMethod, and sends it as the bearer token. HMAC methods sign with secret;\nRSA/ECDSA/PSS methods sign with privateKey.","properties":{"signatureMethod":{"type":"string","enum":["hmac-sha256","hmac-sha384","hmac-sha512","rsa-sha256","rsa-sha384","rsa-sha512","es256","es384","es512","ps256","ps384","ps512"],"description":"JWT signing method. Required when auth.type is \"jwtbearer\". HMAC methods require\nthe secret field; RSA/ECDSA/PSS methods require the privateKey field."},"payload":{"type":"object","additionalProperties":true,"description":"JWT claims payload (the token body). Required and must be non-empty when auth.type\nis \"jwtbearer\". Keys are JWT claim names (e.g. iss, sub, aud, exp)."},"headers":{"type":"object","additionalProperties":true,"description":"JWT header parameters merged into the generated token header (e.g. kid)."},"secret":{"type":"string","writeOnly":true,"description":"Shared secret for HMAC signature methods (hmac-sha256/hmac-sha512), encrypted at rest.\nRequired when signatureMethod is an HMAC method."},"isSecretBase64Encoded":{"type":"boolean","description":"When true, the secret is base64-decoded before being used to sign."},"privateKey":{"type":"string","writeOnly":true,"description":"PEM-encoded private key for RSA/ECDSA/PSS signature methods, encrypted at rest.\nRequired for any rsa-*, es*, or ps* signature method."},"token":{"type":"string","writeOnly":true,"description":"Pre-generated static JWT to use instead of building one from payload (encrypted at rest).\nUse only when the API issues a long-lived JWT that does not need regeneration."}},"if":{"properties":{"signatureMethod":{"enum":["hmac-sha256","hmac-sha384","hmac-sha512"]}},"required":["signatureMethod"]},"then":{"required":["secret"]},"else":{"if":{"properties":{"signatureMethod":{"enum":["rsa-sha256","rsa-sha384","rsa-sha512","es256","es384","es512","ps256","ps384","ps512"]}},"required":["signatureMethod"]},"then":{"required":["privateKey"]}}},"RDBMS":{"type":"object","description":"Configuration for relational database connections. Used when the connection type is \"rdbms\".\nThe type field selects the database system, which determines the SQL dialect, connection driver,\ndefault port, and which additional sub-fields are required.","required":["type"],"properties":{"type":{"type":"string","enum":["mysql","postgresql","mssql","snowflake","oracle","bigquery","redshift","mariadb","azuresynapse"],"description":"The specific relational database system to connect to. Determines the SQL dialect,\nconnection driver, default port, and which additional sub-fields are required."},"host":{"type":"string","description":"Database server hostname or IP address.\nRequired for all types except BigQuery (which uses Google's API endpoints) and Redshift\n(which is reached through its cluster identifier and region instead of a host).\n\nFor Snowflake, use the account URL format: \"account_identifier.snowflakecomputing.com\".\n"},"port":{"type":"number","description":"Database server port number. If omitted, uses the default port for the database type:\n- MySQL/MariaDB: 3306\n- PostgreSQL: 5432\n- MS SQL/Azure Synapse: 1433\n- Oracle: 1521\n- Snowflake: 443\n- Redshift: 5439\n","minimum":1,"maximum":65535},"database":{"type":"string","description":"Database name to connect to. Required for most types.\n\nFor Oracle, use the serviceName field instead of database.\nFor BigQuery, use bigquery.dataset to specify the target dataset.\n"},"instanceName":{"type":"string","description":"Named instance identifier, used when the server hosts multiple named database instances\n(primarily MS SQL Server). Leave empty for a default instance.\n"},"user":{"type":"string","description":"Database username for authentication. Required for all types except\nBigQuery (service account auth), Redshift, and `mssql` / `azuresynapse`\nwith `mssql.authType=azure-service-principal`, where the Entra principal\nin the iClient signs in and a `user` sent anyway is stored unused.\n"},"password":{"type":"string","description":"Database password (encrypted at rest). Required alongside user for password-based auth.","writeOnly":true},"version":{"type":"string","enum":["SQL Server 2008 R2","SQL Server 2012","SQL Server 2014","SQL Server 2016","SQL Server 2017","Azure"],"description":"SQL Server engine version, which selects driver compatibility behavior. Applies to mssql\nand azuresynapse connections only (azuresynapse is always \"Azure\")."},"serviceName":{"type":"string","description":"Oracle service name, supplied instead of the database field for Oracle connections.\nThis is the TNS service name or pluggable database (PDB) service name.\n"},"serverType":{"type":"string","enum":["dedicated","shared","pooled"],"description":"Oracle server connection type. Controls the server process model used for connections."},"concurrencyLevel":{"type":"integer","description":"Maximum number of concurrent database connections.\nSet based on the database server's connection limit and available resources.\nSend no more than the account's licensed maximum (25 standard, 50 with an Environments license).\n","minimum":1,"maximum":50,"default":5},"targetConcurrencyLevel":{"type":"integer","description":"Target concurrency level for auto-scaling. The system adjusts the number of\nconcurrent connections between 1 and this value based on performance feedback.\n","minimum":1,"maximum":50},"disableStrictSSL":{"type":"boolean","description":"When true, disables strict SSL/TLS certificate validation for the database connection.\nOnly use for development/testing with self-signed certificates.\n","default":false},"snowflake":{"type":"object","description":"Snowflake-specific configuration. Required when type is \"snowflake\".\n","required":["authType"],"properties":{"warehouse":{"type":"string","description":"Snowflake virtual warehouse that provides compute resources for queries.\nMust be a warehouse the user's role can access."},"schema":{"type":"string","description":"Default Snowflake schema. If omitted, queries must fully-qualify table names\n(e.g., DATABASE.SCHEMA.TABLE).\n"},"role":{"type":"string","description":"Snowflake security role to use for the session. Determines which databases,\nschemas, and warehouses are accessible. Defaults to the user's default role.\n"},"authType":{"type":"string","enum":["keyPair"],"description":"Authentication type for Snowflake. keyPair uses RSA key-pair authentication and requires\nthe connection's key field. It is the only type the connection form offers for new\nconnections (password-based auth is no longer available)."}}},"mssql":{"type":"object","description":"Microsoft SQL Server / Azure Synapse-specific configuration. Read for\nboth the `mssql` and the `azuresynapse` types — Azure Synapse\nAnalytics shares SQL Server's authentication model and this\nsub-object.","properties":{"authType":{"type":"string","enum":["basic","azure-service-principal"],"description":"How a Microsoft SQL Server or Azure Synapse connection\nauthenticates. Defaults to `basic` when omitted (the connection\nform's \"Database user\"). With `azure-service-principal` the\nconnection signs in as the Entra application registered in the\niClient — a client secret, or on SQL Server a certificate — and\n`_iClientId` must reference it. The SQL Server connection form\noffers `azure-service-principal` in Cloud mode only and saves\n`basic` for an on-premise connection (`_agentId` set).","default":"basic"}}},"bigquery":{"type":"object","description":"Google BigQuery-specific configuration. Required when type is \"bigquery\".\nUses Google Cloud service account credentials for authentication.\n","properties":{"projectId":{"type":"string","description":"Google Cloud project ID that contains the BigQuery datasets.\nFound in the Google Cloud Console project settings.\n"},"dataset":{"type":"string","description":"Default BigQuery dataset name.\nQueries will target tables within this dataset unless fully-qualified names are used.\n"},"clientEmail":{"type":"string","format":"email","description":"Google Cloud service account email address.\nThe service account must have BigQuery Data Editor and BigQuery Job User roles.\n"},"privateKey":{"type":"string","description":"Google Cloud service account private key in PEM format (encrypted at rest).\nDownloaded as part of the service account JSON key file.\n","writeOnly":true}}},"redshift":{"type":"object","description":"Amazon Redshift-specific configuration. `region` is always required.\nCredentials depend on `authType`: static access keys in `aws` for\n`accesskey` (the default), or an `awsIam` iClient referenced by\n`rdbms._iClientId` for `awsIam`. The top-level user and password are optional.\n","properties":{"authType":{"type":"string","enum":["accesskey","awsIam"],"default":"accesskey","description":"Selects the Redshift authentication mode; omitting it behaves as\n`accesskey`. With `awsIam`, the platform assumes the iClient's\nrole and requires exactly one of `clusterIdentifier` (provisioned\ncluster) or `workgroupName` (Redshift Serverless) — supplying\nboth or neither fails with 422. A bulk-load import saves against\neither mode, cluster or Serverless workgroup alike."},"aws":{"type":"object","description":"Static AWS access keys, used when `authType` is `accesskey` (or\nomitted). Not used with `awsIam` — credentials then come from the\niClient.","properties":{"accessKeyId":{"type":"string","description":"AWS access key ID for IAM authentication."},"secretAccessKey":{"type":"string","description":"AWS secret access key (encrypted at rest).","writeOnly":true}}},"clusterIdentifier":{"type":"string","description":"Redshift cluster identifier, used with the region to reach a\nprovisioned cluster. With `awsIam` auth, exactly one of\n`clusterIdentifier` or `workgroupName` must be set."},"workgroupName":{"type":"string","description":"Redshift Serverless workgroup to connect to instead of a\nprovisioned cluster. Only valid when `authType` is `awsIam` —\nthe API rejects it with `accesskey` auth (422 \"Redshift\nServerless (workgroupName) requires authType=awsIam\") — and is\nmutually exclusive with `clusterIdentifier`."},"region":{"type":"string","description":"AWS region where the Redshift cluster is deployed.\n","enum":["us-east-1","us-east-2","us-west-1","us-west-2","eu-west-1","eu-west-2","eu-west-3","eu-north-1","eu-central-1","ap-southeast-1","ap-southeast-2","ap-northeast-1","ap-northeast-2","ap-south-1","sa-east-1","ca-central-1"]}}},"ssl":{"$ref":"#/components/schemas/SSL"},"options":{"type":"array","description":"Additional database driver connection options as name/value pairs.\nUse for driver-specific settings not covered by the standard fields\n(e.g., connection timeout, charset, application name).\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Option name (driver-specific)."},"value":{"type":"string","description":"Option value."}},"required":["name","value"]}},"key":{"type":"string","writeOnly":true,"description":"RSA private key (PEM-encoded, encrypted at rest). Required when type is `snowflake`\nand `snowflake.authType` is `keyPair`. Not used by any other database type."},"passphrase":{"type":"string","writeOnly":true,"description":"Passphrase that decrypts the RSA private key in the key field, when that key is\nencrypted (encrypted at rest). Used only for `snowflake` with `snowflake.authType`\n`keyPair`; omit when the private key is unencrypted."},"_iClientId":{"type":"string","format":"objectId","description":"Reference to an iClient holding auth credentials, stored inside the\nrdbms object. Required when type is `mssql` or `azuresynapse` and\n`mssql.authType` is `azure-service-principal` (an\n`azureserviceprincipal` iClient — the Microsoft Entra tenant id,\nclient id and client secret or certificate), and when type is\n`redshift` and `redshift.authType` is `awsIam` (an `awsIam`-provider\niClient holding the role ARN to assume). On `mssql` / `azuresynapse`\nit is rejected when `mssql.authType` is `basic` or omitted. For\nRedshift the iClient must carry `awsIam.roleArn`, and that role's name\nmust start with `celigo-data-warehouse-adaptor-` — the data-warehouse\nservice assumes only roles in that family, so a role created for an S3\nor HTTP connection cannot be reused here."}},"if":{"properties":{"type":{"enum":["mysql","postgresql","mariadb"]}},"required":["type"]},"then":{"required":["host","database","user","password"]},"else":{"if":{"properties":{"type":{"const":"mssql"}},"required":["type"]},"then":{"if":{"properties":{"mssql":{"properties":{"authType":{"const":"azure-service-principal"}},"required":["authType"]}},"required":["mssql"]},"then":{"required":["host","database","version","_iClientId"]},"else":{"required":["host","database","version","user","password"]}},"else":{"if":{"properties":{"type":{"const":"azuresynapse"}},"required":["type"]},"then":{"if":{"properties":{"mssql":{"properties":{"authType":{"const":"azure-service-principal"}},"required":["authType"]}},"required":["mssql"]},"then":{"required":["host","database","_iClientId"]},"else":{"required":["host","database","user","password"]}},"else":{"if":{"properties":{"type":{"const":"snowflake"}},"required":["type"]},"then":{"required":["host","database","user"],"properties":{"snowflake":{"required":["authType","warehouse"]}},"if":{"properties":{"snowflake":{"properties":{"authType":{"const":"keyPair"}},"required":["authType"]}},"required":["snowflake"]},"then":{"required":["key"]},"else":{"required":["password"]}},"else":{"if":{"properties":{"type":{"const":"oracle"}},"required":["type"]},"then":{"required":["host","user","password"]},"else":{"if":{"properties":{"type":{"const":"bigquery"}},"required":["type"]},"then":{"required":["bigquery"],"properties":{"bigquery":{"required":["projectId","dataset","clientEmail","privateKey"]}}},"else":{"if":{"properties":{"type":{"const":"redshift"}},"required":["type"]},"then":{"required":["database","user","redshift"],"properties":{"redshift":{"required":["region"]}},"if":{"required":["redshift"],"properties":{"redshift":{"required":["authType"],"properties":{"authType":{"const":"awsIam"}}}}},"then":{"required":["_iClientId"],"properties":{"redshift":{"oneOf":[{"required":["clusterIdentifier"],"not":{"required":["workgroupName"]}},{"required":["workgroupName"],"not":{"required":["clusterIdentifier"]}}]}}},"else":{"properties":{"redshift":{"required":["aws","clusterIdentifier"],"not":{"required":["workgroupName"]},"properties":{"aws":{"required":["accessKeyId","secretAccessKey"]}}}}}}}}}}}}},"MongoDB":{"type":"object","description":"Configuration for MongoDB connections. Used when the connection type is \"mongodb\". Supports\nstandalone instances, replica sets, and MongoDB Atlas clusters. For a replica set, list all\nmember addresses in host and set replicaSet to the set name; authSource selects the\nauthentication database when it differs from the target database.","required":["host","username","password"],"properties":{"host":{"type":"array","minItems":1,"items":{"type":"string"},"description":"MongoDB server addresses. An array of one or more host:port strings.\n\n- Standalone: [\"mongodb.example.com:27017\"]\n- Replica set: [\"rs1.example.com:27017\", \"rs2.example.com:27017\", \"rs3.example.com:27017\"]\n- MongoDB Atlas: [\"cluster0-shard-00-00.abc.mongodb.net:27017\", ...]\n\nInclude the port number with each host. Default MongoDB port is 27017.\n"},"database":{"type":"string","description":"Target MongoDB database name.\nAll operations (reads/writes) target collections within this database.\nOptional on the connection — when omitted, set the database per operation instead.\n"},"username":{"type":"string","description":"MongoDB username for authentication."},"password":{"type":"string","writeOnly":true,"description":"MongoDB password. Write-only — accepted on create/update and returned masked as `\"******\"`."},"replicaSet":{"type":"string","description":"MongoDB replica set name. Set this when connecting to a replica set so the driver can\ndiscover all members and handle failover. For MongoDB Atlas, this is typically\n\"atlas-xxxxxx-shard-0\".\n"},"ssl":{"type":"boolean","description":"When true, connects to MongoDB over TLS/SSL.\nRequired for MongoDB Atlas and recommended for all production deployments.\n","default":false},"authSource":{"type":"string","description":"MongoDB authentication database — the database where the user credentials are stored.\nDefaults to the value of the database field. Set to \"admin\" if the user was created\nin the admin database (common for shared MongoDB deployments and Atlas).\n"},"concurrencyLevel":{"type":"integer","description":"Maximum number of concurrent MongoDB operations.\nSend no more than the account's licensed maximum (25 standard, 50 with an Environments license).\n","minimum":1,"maximum":50,"default":5},"targetConcurrencyLevel":{"type":"integer","description":"Target concurrency level for auto-scaling. The system adjusts concurrency\nbetween 1 and this value based on rate limit feedback.\n\nOnly relevant when autoRecoverRateLimitErrors is enabled on the connection.\n","minimum":1,"maximum":50,"default":5}}},"AS2":{"type":"object","description":"AS2 (Applicability Statement 2) connection configuration for EDI","required":["as2Id","partnerId","partnerStationInfo","userStationInfo"],"properties":{"as2Id":{"type":"string","description":"AS2 identifier for this station. Trading partners use this as the \"To\"\nidentifier when sending documents, and integrator.io uses it as the \"From\"\nidentifier when sending documents to partners.\n\nMust be unique across ALL integrator.io users so inbound documents route\ncorrectly. Set on creation and cannot be changed afterward — a PUT with a\ndifferent value is ignored. Use a distinct identifier per environment\n(e.g. production vs. non-production). If omitted, a unique value is auto-generated.\n"},"partnerId":{"type":"string","description":"Trading partner's AS2 identifier — the partner's \"From\" identifier on documents they\nsend and the \"To\" identifier integrator.io uses when sending to them. Set on creation\nand cannot be changed afterward; a PUT with a different value is ignored."},"_tpConnectorId":{"type":"string","format":"objectId","description":"Trading partner connector this AS2 connection was provisioned from during partner onboarding. Omit for a standalone AS2 connection."},"contentBasedFlowRouter":{"type":"object","description":"Routes inbound documents to different flows based on message content, for connections\nshared across multiple flows. Omit unless a routing script is required.","properties":{"function":{"type":"string","description":"Function in the routing script that returns the target flow."},"_scriptId":{"type":"string","format":"objectId","description":"Script containing the routing function named in `function`."}}},"partnerStationInfo":{"type":"object","description":"Partner (remote) station configuration, used on the IMPORT side — controls how messages\nare sent TO the trading partner.","required":["as2URI","signing","encryptionType"],"properties":{"as2URI":{"type":"string","format":"uri","description":"Partner's AS2 endpoint that integrator.io posts outbound messages to.\n\nUse an HTTPS URL whenever possible. HTTP is not considered a secure protocol; data\nsent to an HTTP endpoint is not encrypted and may be intercepted or modified in transit."},"mdn":{"type":"object","description":"Settings for the MDN (Message Disposition Notification) receipt the partner returns for outbound messages.","required":["mdnSigning"],"properties":{"mdnURL":{"type":"string","format":"uri","description":"Endpoint the partner posts asynchronous MDNs to. Set only when the partner\nreturns MDNs asynchronously rather than on the same connection.\n\nUse an HTTPS URL whenever possible. HTTP is not considered a secure protocol;\ndata sent to an HTTP endpoint is not encrypted and may be intercepted or\nmodified in transit."},"signatureProtocol":{"type":"string","enum":["pkcs7-signature"],"description":"Signature protocol the partner uses on returned MDNs."},"mdnSigning":{"type":"string","enum":["NONE","SHA1","MD5","SHA256"],"description":"Hash algorithm used to verify the signature on MDNs the partner returns. Defaults to NONE when the partner does not sign MDNs."},"verifyMDNSignature":{"type":"boolean","description":"When true, verifies the signature on MDNs returned by the partner. Requires unencrypted.partnerCertificate."}}},"auth":{"type":"object","description":"Authentication for posting messages to the partner's AS2 endpoint. Omit (or set\ntype to none) when the endpoint is unauthenticated.","properties":{"type":{"type":"string","enum":["basic","token","none"],"description":"Authentication scheme for the partner endpoint. Set the matching basic or token sub-object for basic or token."},"failStatusCode":{"type":"number","description":"HTTP status code in the partner's response that signals an authentication failure, triggering a token refresh or error."},"failPath":{"type":"string","description":"Path in the partner's JSON response body checked against failValues to detect an authentication failure."},"failValues":{"type":"array","items":{"type":"string"},"description":"Values at failPath that indicate an authentication failure. Requires failPath."},"basic":{"type":"object","description":"Credentials for basic authentication. Present when type is basic.","properties":{"username":{"type":"string","description":"Username for basic authentication."},"password":{"type":"string","writeOnly":true,"description":"Password for basic authentication. Masked as \"******\" in GET responses."}}},"token":{"type":"object","description":"Token configuration. Present when type is token.","properties":{"token":{"type":"string","writeOnly":true,"description":"Bearer/access token sent with each request to the partner. Masked as \"******\" in GET responses."},"location":{"type":"string","enum":["header","url","body"],"description":"Where to place the token in the outbound request."},"headerName":{"type":"string","description":"Header name carrying the token when location is header. Defaults to Authorization."},"scheme":{"type":"string","enum":["bearer","mac","oauth","none"],"description":"Authorization scheme prepended to the token when location is header."},"paramName":{"type":"string","description":"Query parameter name carrying the token when location is url."},"refreshToken":{"type":"string","writeOnly":true,"description":"Token used to obtain a new access token when the current one expires. Masked as \"******\" in GET responses."},"refreshRelativeURI":{"type":"string","description":"Endpoint, relative to the partner's host, called to refresh the token."},"refreshMethod":{"type":"string","enum":["GET","POST","PUT"],"description":"HTTP method for the token-refresh request."},"refreshMediaType":{"type":"string","enum":["json","urlencoded","xml"],"description":"Content type of the token-refresh request body."},"refreshBody":{"type":"string","description":"Request body sent with the token-refresh request, used when refreshMethod is POST or PUT."},"refreshTokenPath":{"type":"string","description":"Path in the refresh response body where the new access token is found."},"refreshHeaders":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Header name to send with the token-refresh request."},"value":{"type":"string","description":"Header value to send with the token-refresh request."}}},"description":"Headers sent with the token-refresh request."}}}}},"rateLimit":{"type":"object","description":"Throttles requests to the partner endpoint when it enforces a non-standard rate limit.","properties":{"failStatusCode":{"type":"number","description":"HTTP status code in the partner's response that indicates rate limiting."},"failPath":{"type":"string","description":"Path in the partner's JSON response body checked against failValues to detect rate limiting."},"failValues":{"type":"array","description":"Values at failPath that indicate rate limiting. Requires failPath.","items":{"type":"string"}},"limit":{"type":"number","minimum":1,"description":"Wait time in milliseconds between requests to the partner endpoint."}}},"SMIMEVersion":{"type":"string","enum":["v2","v3"],"description":"S/MIME version applied to outbound messages. Not exposed in the connection form."},"signing":{"type":"string","enum":["NONE","SHA1","MD5","SHA256"],"description":"Hash algorithm for signing messages sent to the partner. Any value other than NONE requires encrypted.userPrivateKey and unencrypted.userPublicKey."},"encryptionType":{"type":"string","enum":["NONE","DES","RC2","3DES","AES128","AES256"],"description":"Cipher for encrypting messages sent to the partner. Any value other than NONE requires unencrypted.partnerCertificate."},"encoding":{"type":"string","enum":["base64","binary"],"description":"Content transfer encoding for outbound messages. Shown in the form only when encryptionType is not NONE."},"signatureEncoding":{"type":"string","enum":["base64","binary"],"description":"Encoding applied to the digital signature on outbound messages."}}},"userStationInfo":{"type":"object","description":"User (local) station configuration, used on the EXPORT side — controls how inbound\nmessages from the partner are processed.","required":["signing","encryptionType"],"properties":{"mdn":{"type":"object","description":"Settings for the MDN receipt this station returns to the partner for inbound messages.","required":["mdnSigning"],"properties":{"mdnURL":{"type":"string","anyOf":[{"format":"uri"},{"const":""}],"description":"Partner's endpoint this station posts asynchronous MDNs to. Set only when\nthe partner requires asynchronous MDNs; stored as an empty string otherwise.\n\nUse an HTTPS URL whenever possible. HTTP is not considered a secure protocol;\ndata sent to an HTTP endpoint is not encrypted and may be intercepted or\nmodified in transit."},"signatureProtocol":{"type":"string","enum":["pkcs7-signature"],"description":"Signature protocol applied to MDNs this station returns."},"mdnSigning":{"type":"string","enum":["NONE","SHA1","MD5","SHA256"],"description":"Hash algorithm used to sign MDNs this station returns to the partner. Signs the MDN\nonly — the hash algorithm of its `Received-Content-MIC` follows\nuserStationInfo.signing, not this field."},"mdnEncoding":{"type":"string","enum":["base64","binary"],"description":"Encoding applied to MDNs this station returns."}}},"signing":{"type":"string","enum":["NONE","SHA1","MD5","SHA256"],"description":"Hash algorithm for verifying signatures on inbound messages; the same algorithm is used\nfor the `Received-Content-MIC` in the MDN returned for each message (NONE falls back to\nSHA-1, and the algorithm the partner requests in `Disposition-Notification-Options` is\nnot used). The MIC is computed when a message is received, so a change here applies only\nto messages received afterwards — the partner must resend to get an MDN with the new\nalgorithm. Any value other than NONE requires unencrypted.partnerCertificate."},"encryptionType":{"type":"string","enum":["NONE","DES","RC2","3DES","AES128","AES256"],"description":"Cipher for decrypting inbound messages. Any value other than NONE requires encrypted.userPrivateKey and unencrypted.userPublicKey."},"encoding":{"type":"string","enum":["base64","binary"],"description":"Content transfer encoding expected on inbound messages. Shown in the form only when encryptionType is not NONE."},"compressed":{"type":"boolean","description":"When true, message content is compressed. Not exposed in the connection form.","default":false}}},"encrypted":{"type":"object","description":"Encrypted-at-rest key material for this station's own identity. Supply when this station\nsigns outbound messages or decrypts inbound messages; integrator.io injects an\nauto-generated self-signed key otherwise.","properties":{"userPrivateKey":{"type":"string","writeOnly":true,"description":"PEM-encoded X.509 private key for this station, used to sign outbound messages and\ndecrypt inbound ones. Masked as \"******\" in GET responses. Required when\npartnerStationInfo.signing is not NONE or userStationInfo.encryptionType is not NONE."}}},"unencrypted":{"type":"object","description":"Public certificate material for this station and the partner. integrator.io injects an\nauto-generated self-signed certificate for this station when none is supplied.","properties":{"userPublicKey":{"type":"string","description":"PEM-encoded X.509 public certificate for this station, paired with\nencrypted.userPrivateKey. Required when partnerStationInfo.signing is not NONE or\nuserStationInfo.encryptionType is not NONE."},"partnerCertificate":{"type":"string","description":"PEM-encoded X.509 certificate for the trading partner, used to encrypt outbound\nmessages and verify inbound signatures and MDNs. Required when\npartnerStationInfo.encryptionType is not NONE, userStationInfo.signing is not NONE,\nor partnerStationInfo.mdn.verifyMDNSignature is true."}}},"concurrencyLevel":{"type":"integer","description":"Maximum number of messages processed concurrently for this connection. The default of 5\nis safe for most partners. Send no more than the account's licensed maximum (25 standard,\n50 with an Environments license).","minimum":1,"maximum":50,"default":5},"targetConcurrencyLevel":{"type":"integer","description":"Concurrency ceiling integrator.io scales up to when auto-recovering from rate-limit\nerrors. Only applies when autoRecoverRateLimitErrors is true on the connection.","minimum":1,"maximum":50},"preventCanonicalization":{"type":"boolean","description":"When true, inbound messages are verified — and the `Received-Content-MIC` in the returned\nMDN is computed — over the content exactly as received, without first normalizing line\nendings to CRLF; the partner's MDN signatures are verified the same way. Enable only when a\npartner reports signature-verification failures or MIC mismatches caused by\ncanonicalization.","default":false}}},"Filesystem":{"type":"object","description":"Configuration for filesystem connections. Used when the connection type is \"filesystem\".\nProvides access to directories on a Celigo on-premise Agent's host or mounted network\ndrives, so a connection-level `_agentId` is required — cloud-only deployments cannot use\nthis type. File-based PGP encryption/decryption is configured through the connection-level\n`pgp` object.","properties":{"ping":{"type":"object","description":"Connection health-check configuration. integrator.io reads this directory to verify the agent can reach the filesystem.","properties":{"directoryPath":{"type":"string","description":"Absolute directory path the agent checks to confirm filesystem access. Accepts a POSIX path,\na Windows drive path, or a UNC share. Leave unset to skip the directory check during ping."}}},"concurrencyLevel":{"type":"integer","description":"Maximum number of files processed concurrently. Send no more than `targetConcurrencyLevel` and\nthe account's licensed maximum (25 standard, 50 with an Environments license).","minimum":1,"maximum":50,"default":1},"targetConcurrencyLevel":{"type":"integer","description":"Upper bound for auto-scaling. The agent scales concurrency between 1 and this value based on\nthroughput, so it also caps `concurrencyLevel`. Send no more than the account's licensed maximum\n(25 standard, 50 with an Environments license).","minimum":1,"maximum":50,"default":1}}},"MCP":{"type":"object","description":"Configuration for MCP (Model Context Protocol) connections. Used when the connection type\nis \"mcp\". Lets Celigo call tools exposed by an external MCP server over HTTP. Authentication\ngoes through the http sub-object, which reuses the HTTP auth config but accepts only the\ntoken, oauth, and custom auth types.","required":["serverURL","http"],"properties":{"protocol":{"type":"string","enum":["http"],"default":"http","readOnly":true,"description":"Transport protocol for communicating with the MCP server.\nCurrently only \"http\" is supported; the server sets this automatically and ignores other values.\n"},"serverURL":{"type":"string","format":"uri","description":"MCP server endpoint URL. Must be a valid absolute URL\n(e.g. \"https://mcp-server.example.com/mcp\").\n\nUse an HTTPS URL whenever possible. HTTP is not considered a secure protocol; data sent\nto an HTTP endpoint is not encrypted and may be intercepted or modified in transit."},"timeout":{"type":"number","default":600000,"description":"Request timeout in milliseconds for MCP tool invocations.\nIf the MCP server does not respond within this time, the request fails.\nDefaults to 600000 (10 minutes) when omitted.\n"},"allowedTools":{"type":"array","description":"Optional allowlist of MCP tool names that this connection may invoke.\nWhen set, only tools in this list can be called. When omitted or empty,\nall tools exposed by the MCP server are available.\n","items":{"type":"string"}},"http":{"type":"object","required":["auth"],"description":"HTTP transport configuration for the MCP connection, including authentication and headers.\n","properties":{"_iClientId":{"type":"string","format":"objectId","description":"Reference to an OAuth iClient for OAuth-based MCP authentication.\nRequired when http.auth.type is \"oauth\".\n"},"auth":{"type":"object","required":["type"],"description":"Authentication configuration for the MCP connection. The auth.type field selects the\nstrategy. MCP supports only token, oauth, and custom — other HTTP auth types\n(basic, wsse, cookie, jwt, etc.) are rejected with a 422.\n","properties":{"type":{"type":"string","enum":["token","oauth","custom"],"description":"Authentication method for the MCP server. Determines which auth sub-fields apply."},"token":{"$ref":"#/components/schemas/token"},"oauth":{"$ref":"#/components/schemas/OAuth"}}},"headers":{"type":"array","description":"Default HTTP headers included in every request to the MCP server.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Header name."},"value":{"type":"string","description":"Header value. Supports handlebars expressions for dynamic values."}},"required":["name","value"]}},"unencrypted":{"type":"object","description":"Unencrypted custom fields for non-sensitive MCP configuration."},"encrypted":{"type":["object","string"],"description":"Encrypted custom fields for sensitive MCP configuration. Sent as an object;\nreturned as the masked string `\"******\"` on responses."}},"if":{"properties":{"auth":{"properties":{"type":{"const":"token"}},"required":["type"]}},"required":["auth"]},"then":{"properties":{"auth":{"required":["token"],"properties":{"token":{"required":["token"]}}}}},"else":{"if":{"properties":{"auth":{"properties":{"type":{"const":"oauth"}},"required":["type"]}},"required":["auth"]},"then":{"required":["_iClientId"]}}}}},"token":{"type":"object","description":"Token-based authentication configuration. Required when auth.type is \"token\".\n\nSupports static API keys/bearer tokens and automatic token refresh flows.\nThe token can be sent in a header (Authorization), query parameter, or request body.\n","properties":{"token":{"type":"string","description":"The API key or bearer token value (encrypted at rest); returned masked as `\"******\"`.\nOn update, the mask sent back keeps the stored token; a new value replaces it.\nRequired unless automatic token refresh is configured: a token block without a\ntoken must carry a `refreshMethod`, so a create or update that sends neither fails\nwith 422 `Expected field: refreshMethod to be present.` To create a static-token\nconnection before the real token is available, send a placeholder value and\nreplace it later.\n"},"location":{"type":"string","enum":["url","header","body"],"description":"Where to include the token in outbound requests.\nMost APIs expect header. Use headerName and scheme to control the header format,\nor paramName when sending as a URL query parameter."},"headerName":{"type":"string","description":"HTTP header name for the token when location is \"header\".\nDefaults to \"Authorization\" if omitted.\n"},"scheme":{"type":"string","description":"Token scheme/prefix when sent in a header. Prepended before the token value.\nCommon values: \"Bearer\", \"Token\", \"Basic\".\nExample: scheme \"Bearer\" produces header \"Authorization: Bearer <token>\".\n"},"paramName":{"type":"string","description":"Query parameter name for the token when location is \"url\".\nExample: paramName \"api_key\" produces URL \"?api_key=<token>\".\n"},"refreshMethod":{"type":"string","enum":["GET","POST","PUT"],"description":"HTTP method for automatic token refresh requests.\nRequired when no static token is provided (refresh-based auth flow).\n","default":"POST"},"refreshRelativeURI":{"type":"string","description":"Relative URI (appended to baseURI) for the token refresh endpoint.\nThe system calls this endpoint to obtain a new token when the current one expires.\n"},"refreshBody":{"type":"string","description":"Request body to send with the token refresh request."},"refreshMediaType":{"type":"string","enum":["json","urlencoded","xml","plaintext"],"description":"Content type for the token refresh request body.\n","default":"urlencoded"},"refreshResponseMediaType":{"type":"string","enum":["json","xml"],"description":"Expected content type of the token refresh response."},"refreshTokenPath":{"type":"string","description":"JSON path to extract the new token from the refresh response.\nExample: \"access_token\" or \"data.token\".\n"},"refreshToken":{"type":"string","description":"Refresh token used to obtain a new access token (encrypted at rest).","writeOnly":true},"refreshTokenLocation":{"type":"string","enum":["header","body"],"description":"Where to include the refresh token in refresh requests."},"refreshHeaders":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Header name to send with the token-refresh request."},"value":{"type":"string","description":"Header value to send with the token-refresh request."}}},"description":"Additional headers to include in token refresh requests."},"tokenPaths":{"type":"array","items":{"type":"string"},"description":"JSON paths to extract multiple token values from the refresh response.\nUse when the refresh response contains tokens at different paths that need\nto be stored for subsequent requests.\n"},"revoke":{"type":"object","description":"Token-revocation request configuration. When set, the system calls this\nendpoint to revoke the current token (e.g. when the connection is deleted or\nre-authenticated). Used mainly by pre-built HTTP Connector templates.\n","properties":{"uri":{"type":"string","description":"Absolute URL of the token-revocation endpoint."},"body":{"type":"string","description":"Request body to send with the token-revocation request."},"headers":{"type":"array","description":"Additional headers to include in the token-revocation request.","items":{"type":"object","properties":{"name":{"type":"string","description":"Header name to send with the revocation request."},"value":{"type":"string","description":"Header value to send with the revocation request."}}}}}}}},"DynamoDB":{"type":"object","description":"Configuration for Amazon DynamoDB connections. Used when the connection type is \"dynamodb\".\nAuthenticates with AWS IAM access keys, which must grant the DynamoDB actions the integration\nuses (GetItem, PutItem, Query, Scan, etc.). The AWS region is resolved per table at request\ntime, so no region is stored on the connection.","required":["aws"],"properties":{"aws":{"type":"object","description":"AWS IAM access-key credentials used to sign DynamoDB requests.","required":["accessKeyId","secretAccessKey"],"properties":{"accessKeyId":{"type":"string","description":"Access key ID of the IAM user or role whose policy grants the required DynamoDB actions."},"secretAccessKey":{"type":"string","description":"Secret access key paired with `accessKeyId`. Encrypted at rest and returned masked as `\"******\"`.","writeOnly":true}}},"concurrencyLevel":{"type":"integer","description":"Maximum number of DynamoDB requests this connection runs at once.\n\nSend no more than the account's licensed maximum (25 standard, 50 with an Environments license).","minimum":1,"maximum":50,"default":5},"targetConcurrencyLevel":{"type":"integer","description":"Concurrency ceiling for auto-scaling. The system adjusts the active concurrency between 1\nand this value based on rate-limit feedback. Only applied when `autoRecoverRateLimitErrors`\nis enabled on the connection. The same licensed maximum applies as for `concurrencyLevel`.","minimum":1,"maximum":50,"default":5}}},"JDBC":{"type":"object","description":"Configuration for JDBC (Java Database Connectivity) connections. Used when the connection\ntype is \"jdbc\". Provides access through Java JDBC drivers to databases not covered by the\nrdbms type — such as NetSuite SuiteAnalytics, Databricks, DB2, and Workday. The driver runs\non a Celigo on-premise Agent, so most jdbc connections need a connection-level _agentId.","required":["type"],"properties":{"type":{"type":"string","enum":["agent","netsuitejdbc","databricks","oracle:thin","sqlserver","activedirectory","db2","workday"],"description":"JDBC driver/connection type. Selects which JDBC driver is used and determines\nthe required fields. Most JDBC connections require a Celigo on-premise Agent."},"version":{"type":"string","description":"JDBC driver version. Used for driver compatibility when multiple versions are available."},"host":{"type":"string","description":"Database server hostname or IP address.\nFor NetSuite JDBC, use the SuiteAnalytics Connect hostname\n(e.g., \"account-id.connect.api.netsuite.com\").\n"},"port":{"type":"number","description":"Database server port number. Default varies by driver type."},"database":{"type":"string","description":"Database or catalog name. For Oracle, use the serviceName field instead.\n"},"user":{"type":"string","description":"Database username for authentication."},"password":{"type":"string","description":"Database password (encrypted at rest).","writeOnly":true},"serviceName":{"type":"string","description":"Oracle service name. Used instead of the database field for Oracle JDBC connections.\nThis is the TNS service name or pluggable database (PDB) service name.\n"},"authType":{"type":"string","enum":["customjdbc","wallet"],"description":"Authentication method for the JDBC connection. Defaults to customjdbc (username/password).\nUse wallet for Oracle Wallet authentication with the wallet field."},"wallet":{"type":"string","description":"Oracle Wallet file contents (encrypted at rest).\nRequired when authType is \"wallet\". Contains the auto-login wallet (cwallet.sso)\nwith encrypted credentials for passwordless Oracle authentication.\n","writeOnly":true},"driverPath":{"type":"string","description":"File path to the JDBC driver JAR on the Celigo Agent.\nRequired when type is \"agent\" (generic JDBC).\nThe driver must be deployed on the agent before creating the connection.\n"},"properties":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"JDBC connection property name (driver-specific)."},"value":{"type":"string","description":"JDBC connection property value."}}},"description":"Additional JDBC connection properties as name/value pairs.\nThese are passed directly to the JDBC driver as connection properties.\nUse for driver-specific settings like SSL mode, connection timeout,\napplication name, etc.\n"},"concurrencyLevel":{"type":"integer","description":"Maximum number of concurrent database connections.\nJDBC connections often run through a single Agent, so keep this value\nconservative to avoid overwhelming the Agent or database.\nSend no more than the account's licensed maximum (25 standard, 50 with an Environments license).\n","minimum":1,"maximum":50,"default":5},"targetConcurrencyLevel":{"type":"integer","description":"Target concurrency level for auto-scaling. The system adjusts the number of\nconcurrent connections between 1 and this value based on performance feedback.\n","minimum":1,"maximum":50,"default":5}},"if":{"properties":{"type":{"const":"db2"}},"required":["type"]},"then":{"required":["host","user","password","database","port","properties"]},"else":{"if":{"properties":{"type":{"const":"oracle:thin"}},"required":["type"]},"then":{"required":["user","password","authType"],"if":{"properties":{"authType":{"const":"wallet"}},"required":["authType"]},"then":{"required":["wallet","serviceName"]},"else":{"required":["host"]}},"else":{"if":{"properties":{"type":{"const":"activedirectory"}},"required":["type"]},"then":{"required":["host","port","user","password","properties"]},"else":{"if":{"properties":{"type":{"const":"netsuitejdbc"}},"required":["type"]},"then":{"required":["host","user","properties"]},"else":{"if":{"properties":{"type":{"const":"agent"}},"required":["type"]},"then":{"required":["host","driverPath"]},"else":{"if":{"properties":{"type":{"const":"workday"}},"required":["type"]},"then":{"required":["host","user","password","properties"]},"else":{"if":{"properties":{"type":{"const":"databricks"}},"required":["type"]},"then":{"required":["host"]}}}}}}}},"VAN":{"type":"object","description":"Configuration for VAN (Value-Added Network) connections. Used when the connection type is \"van\".\nA VAN connection exchanges EDI documents through a managed network mailbox; outbound documents are\nposted to the mailbox and inbound documents are picked up from it, addressed by the connection's AS2 identifier.","properties":{"as2Id":{"type":"string","description":"AS2 identifier for this VAN station — the \"From\" identifier on outbound documents and the \"To\"\nidentifier on inbound. Must be unique across all integrator.io VAN connections; a duplicate fails\nthe create. Set once when the connection is created and cannot be changed afterward (writes on update\nare ignored). Omit it to have the network assign one automatically."},"mailboxId":{"type":"number","readOnly":true,"description":"Numeric mailbox identifier assigned by the VAN provider when the connection is provisioned.\nServer-assigned and not writable — values sent on create or update are ignored."},"contentBasedFlowRouter":{"type":"object","description":"Routes inbound documents to different flows based on message content, for a connection shared across\nmultiple flows. The named function in the referenced script inspects each incoming document and returns\nthe flow to run. Omit to disable content-based routing.","properties":{"function":{"type":"string","description":"Function in the referenced script that returns the target flow for each inbound document."},"_scriptId":{"type":"string","format":"objectId","description":"ID of the script resource that contains the routing function. Must reference an existing script; an unknown ID fails the request with 422 `invalid_ref`."}}}}},"Wrapper":{"type":"object","description":"Configuration for Wrapper connections. Used when the connection type is \"wrapper\". A wrapper\nis a fully custom connector implemented in server-side JavaScript on a Celigo Stack\n(referenced by _stackId) — use it when the target API needs\nnon-standard authentication or logic that HTTP connections can't express. Connection-specific\nvalues go in the encrypted/unencrypted fields, which the wrapper code reads at runtime.","required":["pingFunction"],"properties":{"pingFunction":{"type":"string","description":"Name of the JavaScript function on the Stack that tests connection health, invoked when\n\"Test Connection\" is clicked. It should verify the credentials are valid and the target\nsystem is reachable."},"unencrypted":{"type":"object","description":"Unencrypted custom fields for non-sensitive configuration values.\nThese values are accessible to the wrapper code on the Stack at runtime.\nField definitions are specified in unencryptedFields.\n"},"unencryptedFields":{"type":"array","description":"Metadata defining the unencrypted custom fields on this connection.","items":{"type":"object","properties":{"id":{"type":"string","description":"Field identifier — matches the key in the unencrypted object."},"label":{"type":"string","description":"Human-readable label shown in the UI."},"required":{"type":"boolean","default":false,"description":"When true, the custom field defined by this entry is mandatory — a value for it must be supplied before the connection can be saved."},"position":{"type":"number","description":"Display order in the UI form."},"helpText":{"type":"string","description":"Tooltip text shown next to the field in the UI."},"type":{"type":"string","description":"Optional, legacy field-type hint. The wrapper form ignores it — it always renders these\ncustom fields as text inputs — so it is rarely set."}}}},"encrypted":{"type":["object","string"],"description":"Encrypted custom fields for sensitive configuration values (API keys, passwords, etc.).\nSent as an object of field-name/value pairs; values are encrypted at rest and decrypted\nonly on the Stack at runtime. Returned as the masked string `\"******\"` on responses.\nField definitions are specified in encryptedFields.\n"},"encryptedFields":{"type":"array","description":"Metadata defining the encrypted custom fields on this connection.","items":{"type":"object","properties":{"id":{"type":"string","description":"Field identifier — matches the key in the encrypted object."},"label":{"type":"string","description":"Human-readable label shown in the UI."},"required":{"type":"boolean","default":false,"description":"When true, the custom field defined by this entry is mandatory — a value for it must be supplied before the connection can be saved."},"position":{"type":"number","description":"Display order in the UI form."},"helpText":{"type":"string","description":"Tooltip text shown next to the field in the UI."},"type":{"type":"string","description":"Optional, legacy field-type hint. The wrapper form ignores it — it always renders these\ncustom fields as text inputs (masked) — so it is rarely set."}}}},"_stackId":{"type":"string","format":"objectId","description":"Reference to the Celigo Stack whose server-side JavaScript implements this connection."},"concurrencyLevel":{"type":"integer","description":"Maximum number of concurrent operations through this wrapper connection.\nSend no more than the account's licensed maximum (25 standard, 50 with an Environments license).\n","minimum":1,"maximum":50,"default":5},"targetConcurrencyLevel":{"type":"integer","description":"Target concurrency level for auto-scaling. The system adjusts concurrency\nbetween 1 and this value based on performance feedback.\n","minimum":1,"maximum":50}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/connections":{"get":{"summary":"List connections a tool depends on","operationId":"listToolConnections","tags":["Tools"],"description":"Returns the full Connection resources the tool references — both directly\n(via `_connectionId` fields on its steps) and transitively through\ndescendant resources (inner tools, lookups, imports, exports).\n\nUseful for discovering what systems a tool talks to before cloning, moving,\nor evaluating the blast radius of a connection change. For the full\ndependency tree (imports, exports, nested tools), use\n`GET /v1/tools/{_id}/descendants` instead.","parameters":[{"name":"_id","in":"path","required":true,"description":"Tool id.","schema":{"type":"string","format":"objectId"}}],"responses":{"200":{"description":"Array of full Connection objects referenced by the tool and its\ndescendants. Empty array when no connections are referenced.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Connection"}}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"}}}}}}
```

## Replace a connection across a tool

> Repoints every export and import step in the tool's \`routers\` that uses\
> \`\_connectionId\` to \`\_newConnectionId\`, saving each step resource in place. Use it\
> instead of per-resource PUTs when rotating credentials or promoting a tool between\
> environments; \`GET /v1/tools/{\_id}/connections\` shows which connections the tool\
> references before and after.\
> \
> Each affected step is re-validated against the replacement connection before anything\
> is saved, so a connection of an incompatible type (an FTP connection for an HTTP\
> lookup, for example) fails the whole request with \`422\` and leaves every step\
> unchanged. A request whose \`\_connectionId\` matches no step still succeeds with \`204\`.\
> Unlike the API sibling, no count is returned.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ToolReplaceConnectionRequest":{"type":"object","description":"Request body to swap one connection for another on every export and import step in\nthe tool's routers. Both ids must be well-formed; the replacement connection must exist\nin the account and satisfy each affected step's adaptor validation.","required":["_connectionId","_newConnectionId"],"properties":{"_connectionId":{"type":"string","format":"objectId","description":"Connection currently referenced by the tool's steps. Steps that reference a\ndifferent connection are left untouched."},"_newConnectionId":{"type":"string","format":"objectId","description":"Connection to reference instead. Must be usable by every matching step — an\nHTTP lookup cannot be repointed to an FTP connection, for example."}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}}}},"paths":{"/v1/tools/{_id}/replaceConnection":{"put":{"operationId":"replaceConnectionOnTool","tags":["Tools"],"summary":"Replace a connection across a tool","description":"Repoints every export and import step in the tool's `routers` that uses\n`_connectionId` to `_newConnectionId`, saving each step resource in place. Use it\ninstead of per-resource PUTs when rotating credentials or promoting a tool between\nenvironments; `GET /v1/tools/{_id}/connections` shows which connections the tool\nreferences before and after.\n\nEach affected step is re-validated against the replacement connection before anything\nis saved, so a connection of an incompatible type (an FTP connection for an HTTP\nlookup, for example) fails the whole request with `422` and leaves every step\nunchanged. A request whose `_connectionId` matches no step still succeeds with `204`.\nUnlike the API sibling, no count is returned.","parameters":[{"name":"_id","in":"path","required":true,"description":"Tool id.","schema":{"type":"string","format":"objectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolReplaceConnectionRequest"}}}},"responses":{"204":{"description":"Matching steps now reference the new connection (also returned when no step matched). No response body."},"400":{"description":"A required id is missing or malformed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"description":"The tool or the replacement connection does not exist in the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A matching step cannot use the replacement connection. The error is the affected\nstep's adaptor validation failure; no step is changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## List resources a tool depends on, grouped by type

> Returns the full dependency tree of a tool as three arrays: the\
> \`imports\`, \`exports\`, and nested \`tools\` it references directly or\
> transitively. Each entry is the complete resource document, so the\
> caller doesn't need to fan out individual GETs.\
> \
> Pair with \`GET /v1/tools/{\_id}/connections\` to enumerate the full\
> resource and connection footprint in two calls.

````json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Import":{"type":"object","required":["_id","name","adaptorType","apiIdentifier","createdAt","lastModified"],"description":"Import object as returned by the API.","allOf":[{"$ref":"#/components/schemas/ImportBase"},{"$ref":"#/components/schemas/ResourceResponse"},{"$ref":"#/components/schemas/IAResourceResponse"},{"type":"object","properties":{"aiDescription":{"$ref":"#/components/schemas/AIDescription"},"apim":{"$ref":"#/components/schemas/APIM"},"apiIdentifier":{"type":"string","readOnly":true,"description":"API identifier assigned to this import."},"_sourceId":{"type":"string","format":"objectId","readOnly":true,"description":"Reference to the source resource this import was created from."},"_templateId":{"type":"string","format":"objectId","readOnly":true,"description":"Template this import was created from."},"draft":{"type":"boolean","readOnly":true,"description":"When true, this import is in draft state and has not been confirmed."},"draftExpiresAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the draft version of this import expires."},"debugUntil":{"type":"string","format":"date-time","readOnly":true,"description":"Turns on step debug capture until this time: while it lies in the future, every\nrequest this import sends — or, for a `ToolImport`, every record passing through\nit with the tool's output — is recorded in the step's debug log\n(`GET /v1/{flows|apis|tools}/{containerId}/{_id}/requests`). Read-only on PUT —\nwritten only by `PATCH /v1/imports/{_id}` on `/debugUntil`, at most 3600 seconds\nahead (`422` `update_not_allowed` beyond that); `remove` stops capture."}}}]},"ImportBase":{"type":"object","description":"Writable import fields shared by the request and response schemas.","properties":{"_connectionId":{"type":"string","format":"objectId","description":"Connection this import uses to reach the destination system. The connection's type must\nbe compatible with the import's `adaptorType` (e.g. an `HTTPImport` needs an `http`\nconnection; `NetSuiteDistributedImport` and `NetSuiteHTTPImport` both need a `netsuite`\nconnection). Server-required — POST without it fails with 422 \"Expected field:\n_connectionId to be present\" — except for the connection-less flavors (`ToolImport`,\n`AiAgentImport`, `GuardrailImport`, `TopicImport`), which the server creates without\none. An Event Streams publisher (`TopicImport`) must omit it: with a connection set the\nserver validates the document as that connection's adaptor instead."},"_integrationId":{"type":["string","null"],"format":"objectId","description":"Not stored on an import: a value sent on create or on a full update is dropped, and a GET\ndoesn't return the field. An import isn't owned by an integration; to find the flows and\nother resources that use it, call `GET /v1/imports/{_id}/dependencies`."},"_connectorId":{"type":"string","format":"objectId","description":"Connector this import was created from, set when the import is part of an installed\nintegration app. The app's install sets it: a create that sends it is refused with 403\n(\"Cannot create an Integration App resource\")."},"adaptorType":{"type":"string","description":"Selects the adaptor technology that executes this import, which determines the compatible\nconnection types and which adaptor-specific configuration object must also be supplied\n(e.g. set `salesforce` when using `SalesforceImport`).","enum":["HTTPImport","FTPImport","AS2Import","S3Import","NetSuiteImport","NetSuiteDistributedImport","NetSuiteHTTPImport","SalesforceImport","JDBCImport","RDBMSImport","MongodbImport","DynamodbImport","WrapperImport","AiAgentImport","GuardrailImport","FileSystemImport","ToolImport","TopicImport","RESTImport"]},"nsDomainType":{"type":"string","enum":["suitetalk","restlet"],"description":"Selects which NetSuite REST host a `NetSuiteHTTPImport` calls. The server derives the\nfull base URL from the connection's NetSuite account\n(`https://<account>.suitetalk.api.netsuite.com` or `https://<account>.restlets.api.netsuite.com`)\nand signs each request with the connection's token-based credentials, so each\n`http.relativeURI` entry carries the complete path starting at `/services/rest/...` or\n`/app/site/hosting/restlet.nl`. Ignored on other adaptor types."},"externalId":{"type":["string","null"],"description":"External identifier for correlating the import with a record in another system."},"as2":{"$ref":"#/components/schemas/As2"},"dynamodb":{"$ref":"#/components/schemas/Dynamodb"},"http":{"$ref":"#/components/schemas/Http"},"ftp":{"$ref":"#/components/schemas/Ftp"},"jdbc":{"$ref":"#/components/schemas/Jdbc"},"mongodb":{"$ref":"#/components/schemas/Mongodb"},"netsuite":{"$ref":"#/components/schemas/NetSuite-2"},"netsuite_da":{"$ref":"#/components/schemas/NetsuiteDistributed"},"rdbms":{"$ref":"#/components/schemas/Rdbms"},"s3":{"$ref":"#/components/schemas/S3-2"},"wrapper":{"$ref":"#/components/schemas/Wrapper-2"},"salesforce":{"$ref":"#/components/schemas/Salesforce-2"},"tool":{"$ref":"#/components/schemas/Tool-2"},"topic":{"$ref":"#/components/schemas/Topic"},"file":{"$ref":"#/components/schemas/File"},"filesystem":{"$ref":"#/components/schemas/FileSystem"},"aiAgent":{"$ref":"#/components/schemas/AiAgentConfig"},"guardrail":{"$ref":"#/components/schemas/GuardrailConfig"},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Display name for the import, shown in the flow builder, job history, and error logs.\nDescriptive, unique names indicating the destination system and purpose make large\naccounts easier to manage."},"description":{"type":["string","null"],"description":"Free-text summary of what the import writes and why. Shown in the UI and available to\nAI agents for context; has no effect on execution.","maxLength":5120},"unencrypted":{"type":"object","description":"Custom configuration values stored without encryption and returned in API responses."},"sampleData":{"type":["object","array","string"],"description":"Sample input record used to preview and build the import's mappings."},"distributed":{"type":"boolean","description":"When true, the import uses a distributed adaptor (such as `NetSuiteDistributedImport`)\nthat executes inside the target application rather than on Celigo's servers."},"maxAttempts":{"type":"number","description":"Maximum number of attempts made to deliver a record before it is marked as failed."},"ignoreExisting":{"type":"boolean","description":"When true, records that already exist in the destination system are silently skipped\ninstead of being created or updated — used for create-only operations that must avoid\nduplicates. Existing records are identified by the import's lookup configuration or by\na populated `ignoreExtract` field on the incoming record."},"ignoreMissing":{"type":"boolean","description":"When true, records that do not already exist in the destination system are silently\nskipped instead of producing errors — used for update-only operations. Existing records\nare identified the same way as for `ignoreExisting`: the adaptor's ignore lookup\n(`ignoreLookupName`) or a populated `ignoreExtract` field on the incoming record."},"idLockTemplate":{"type":["string","null"],"description":"Handlebars template that generates a concurrency lock key for each record: records that\nrender the same key are never submitted at the same time, while records with different\nkeys are processed in parallel. When omitted, the record's trace key is the lock key (a\nblank trace key means no lock; a custom `traceKeyTemplate` upstream arrives on the record\nas `io_custom_trace_key`); a template replaces that default rather than adding to it.\nThe template is rendered from the record as it arrives at the import — before the import's\nown lookups and mapping run — so a key built from one of the import's lookup results\n(`{{lookup.<name>}}`) or from a mapped field renders empty, and an empty key is no lock at\nall; a field added upstream by a lookup step's response mapping is on the record and works."},"dataURITemplate":{"type":["string","null"],"description":"Handlebars template that builds a link back to each record in the destination\napplication's UI. The resolved URL is stored with error records in job history so users\ncan jump straight to the record."},"oneToMany":{"$ref":"#/components/schemas/OneToMany"},"pathToMany":{"$ref":"#/components/schemas/PathToMany"},"blobKeyPath":{"type":"string","description":"Path in the input record that holds the blob key identifying the file content to import.\nAt send time the platform follows this path, retrieves the referenced file from integrator.io\nstorage, and streams its bytes into the outgoing request. Not accepted on `AiAgentImport`,\n`GuardrailImport`, or `ToolImport` — those adaptors process records only and reject the\nfield with 422 `not_supported`."},"blob":{"type":"boolean","description":"When true, this import transfers raw file content (blobs) to the destination rather than\nstructured records, reading each file through `blobKeyPath` (optional on FTP and Google\nDrive file-provider imports, which fall back to the record's root `blobKey`). Not accepted on\n`AiAgentImport`, `GuardrailImport`, or `ToolImport` — those adaptors process records only\nand reject the flag with 422 `not_supported`."},"assistant":{"type":"string","description":"Identifier for the connector assistant used to configure this import. For the cloud\nfile providers it also selects the provider-specific location rules —\n`azuredatalakestoragegen2` (container in `file.directory.name`, file path split into\n`http.relativeURI` and `file.fileName`) and `googlecloudstorage` (bucket in\n`file.directory.storageRootId`)."},"deleteAfterImport":{"type":"boolean","description":"When true, the source file is deleted after it is successfully imported."},"assistantMetadata":{"type":"object","description":"Metadata associated with the connector assistant configuration."},"useTechAdaptorForm":{"type":"boolean","description":"When true, the UI presents the full technical adaptor form for this import instead of\nthe simplified assistant form."},"distributedAdaptorData":{"type":"object","description":"Internal state stored by distributed adaptors (such as the NetSuite SuiteApp) for this import."},"filter":{"description":"Filter applied to incoming records before they are sent to the destination system.\nRecords that match continue through the import; records that don't skip this import,\ncounted as ignored, and continue to the next step. Filter expressions reference the\nincoming record's fields.","allOf":[{"$ref":"#/components/schemas/Filter"}]},"traceKeyTemplate":{"type":"string","description":"Handlebars template that overrides how each record's unique trace key is generated,\nused to track records through the flow and match errors to records. Trace keys are\ncapped at 256 characters; a longer key is stored truncated from the middle, keeping\nthe beginning and end of the value. The trace key is also the import's default\nconcurrency lock key when `idLockTemplate` is not set; it never de-duplicates — a record\nthat arrives twice with the same key is processed twice."},"mockResponse":{"type":"array","description":"Predefined response records used in place of calling the destination system when\ntesting the import, so flows can run without writing real data. Records must be in\nintegrator.io canonical format — the server rejects any other shape with a 422.","items":{"type":"object","properties":{"statusCode":{"type":"integer","description":"HTTP status code the mock returns (e.g. 200, 403)."},"id":{"type":["string","integer"],"description":"Identifier echoed for the mocked record."},"ignored":{"type":"boolean","description":"When true, the mocked record reports as ignored rather than imported."},"dataURI":{"type":"string","description":"Data URI echoed for the mocked record."},"errors":{"type":"array","description":"Mock error entries returned with the record."},"_json":{"type":["object","array"],"description":"Mock response body."},"_headers":{"type":"object","description":"Mock response headers."}}}},"_ediProfileId":{"type":"string","format":"objectId","description":"EDI profile this import uses to generate outbound X12 or EDIFACT documents — it supplies\nthe envelope qualifiers, delimiters, version, and validation rules. Set it when the\nimport produces EDI output; omit it otherwise. Accepted on the file-writing imports —\nFTP, AS2, S3 and HTTP file mode (`http.type: \"file\"`)."},"parsers":{"type":"array","description":"Legacy parser configuration slot. The server initializes this field to an empty array\nand current API writes never populate it; treat it as server bookkeeping rather than a\nsetting to configure."},"hooks":{"type":"object","description":"Custom JavaScript hooks that run at fixed points in the import lifecycle for\ntransformations, validation, and custom result handling beyond what configuration alone\ncan express. A hook name not listed here (an export's `preSavePage`, for example) is\ndropped on save without an error, so read `hooks` back after saving.","properties":{"preMap":{"type":"object","description":"Hook that runs on each page of records before the import's mappings are applied.\nCommonly used to reshape or filter records ahead of mapping. Its changes shape only\nthe request this import sends: the next step receives the record as it arrived here,\nplus whatever this step's response mapping merges back. A value later steps need\nbelongs in the source export's `preSavePage`, in this step's `postResponseMap` hook on\nthe flow, or in the later import's own `preMap`.\n\nThe function returns one element per record in `options.data`, in order:\n`{ data: <record> }` sends the record and `{ errors: [{ code, message }] }` fails it.\nAn element without `data` — `{}`, or the record itself returned unwrapped — skips that\nrecord with no error: nothing is sent and the record is reported `ignored: true` at\nstage `preMapHook`. A return with more or fewer elements fails every record on the page\nwith `invalid_extension_response` (\"Extension result doesn't contain the same number\nof elements as the request object. Expected 2, got 1. Extension returned statusCode\n200\"), source `pre_map_hook`.","allOf":[{"$ref":"#/components/schemas/Hook"}]},"postMap":{"type":"object","description":"Hook that runs after the import's mappings are applied but before records are sent\nto the destination system. Commonly used for adjustments that need the mapped\n(destination-shaped) record. Like `preMap`, its changes shape only the request this\nimport sends; the next step never sees them.","allOf":[{"$ref":"#/components/schemas/Hook"}]},"postSubmit":{"type":"object","description":"Hook that runs after the destination system responds, with access to both the\nsubmitted records and the destination's responses. Commonly used to inspect results\nor adjust the response data passed downstream. Each `options.responseData` entry is\none submitted record's result, in order: `statusCode`, `errors` (`{code, message,\nsource}`, empty on success), `ignored`, `id`, and `_json` — the destination's parsed\nresponse body, where its own error detail sits (`_headers` holds the response\nheaders).","allOf":[{"$ref":"#/components/schemas/Hook"}]},"postAggregate":{"type":"object","description":"Hook that runs after an aggregated file has been submitted to the destination, for\nfile-based imports that combine records into a single file.","allOf":[{"$ref":"#/components/schemas/Hook"}]}}},"sampleResponseData":{"type":["object","array","string"],"description":"Sample destination response kept with the import for authoring its response mapping.\nFlow test runs and previews don't read it; they substitute `mockResponse`, or a\nplaceholder when none is saved."},"responseTransform":{"description":"Transformation that reshapes the destination system's response after records are\nimported: it runs after the postSubmit hook, on each record's response body (`_json`),\nand its output replaces that body, so response mapping can't read a field the\ntransform drops. Expression rules use version `\"1\"` (`rules`); version `\"2\"` is refused\non save. Commonly used to extract relevant fields from verbose API responses.","allOf":[{"$ref":"#/components/schemas/Transform"},{"type":"object","properties":{"expression":{"type":"object","properties":{"version":{"type":"string","enum":["1"],"description":"Must be `\"1\"` on an import's response transform; `\"2\"` is refused with 422."}}}}}]},"modelMetadata":{"type":"object","description":"Metadata about the destination data model captured for this import. Rarely set."},"mapping":{"type":"object","description":"Mapper 1.0 mapping configuration. `fields` maps individual fields; `lists` maps\nrecord lists, each with its own `fields` array. Superseded by Mapper 2.0\n(`mappings`) but still widely used.","properties":{"fields":{"type":"array","description":"Field-level mapping entries applied to each record.","items":{"$ref":"#/components/schemas/MappingField"}},"lists":{"type":"array","description":"List-level mappings, each generating a sublist on the target record.","items":{"type":"object","properties":{"generate":{"type":"string","description":"Target sublist or array path to generate."},"fields":{"type":"array","description":"Field mappings applied within each generated list item.","items":{"$ref":"#/components/schemas/MappingField"}}}}}}},"mappings":{"description":"Field mappings that transform incoming records into the destination system's field\nstructure — renaming fields, converting types, building nested objects, and applying\nformulas or lookups to derive values. The mapped output builds only the request this\nimport sends: the next step receives the record as it arrived here, plus whatever this\nstep's response mapping merges back.","allOf":[{"$ref":"#/components/schemas/Mappings"}]},"lookups":{"description":"Top-level mirror of the adaptor-specific ``lookups`` array. Celigo persists lookups\nin TWO locations on every import resource: ``<adaptor_key>.lookups`` (the typed,\nadaptor-specific definition) and this top-level ``lookups`` array (a permissive copy).","allOf":[{"$ref":"#/components/schemas/Lookups"}]},"inputContext":{"type":"string","enum":["record","envelope"],"description":"Controls the shape of the input passed to the import's processing pipeline."},"settingsForm":{"$ref":"#/components/schemas/Form"},"preSave":{"$ref":"#/components/schemas/PreSave"},"settings":{"$ref":"#/components/schemas/Settings"}}},"As2":{"type":"object","description":"Configures how outbound AS2 messages are built for this import. Required when the\n_connectionId field references an AS2 connection. AS2 (Applicability Statement 2) transmits\nEDI and other data securely over HTTP/S using S/MIME encryption and digital signatures; this\nobject names the payload file, the message identifier, retry behavior, and any extra headers.","required":["fileNameTemplate"],"properties":{"_tpConnectorId":{"type":"string","format":"objectId","description":"Trading Partner Connector that supplies the partner-specific EDI and AS2 configuration.\nWhen set, the import inherits the connector's pre-configured settings; omit to use only\nthe AS2 connection details."},"fileNameTemplate":{"type":"string","description":"Handlebars template that names the file carrying each AS2 message payload. Include a\nuniqueness token such as {{timestamp}} so concurrent or repeated sends do not collide;\ndefaults to file-{{timestamp}} in the form."},"messageIdTemplate":{"type":"string","description":"Handlebars template that generates the Message-ID header for each AS2 message. Placeholders\nare replaced at runtime; the resulting value must be globally unique and follow RFC 5322\nheader formatting. Leave unset to let the platform generate the Message-ID."},"maxRetries":{"type":"number","default":0,"description":"Number of times a failed transmission is retried after transient errors such as network\nfailures or timeouts; does not affect the initial send. The form offers 1–5; set to 0\n(the default) to report failure immediately without retrying."},"headers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the HTTP header sent with the AS2 message (for example, `content-disposition`). Header names are case-insensitive."},"value":{"type":"string","description":"Value sent for the header. Supports handlebars expressions rendered against the record."}}},"description":"HTTP headers sent with the AS2 message transmission.\nSupports both standard AS2 headers and custom headers required by trading partner agreements."}}},"Dynamodb":{"type":"object","description":"Configuration for DynamoDB imports. Required when the _connectionId field references a\nDynamoDB connection; must not be included for other connection types. putItem writes a full\nitem document; updateItem modifies named attributes of an existing item identified by its\nkey.","required":["region","method","tableName"],"properties":{"region":{"type":"string","enum":["us-east-1","us-east-2","us-west-1","us-west-2","af-south-1","ap-east-1","ap-south-1","ap-northeast-1","ap-northeast-2","ap-northeast-3","ap-southeast-1","ap-southeast-2","ca-central-1","eu-central-1","eu-west-1","eu-west-2","eu-west-3","eu-south-1","eu-north-1","me-south-1","sa-east-1"],"default":"us-east-1","description":"AWS region hosting the DynamoDB table. Determines the service endpoint used for all\nrequests and must match the region where the table is deployed."},"method":{"type":"string","enum":["putItem","updateItem"],"description":"Write operation performed against the DynamoDB table for each record."},"tableName":{"type":"string","description":"DynamoDB table that receives the imported records.\nMust match an existing table in the target AWS account and region; table names are case-sensitive."},"partitionKey":{"type":"string","description":"Value of the target item's partition key for the updateItem operation; together with\nsortKey (when the table defines one) it identifies the item each record writes to.\nSupports handlebars to reference the incoming record's fields."},"sortKey":{"type":"string","description":"Value of the target item's sort key; supports handlebars. Omit when the table defines only\na partition key."},"itemDocument":{"type":"string","description":"JSON document written to the table for the putItem operation, containing all attribute\nnames and values for the item, including its key attributes. Supports handlebars."},"updateExpression":{"type":"string","description":"DynamoDB update expression naming which attributes to set, add, or remove on the item for\nthe updateItem operation. Only the named attributes change; reference attribute names and\nvalues through the expressionAttributeNames and expressionAttributeValues placeholders."},"conditionExpression":{"type":"string","description":"Condition evaluated against the existing item before the write executes.\nWhen the condition evaluates to false, the operation is aborted and no changes are made."},"expressionAttributeNames":{"type":"string","description":"JSON placeholder-to-name map for attribute names referenced in expressions, for example {\"#N\": \"Name\"}.\nUse it when an attribute name is a reserved word or contains special characters; placeholder keys start with #."},"expressionAttributeValues":{"type":"string","description":"JSON placeholder-to-value map for attribute values referenced in expressions; placeholder\nkeys start with a colon (e.g. \":val\") and each value is a DynamoDB attribute-value object\nsuch as {\"S\": \"text\"}. Supports handlebars."},"ignoreExtract":{"type":"string","description":"Path to the field in the source record used to determine whether the record already exists.\nOnly used when ignoreExisting is set on the import."}},"if":{"properties":{"method":{"const":"updateItem"}},"required":["method"]},"then":{"required":["partitionKey","updateExpression"]}},"Http":{"type":"object","description":"Configuration for HTTP imports.\nRequired whenever the connection referenced by _connectionId has type http, and on\n`NetSuiteHTTPImport` (NetSuite's REST APIs on a `netsuite` connection, where `nsDomainType`\nsupplies the host).","properties":{"sendPostMappedData":{"type":"boolean","default":true,"description":"When true, the request body is the record as reshaped by the import's mapping step. When false, the original pre-mapped record is sent instead, bypassing the mappings for the payload."},"isRest":{"type":"boolean","description":"Internal flag indicating the import was built with the legacy REST form rather than the unified HTTP framework. Set by the form; not configured directly."},"strictHandlebarEvaluation":{"type":"boolean","description":"Intended to make handlebars expressions that reference a missing field raise an error instead of rendering an empty string. In live runs on an HTTP import it does not have that effect on `http.body` templates or mapping expressions — an undefined path still renders empty and the record succeeds with the flag true — and `relativeURI` / `headers[].value` templates already fail on an undefined path regardless of this flag. Not a safeguard against a wrong template root or a misspelled path."},"formType":{"type":"string","enum":["assistant","http","rest","graph_ql","assistant_graphql"],"description":"Determines the UI form and configuration experience for this import."},"type":{"type":"string","enum":["file","records"],"description":"Controls whether the import sends structured record data or raw file content.\nMost HTTP imports use records; use file only when importing raw file content that will be processed downstream."},"requestMediaType":{"type":"string","enum":["xml","json","csv","urlencoded","form-data","octet-stream","plaintext"],"description":"Content type used to serialize the request body sent to the target API.\nMost REST APIs use json."},"_httpConnectorEndpointIds":{"type":"array","items":{"type":"string","format":"objectId"},"description":"The connector endpoints this import calls, one per entry of `relativeURI` / `method` (for\nexample a CREATE and an UPDATE endpoint), shown in the UI as the selected endpoints. On save the\nplatform derives them (with the version and resource ids) from each `relativeURI` / `method`\npair that matches an endpoint of the connector the connection is bound to, and replaces ids\nthat belong to another connector; ids for the connection's own connector are kept as sent, and\nwhen nothing matches, or the connection has no connector, whatever was sent is kept."},"blobFormat":{"type":"string","enum":["utf8","ucs2","utf-16le","ascii","binary","base64","hex"],"description":"Character encoding format for blob/binary data imports.\nOnly relevant when type is \"file\" or when handling binary content.\n"},"batchSize":{"type":"integer","description":"Maximum number of records submitted per HTTP request, which affects throughput and API rate limiting.\nREST services typically send one record per request; raise it only for batch endpoints or RPC/XML services that accept multiple records, consulting the target API documentation for the optimal value.\nAbove 1, the body, relative URI, and header templates render once per request against `batch_of_records` — each entry carrying that record as `record` (or `rows` for a grouped record) — and a root-level `record` or `rows` is undefined.\n\nThe default varies by API."},"successMediaType":{"type":"string","enum":["xml","json","plaintext"],"description":"Media type expected in successful responses from the target API. Most APIs return json."},"requestType":{"type":"array","items":{"type":"string","enum":["CREATE","UPDATE"]},"description":"Operation type each request performs; for composite (upsert) imports, include both values.\nThe array is positionally aligned with the relativeURI and method arrays — each index maps to one operation — and the existingExtract field determines which operation runs at runtime."},"errorMediaType":{"type":"string","enum":["xml","json","plaintext"],"description":"Media type expected in error responses from the target API. Most APIs return json."},"followRedirects":{"type":"boolean","default":true,"description":"When explicitly false, 3xx responses are not followed — the redirect\nresponse itself (status code, `Location` header, body) is what the\nimport records as the result. Omitted or true follows allowed\nredirects (the default behavior)."},"maxRedirects":{"type":"integer","minimum":1,"maximum":10,"description":"Caps how many consecutive 3xx redirects are followed. Only applies\nwhen `followRedirects` is not false; to not follow at all, set\n`followRedirects: false` rather than `maxRedirects: 0`. Decimal\nvalues are rejected on save."},"relativeURI":{"type":"array","items":{"type":"string"},"description":"Relative URI path for each import request, as an array of strings (never an object); values can include Handlebars expressions for dynamic segments.\nTemplates render against the pre-mapped record — the original input before the Import's mapping step — because URI construction usually needs business identifiers that mappings may rename or remove.\nFor composite (upsert) imports, the array is positionally aligned with method and requestType, and existingExtract decides the index used at runtime: the UPDATE index when its field has a value, the CREATE index when it is empty or missing.\nReference the existing record ID in the UPDATE URI with `{{{data.0.fieldName}}}` (triple braces with the data.0 prefix).\nUse separate array elements per operation rather than `{{#if}}` conditionals inside a single URI.\nURI templates render strictly: a path that is undefined on the record — such as `record.<field>` on a grouped record, whose root is `rows` — fails the request instead of rendering empty.\nA field the record lacks, or a `{{lookup.<name>}}` whose lookup found nothing, fails that record before anything is sent, with `handlebars_template_parse_error` (source `application`, classification `parse`): `Failed to generate request url from template: <URL>. Details: \"<field>\" not defined in the model.` That is missing data, not template syntax, and a retry of the same record fails the same way: send records that may lack the destination id through a composite UPDATE/CREATE import (`existingExtract`), or skip them with `ignoreMissing` and `ignoreExtract`.\nA field that holds an empty string renders empty, and the request is sent without the value. A field that holds null renders the text `null` (a URI of `/orders/{{record.id}}` is sent as `/orders/null`), and a field under an object that is null fails the record like a missing one (`\"<field>\" not defined in null`)."},"method":{"type":"array","items":{"type":"string","enum":["GET","PUT","POST","PATCH","DELETE"]},"description":"HTTP method used for each import request.\nFor composite (upsert) imports, the array is positionally aligned with relativeURI and requestType — e.g. `[\"PUT\", \"POST\"]` with `requestType: [\"UPDATE\", \"CREATE\"]` uses PUT for updates and POST for creates."},"_httpConnectorVersionId":{"type":"string","format":"objectId","description":"Pins the import to a version of the published HTTP connector its connection is bound to\n(`connection.http._httpConnectorId`), so the UI renders that connector's endpoint forms instead\nof hand-authored requests. Linked on save together with `_httpConnectorResourceId` and\n`_httpConnectorEndpointIds` — see that field for the rule."},"_httpConnectorResourceId":{"type":"string","format":"objectId","description":"The connector resource (a group of related endpoints, such as Orders) that this import's\nendpoints belong to. Linked on save together with `_httpConnectorEndpointIds` — see that field\nfor the rule."},"_httpConnectorEndpointId":{"type":"string","format":"objectId","readOnly":true,"description":"Not used by imports: values sent on create or update are dropped, and the platform does not\npopulate it. Imports bind connector endpoints per operation through `_httpConnectorEndpointIds`."},"body":{"type":"array","items":{"type":["string","null"]},"description":"Request body template for the import — an array of Handlebars template strings\npositionally aligned with the `method` array (e.g. `[\"{{{record}}}\"]`). A `null`\nentry means no template for the method at that position. Each entry is a string:\nwrite a JSON body as a template string (`[\"{\\\"id\\\": \\\"{{record.id}}\\\"}\"]`), not as\na JSON object, because an object entry does not render as JSON at runtime.\nLeave undefined for standard imports — the Import's mapping step transforms the source record and the mapped data is sent as the request body automatically.\nSet it only when the target API requires XML, SOAP, or a custom structure that mappings cannot produce, or when the user explicitly specifies a request body.\nWhen set, the mapping step still runs first and the template renders against the post-mapped record — use `{{record.<mappedField>}}` to reference mapping outputs.\nHand-templated bodies are harder to maintain and debug than mappings; when in doubt, leave this field undefined.\n\nCustom envelopes can combine static header or account fields with per-message\nfield references inside a loop.\n\nStatic wrappers around the mapped record are appropriate for message-bus class envelopes,\nHMAC signature wrappers, integration batch headers,\nnamed single-record array wrappers, and similar destination-required structures.\n\nBatch templates can wrap an envelope, such as a header, version, or marketplace,\naround serialized records.\n\nUse this pattern when each message needs a small number of fields rather than the whole record."},"ignoreEmptyNodes":{"type":"boolean","description":"When true, fields with empty values are stripped from the request body before it is sent,\nso the target API receives only populated fields. Shown as \"Remove empty fields from HTTP\nrequest body\" in the UI."},"existingExtract":{"type":"string","description":"Field name or JSON path that drives the upsert decision for composite imports with both CREATE and UPDATE in requestType.\nWhen the field has a value in the incoming record, the UPDATE operation's relativeURI and method are used; when it is empty or missing, the CREATE operation runs.\nMust match a field populated by an upstream lookup or response mapping (e.g. a destination system ID like \"shopifyCustomerId\"); omit for single-operation imports."},"existingLookupName":{"type":"string","description":"Name of an entry in `lookups` that resolves whether each record already exists in the destination, driving the composite upsert decision when `existingExtract` alone is insufficient (e.g. the check requires a call to the target system). Paired with composite CREATE/UPDATE imports.\n\nUse `existingLookupName` when the incoming record has no destination ID and\nneeds a per-record probe to discover one;\nuse `existingExtract` when the record already carries the ID.\n\nFor single-operation imports,\nuse `ignoreExtract` or `ignoreLookupName` with `ignoreExisting` or\n`ignoreMissing` to skip records instead of routing them.\n\nThe lookup pattern adds one HTTP request per record at runtime,\nmaking it better suited to one-off migrations and low-volume reconciliation than\nhigh-volume recurring syncs."},"ignoreExtract":{"type":"string","description":"Field name on the incoming record that drives the SKIP-vs-PROCESS\ndecision when ``ignoreExisting`` or ``ignoreMissing`` is set.\n\nPairs with either of the two skip-mode flags:\n\n- With ``ignoreExisting: true`` (CREATE-only imports that should\n  avoid duplicates): if ``ignoreExtract`` has a value on the\n  record, the record is treated as already-existing and is\n  skipped; if empty, the record is created.\n- With ``ignoreMissing: true`` (UPDATE-only imports that should\n  avoid creating new records): if ``ignoreExtract`` has a value,\n  the record is treated as existing and is updated; if empty,\n  the record is skipped.\n\n``ignoreExtract`` checks a field that's already on the record —\nzero per-record HTTP overhead.  Use ``ignoreLookupName`` instead\nwhen the record has no field to inspect and the existence check\nneeds a per-record probe against the destination.\n\n**Mutually exclusive with ``ignoreLookupName``**\n\nSet EXACTLY ONE of ``ignoreExtract`` / ``ignoreLookupName`` when\n``ignoreExisting`` or ``ignoreMissing`` is enabled.\n\n**When not to set this field**\n\nOnly valid for single-operation imports with either\n``ignoreExisting: true`` or ``ignoreMissing: true`` set.\nComposite (upsert) imports — those with both ``CREATE`` and\n``UPDATE`` in ``requestType`` — use ``existingExtract`` /\n``existingLookupName`` instead, because they route records\nbetween two write paths rather than skipping them."},"endPointBodyLimit":{"type":"integer","description":"Maximum size limit for the request body in bytes.\nUsed to enforce API-specific size constraints.\n"},"headers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the HTTP header to include with each import request."},"value":{"type":"string","description":"Value sent for the header. Supports handlebars expressions rendered against the pre-mapped record, with the same data roots as the body (`record` / `rows` / `batch_of_records`, plus `data`); an undefined path fails the request rather than rendering empty."}}},"description":"Custom HTTP headers included with import requests; values can contain Handlebars expressions.\nValue templates render against the pre-mapped record (the original input before the Import's mapping step) — use `{{record.<field>}}` to reference fields as they appear in the upstream source."},"response":{"type":"object","properties":{"resourcePath":{"type":"array","items":{"type":"string"},"description":"Per-operation JSON path to the resource collection in each\nresponse.  Outer-array length MUST match ``requestType``\nlength: 1 entry for single-op imports, N entries for an\nN-operation composite import (duplicate the value when\noperations agree).  See the ``response`` object docs\nabove for the full positional-array contract.\n\nRequired when ``batchSize > 1`` because the platform needs\nto know where the array of result records lives inside the\nresponse envelope.  When the API returns a bare array at\nthe top level, leave this field unset.\n\nEach entry is a DOT-PATH STRING (e.g. ``\"data.results\"``,\n``\"items\"``) — do NOT split paths into segments across\narray elements."},"resourceIdPath":{"type":"array","items":{"type":["string","null"]},"description":"Per-operation JSON path to the unique ID field within each\nrecord in the response.  Outer-array length MUST match\n``requestType`` length — duplicate the value when both\noperations agree (the common case) rather than collapsing\nto a 1-entry array.\n\nEach entry is a DOT-PATH STRING, or null for an operation\nwith no ID path configured. When not specified, the platform\nlooks for standard `id` or `_id` fields automatically. When\nset, a response with no value at the path fails the record\nwith `missing_id`, quoting the response."},"successPath":{"type":"array","items":{"type":"string"},"description":"Per-operation JSON path to a field that indicates whether\nthe API call succeeded.  Outer-array length MUST match\n``requestType`` length — duplicate the value when both\noperations agree rather than collapsing to a 1-entry\narray on a composite import.\n\nUse this when the API returns HTTP 200 for everything and\nsignals success / failure through a body field.  Pairs with\n``successValues`` to define which values at this path mean\nsuccess.\n\nEach entry is a DOT-PATH STRING."},"successValues":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"Per-operation list of values at ``successPath`` that mean\nsuccess.  TWO levels of array nesting:\n\n- Outer array: one entry per ``requestType`` index.\n  Length MUST match ``requestType`` length — duplicate\n  the inner array when both operations agree rather than\n  collapsing to a 1-entry outer array on a composite\n  import.\n- Inner array: the list of acceptable success values for\n  that operation.\n\nAll comparisons are STRING comparisons regardless of the\nactual response value type — the platform stringifies\nnumbers, booleans, etc. before comparing."},"failPath":{"type":"array","items":{"type":"string"},"description":"Per-operation JSON path to a field that signals failure\neven when HTTP returns 200.  Outer-array length MUST\nmatch ``requestType`` length — duplicate when both\noperations agree.  Same array semantics as ``successPath``.\n\nSome APIs use only ``successPath`` (failure = absence of\nsuccess); others publish both an explicit success indicator\nand an explicit failure indicator.  When both are\navailable, set both — the platform evaluates ``successPath``\nfirst and falls through to ``failPath`` only when the\nsuccess check is inconclusive.\n\nWithout ``failValues``, a truthy value at the path fails\nthe response. A path that resolves to an array matches only\nwhen the array has exactly one element."},"failValues":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"Per-operation list of values at ``failPath`` that mean\nfailure.  Same two-level array shape and length contract\nas ``successValues`` — outer length MUST match\n``requestType`` length, duplicate the inner array on\ncomposite imports when both operations agree.\n\nOften used as the inverse of ``successValues`` (e.g.\n``successValues: [[\"true\"]]`` paired with\n``failValues: [[\"false\"]]``)."},"errorPath":{"type":"string","description":"JSON path to the error message inside the response body.\n\n**Note**: this field is NOT a per-operation array — it's a\nsingle string applied to every operation.  Most APIs use\nthe same error-message field shape for both UPDATE and\nCREATE responses, so the platform doesn't expose a per-op\noverride here."},"allowArrayforSuccessPath":{"type":"boolean","description":"When true, allows array values at successPath during success evaluation."},"hasHeader":{"type":"boolean","description":"When true, treats the first record in the response as a header row (for CSV responses)."}},"description":"Configuration for parsing and interpreting HTTP responses returned\nfrom each write request the import issues.\n\n**Critical: positional per-operation arrays**\n\nEvery field inside ``response`` (``resourcePath``, ``resourceIdPath``,\n``successPath``, ``successValues``, ``failPath``, ``failValues``)\nis an ARRAY whose outer length is positionally aligned with\n``requestType`` / ``method`` / ``relativeURI``.  This is the\nSAME positional-alignment contract as those three sibling\narrays — see ``relativeURI`` docs for the full explanation.\n\nEach outer-array index describes how to parse the response from\nthe operation at that same index in ``requestType``:\n\n- **Single-operation import** (``requestType: [\"CREATE\"]``):\n  every response field is a 1-element array.\n  ``successPath: [\"ok\"]`` means \"for the one operation,\n  look at path ``ok`` to detect success\".\n- **Composite upsert** (``requestType: [\"UPDATE\", \"CREATE\"]``):\n  every response field is a 2-element array.  Index 0 parses\n  the UPDATE response, index 1 parses the CREATE response.\n\n**successValues / failValues — array of arrays**\n\n``successValues`` and ``failValues`` carry an extra array level\nbecause each operation can have MULTIPLE acceptable values:\n\n- Outer array: one entry per operation (positional, as above).\n- Inner array: the list of acceptable values for that operation.\n\nExample:\n```json\n\"requestType\": [\"UPDATE\", \"CREATE\"],\n\"successPath\": [\"ok\", \"ok\"],\n\"successValues\": [[\"true\", \"1\"], [\"true\"]]\n```\nMeans: the UPDATE response is successful when ``ok`` is either\n``\"true\"`` or ``\"1\"``; the CREATE response is successful only\nwhen ``ok`` is ``\"true\"``.\n\n**Always duplicate when operations agree (REQUIRED for composite)**\n\nThe outer-array length MUST match ``requestType`` length on\nevery response field.  When the UPDATE and CREATE responses\nof the same connector look the same (which is the common\ncase — most APIs return the same shape from both endpoints),\nDUPLICATE the entry so the array has the same length as\n``requestType``.  Do NOT collapse to a 1-entry array on a\ncomposite import.\n\n```json\n// ✅ Correct — explicit per-operation entries, even when identical\n\"requestType\": [\"UPDATE\", \"CREATE\"],\n\"resourceIdPath\": [\"data.id\", \"data.id\"],\n\"successPath\": [\"success\", \"success\"],\n\"successValues\": [[\"true\"], [\"true\"]]\n```\n\n```json\n// ❌ Wrong — 1-entry array on a composite import\n\"requestType\": [\"UPDATE\", \"CREATE\"],\n\"resourceIdPath\": [\"data.id\"],\n\"successPath\": [\"success\"],\n\"successValues\": [[\"true\"]]\n```\n\nWhy duplicate even when they agree:\n\n- **UI parity.**  The Celigo UI renders one response-handling\n  form per ``requestType`` index.  A 1-entry array on a\n  composite import leaves the second operation's form panel\n  empty, which looks like a half-built config to the user\n  reviewing the saved import — even if it works at runtime.\n- **Symmetric shape rule.**  ``relativeURI`` / ``method`` /\n  ``requestType`` REQUIRE the entry count to match across\n  all three (the runtime decider uses positional alignment).\n  Making ``response.*`` follow the same \"outer length ==\n  requestType length\" rule means there's ONE shape rule to\n  remember, not two.\n- **Unambiguous intent.**  A 2-entry duplicated array\n  documents in the saved config that the developer\n  considered both operations.  A 1-entry array could mean\n  \"both ops use this\" or could mean \"I forgot to fill in\n  the second op.\"  Duplication eliminates the ambiguity.\n\nWhen operations DIFFER (rare — different response shapes\nper endpoint), set distinct values at each index.  Either\nway the outer length always matches ``requestType``.\n\n**Common llm mistake (DO not do THIS)**\n\nDo NOT split a single dot-path into array segments thinking the\nouter array represents path segments:\n\n```json\n// WRONG — the LLM expressed \"data.id\" as path segments\n\"requestType\": [\"UPDATE\", \"CREATE\"],\n\"resourceIdPath\": [\"data\", \"id\"]\n```\n\nThis is interpreted by the platform as: UPDATE response's id is\nat path ``data``, CREATE response's id is at path ``id`` — almost\nnever what the user wants.  The CORRECT shape is the single\ndot-path expressed as ONE string per operation, duplicated to\nmatch ``requestType`` length:\n\n```json\n\"requestType\": [\"UPDATE\", \"CREATE\"],\n\"resourceIdPath\": [\"data.id\", \"data.id\"]\n```"},"_asyncHelperId":{"type":"string","format":"objectId","description":"Reference to an AsyncHelper resource that polls an asynchronous\ndestination API on this import's behalf.\n\nSet this ONLY when the destination API is genuinely asynchronous — it\nacknowledges the submitted payload (HTTP 202, a job ticket) and ingests\nit in the background, so completion must be polled for (e.g. bulk\nimage/file ingestion, large bulk-load endpoints). Most imports are\nsynchronous and need NO async helper; adding one to a synchronous\ndestination just adds polling overhead plus a status and result export\nto maintain. When in doubt, leave it unset.\n\nThe referenced helper bundles the polling config plus a required status\nexport (polled to check progress) and a result export (fetches the\nfinal payload)."},"ignoreLookupName":{"type":"string","description":"Name of an entry in `lookups` used to check whether each record already exists, so `ignoreMissing`/`ignoreExisting` can skip it. Use it instead of `ignoreExtract` when existence must be resolved by a call to the target system rather than read from a field on the incoming record."},"lookups":{"description":"Lookups referenced by `existingLookupName`/`ignoreLookupName` to resolve record existence against the destination system at runtime.","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this lookup. Mapping fields reference it\nvia ``lookupName`` and Handlebars expressions reference it via\n``{{lookup.name}}``. Must be unique within this\nresource's ``lookups`` array.\n"},"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"],"description":"HTTP method used to issue the lookup request. ``GET`` and\n``POST`` are most common. Omit when using a static ``map`` or\n``_lookupCacheId``.\n"},"relativeURI":{"type":"string","description":"HTTP path (relative to the connection's base URL) that\nresolves the lookup. Use Handlebars with triple braces to\ninject values from the incoming record — e.g.\n``/customers?email={{{email}}}`` or\n``/accounts/{{{accountId}}}``.\n"},"body":{"type":"string","description":"Request body template for POST / PUT / PATCH lookup methods, typically\na JSON string with Handlebars placeholders (``{\"email\":\"{{email}}\"}``);\nignored for GET / DELETE. The record's fields sit at the top level:\n``{{email}}`` renders the record's value, while ``{{record.email}}``\nrenders empty.\n"},"headers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"description":"Optional custom headers to attach to the lookup request.\nUseful for endpoints that need a different Accept header or\nan auxiliary auth token separate from the connection-level\nheaders.\n"},"extract":{"type":"string","description":"Path to the lookup value in the HTTP response body, written\nwithout a ``$.`` prefix: ``id``, ``results[0].id``, or ``ids``\nfor an array of ids. A ``$.``-prefixed path, a ``[*]``\nwildcard, or a dot path through an array (``results.id``)\nresolves nothing, so the lookup misses: with\n``allowFailures: false`` the record fails\n(``batch_lookup_evaluation_failed``); otherwise it goes on with\n``default`` or without the value, and nothing errors. A\ncomposite import whose ``existingLookupName`` names such a\nlookup sends every record to CREATE. Mapper 2.0 mapping\nextracts are the opposite: they take the ``$.``.\n"},"_lookupCacheId":{"type":"string","format":"objectId","description":"Optional reference to a LookupCache resource. When set, the\nlookup reads from the cache instead of issuing a live HTTP\nrequest — useful for stable reference data that should be\npre-loaded.\n"},"map":{"type":["object","null"],"description":"Optional static key→value object evaluated BEFORE the HTTP\nrequest. When the incoming value matches a key, the mapped\nvalue is returned without hitting the remote endpoint. Null\non dynamic lookups with no static map.\n"},"default":{"type":["string","null"],"description":"Value written when the lookup finds no match and the miss is\nallowed (``allowFailures`` true, or omitted on a static\n``map``). A save refuses a ``default`` together with an\nexplicit ``allowFailures: false`` (422 \"Lookups with default\nset can not have allowFailures explicitly set to false.\"), so\na lookup either fails a miss (``allowFailures: false``, no\ndefault) or falls back to its default. Stored as null when no\nfallback is configured.\n"},"allowFailures":{"type":["boolean","null"],"description":"When true, a lookup miss uses ``default`` (or leaves the mapped\nfield out when there is none) and the record continues. When\nfalse, a miss fails the record: ``lookup_error`` (\"Could not find\nvalue for static lookup: …\") for a static ``map``,\n``lookupcache_evaluation_failed`` for a cache, and\n``batch_lookup_evaluation_failed`` (source ``internal``, \"<field>:\nThe batch lookup could not be evaluated. Check the value in the\nkey '<value>', and try again.\") for a live request that matches\nnothing or whose ``extract`` resolves nothing. Omitted or null,\na miss doesn't fail the record either: a static lookup writes\nits ``default`` if it has one, and a lookup without one (static,\ncache, or a live request that matches nothing or fails) leaves\nthe mapped field out.\n\nFalse is also the only setting under which an ambiguous answer\nfails the record: two or more values at ``extract`` (an array of\nids or of result objects) fail it with ``lookup_conflict``\n(\"Multiple matches found for lookup.\"). Omitted, null or true,\nthe first value is sent with no error, and an array of result\nobjects is written as the text ``[object Object]``. An indexed\n``extract`` (``ids[0]``) takes the first match whatever this\nfield says, so it hides an ambiguous search rather than fixing\nit.\n"},"useDefaultOnMultipleMatches":{"type":"boolean","description":"Meant to return ``default`` when the response holds more than\none match. On an HTTP import it has not been seen to take\neffect: with ``allowFailures`` omitted or true the first match\nis sent whatever this flag says, with or without a\n``default``, and with ``allowFailures: false`` the record\nfails with ``lookup_conflict``. Two or more matches fail the\nrecord only when ``allowFailures`` is false (see\n``allowFailures``), so make the search match one record\nrather than relying on this flag.\n"}}},"type":"array"}}},"Ftp":{"type":"object","description":"Defines where files are written on an FTP, FTPS, or SFTP server. Required when the\n_connectionId field references an FTP/SFTP connection; must not be included for other\nconnection types. directoryPath selects the target folder and the file naming comes from\nthe import's file configuration.","required":["directoryPath"],"properties":{"_tpConnectorId":{"type":"string","format":"objectId","description":"Trading Partner Connector that supplies partner-specific B2B settings for this import.\nWhen set, the import inherits the connector's pre-configured settings; omit to use only\nthe FTP connection details."},"directoryPath":{"type":"string","description":"Directory on the server where imported files are written, either absolute or relative to\nthe login directory; the FTP user must have write permission on it. Use forward slashes\nregardless of server OS — paths are case-sensitive on UNIX/Linux servers. Supports\nhandlebars templates."},"fileName":{"type":"string","description":"Name of the file written to the server, including its extension; do not include directory\nseparators — the location comes from directoryPath. Supports handlebars placeholders such\nas items-{{timestamp}}.csv to generate a unique name per run. The template is rendered\nagainst the incoming record: a file-transfer import from an FTP source kept the source\nfile's name with `{{record.fileMeta.fileName}}` set on `file.fileName` and this field left\nunset — other source providers expose the name under their own `fileMeta` field."},"inProgressFileName":{"type":"string","description":"Temporary name the file carries while its upload is in progress, preventing other systems\nfrom processing a partially transferred file; it is renamed to its final name once the\nupload completes. Its presence is what the UI shows as \"Use temp file while upload in\nprogress\": set it to turn the temporary name on, omit it to turn it off. Include a\nhandlebars uniqueness token (e.g. {{timestamp}}) when one file is written per flow run."},"backupDirectoryPath":{"type":"string","description":"Directory on the same server where a copy of each written file is retained after a\nsuccessful import; if omitted, no server-side backup is kept. Supports static paths or\nhandlebars templates."}}},"Jdbc":{"type":"object","description":"Configuration for JDBC import operations. Defines how data is written to a database\nvia a JDBC connection.\n\n**Query type determines which fields are required**\n\n| queryType        | Required fields              | Do NOT set        |\n|------------------|------------------------------|-------------------|\n| [\"per_record\"]   | query (array of SQL strings) | bulkInsert        |\n| [\"per_page\"]     | query (array of SQL strings) | bulkInsert        |\n| [\"bulk_insert\"]  | bulkInsert object            | query             |\n| [\"bulk_load\"]    | bulkLoad object              | query             |\n\n**Critical:** query IS AN ARRAY OF STRINGS\nThe query field must be an array of plain strings, NOT a single string and NOT an array of objects.\nCorrect: [\"INSERT INTO users (name) VALUES ('{{{name}}}')\"]\nWrong: \"INSERT INTO users ...\"\nWrong: [{\"query\": \"INSERT INTO users ...\"}]","required":["queryType"],"properties":{"query":{"type":"array","items":{"type":"string"},"description":"Array of SQL query strings to execute. REQUIRED when queryType is [\"per_record\"] or [\"per_page\"].\n\nEach element is a complete SQL statement as a plain string. Typically contains a single query.\n\nUse Handlebars {{fieldName}} syntax to inject values from incoming records.\n\n**Format — array of strings**\n- CORRECT: [\"INSERT INTO users (name, email) VALUES ('{{{name}}}', '{{{email}}}')\"]\n- WRONG: \"INSERT INTO users ...\"  (not an array)\n- WRONG: [{\"query\": \"INSERT INTO users ...\"}]  (objects are invalid — causes Cast error)\n\n**Examples**\n- INSERT: [\"INSERT INTO users (name, email) VALUES ('{{{name}}}', '{{{email}}}')\"]\n- UPDATE: [\"UPDATE inventory SET qty = {{{quantity}}} WHERE sku = '{{{sku}}}'\"]\n- MERGE/UPSERT: [\"MERGE INTO target USING (SELECT CAST(? AS VARCHAR) AS email) AS src ON target.email = src.email WHEN MATCHED THEN UPDATE SET name = ? WHEN NOT MATCHED THEN INSERT (name, email) VALUES (?, ?)\"]\n\n**String vs numeric values in handlebars**\n- Use triple braces so the raw value is emitted; double braces emit the value already single-quoted, so wrapping them in quotes double-quotes it.\n- Strings: supply the quotes yourself — '{{{name}}}'\n- Numbers: no quotes — {{{quantity}}}"},"queryType":{"type":"array","items":{"type":"string","enum":["bulk_insert","per_record","per_page","bulk_load"]},"description":"Execution strategy for the SQL operation. REQUIRED. Must be an array with one value.\n\n**Decision tree**\n\n1. If UPDATE or UPSERT/MERGE → [\"per_record\"] (set query field)\n2. If INSERT with \"ignore existing\" / \"skip duplicates\" / match logic → [\"per_record\"] (set query field)\n3. If pure INSERT with no duplicate checking → [\"bulk_insert\"] (set bulkInsert object)\n4. If high-volume bulk load → [\"bulk_load\"] (set bulkLoad object)\n\n**Critical relationship to other fields**\n| queryType        | REQUIRES              | DO NOT SET   |\n|------------------|-----------------------|--------------|\n| [\"per_record\"]   | query (array)         | bulkInsert   |\n| [\"per_page\"]     | query (array)         | bulkInsert   |\n| [\"bulk_insert\"]  | bulkInsert object     | query        |\n| [\"bulk_load\"]    | bulkLoad object       | query        |\n\n**Examples**\n- Per-record upsert: [\"per_record\"]\n- Bulk insert: [\"bulk_insert\"]\n- Bulk load: [\"bulk_load\"]\n"},"bulkInsert":{"type":"object","description":"Bulk insert configuration. REQUIRED when queryType is [\"bulk_insert\"]. DO NOT SET when queryType is [\"per_record\"].\n\nEnables efficient batch insertion of records into a database table without per-record SQL.\n","properties":{"tableName":{"type":"string","description":"Target database table name for bulk insert. REQUIRED.\nCan include schema qualifiers (e.g., \"schema.tableName\").\n"},"batchSize":{"type":"string","description":"Number of records per batch during bulk insert.\nLarger values improve throughput but use more memory.\nCommon values: \"1000\", \"5000\".\n"}}},"bulkLoad":{"type":"object","description":"Bulk load configuration. REQUIRED when queryType is [\"bulk_load\"]. Uses database-native bulk loading for maximum throughput.\n","properties":{"tableName":{"type":"string","description":"Target database table name for bulk load. REQUIRED.\nCan include schema qualifiers (e.g., \"schema.tableName\").\n"},"primaryKeys":{"type":["array","null"],"items":{"type":"string"},"description":"Primary key column names for upsert/merge during bulk load.\nWhen set, existing rows matching these keys are updated; non-matching rows are inserted.\nExample: [\"id\"] or [\"order_id\", \"product_id\"] for composite keys.\n"},"overrideMergeOrInsertQuery":{"type":"boolean","description":"When true, a custom merge/insert query replaces the auto-generated statement for the\nbulk load, enabling ignore-existing logic or conditional updates."}}},"lookups":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this lookup. Mapping fields reference it\nvia `lookupName` and Handlebars expressions reference it via\n`{{lookup \"name\" value}}`. Must be unique within this\nresource's `lookups` array."},"query":{"type":"string","description":"SQL query for the lookup (e.g., \"SELECT id FROM users WHERE email = '{{{email}}}'\")."},"extract":{"type":"string","description":"JSONPath-style expression that selects the value from the\nquery result row (e.g. `id`, `details.price`). Omit when\nthe query returns a single scalar."},"_lookupCacheId":{"type":"string","format":"objectId","description":"Optional reference to a LookupCache resource. When set, the\nlookup reads from the cache instead of issuing a live SQL\nquery — useful for stable reference data that should be\npre-loaded."},"allowFailures":{"type":"boolean","description":"When true, a lookup miss (no matching row and no `default`)\nresolves to `null` and the import continues. When false\n(default), a miss halts the record."},"map":{"type":"object","description":"Optional static key→value object evaluated BEFORE the SQL\nquery. When the incoming value matches a key, the mapped\nvalue is returned without querying the database."},"default":{"type":"string","description":"Value returned when the query matches no rows. When omitted\nand `allowFailures` is false, an unmatched lookup halts the\nrecord."}}},"description":"Lookup definitions executed against the JDBC connection. Each entry\nruns a `SELECT` and extracts a value used by field mappings\n(referenced via `lookupName`) or Handlebars expressions\n(referenced via `{{lookup \"name\" value}}`)."}}},"Mongodb":{"type":"object","description":"Configuration for MongoDB imports. Contains only the MongoDB-specific properties; import-level fields such as ignoreExisting, ignoreMissing, name, and description are set on the parent import, not here.","required":["method","collection"],"properties":{"method":{"type":"string","enum":["insertMany","updateOne"],"description":"Write operation performed against the MongoDB collection for each record."},"collection":{"type":"string","description":"MongoDB collection that receives the imported records.\nCollection names are case-sensitive and must not start with the reserved \"system.\" prefix."},"filter":{"type":"string","description":"MongoDB query filter that selects the document each updateOne applies to; required when\nmethod is updateOne. Supports standard MongoDB query operators and dot notation, and\nhandlebars placeholders to reference the incoming record's fields."},"document":{"type":"string","description":"Content of the document written to the collection (used when method is insertMany), as a JSON string that can include nested documents and arrays and handlebars placeholders."},"update":{"type":"string","description":"Modifications applied to matching documents (used when method is updateOne), using MongoDB update operators such as $set, $inc, or $push.\nA complete document without operators replaces the matched document entirely."},"upsert":{"type":"boolean","description":"When true, inserts a new document built from the update criteria if no existing document matches the filter.\nWhen false, records with no matching document are skipped and nothing is inserted."},"ignoreExtract":{"type":"string","description":"Path to the field in the source record that identifies existing records. When that field has a value, the record is treated as existing and skipped.\nOnly used when the import-level ignoreExisting flag is true; do not set it otherwise.\nEnclose field names containing special characters in square brackets (for example, [vendor-code]), and reference array items with an index, such as items[0].id."},"ignoreLookupFilter":{"type":"string","description":"JSON-stringified MongoDB query filter used to find existing documents in the target collection when the import-level ignoreExisting flag is true.\nIf the query matches a document, the incoming record is treated as existing and skipped.\nUse Handlebars placeholders to reference values from the incoming record, for example \"{\\\"email\\\":\\\"{{email}}\\\"}\".\nUnlike ignoreExtract, which only checks whether a field on the incoming record has a value, this filter queries the MongoDB collection itself."}},"if":{"properties":{"method":{"const":"updateOne"}},"required":["method"]},"then":{"required":["filter"]}},"NetSuite-2":{"type":"object","description":"Configuration for NetSuite imports (legacy adaptor; use netsuite_da / NetSuiteDistributedImport for SuiteApp 2.0).","properties":{"lookups":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique name for this lookup, referenced from mappings and handlebars templates with `{{lookup 'name' value}}`."},"recordType":{"type":"string","description":"NetSuite record type searched by a dynamic lookup (for example, `customer`, `salesOrder`). Set with `searchField` or `expression`; omit for a static `map` lookup."},"searchField":{"type":"string","description":"NetSuite field the dynamic lookup searches on (for example, `externalid`). Must be a searchable field on `recordType`. Use this or `expression`."},"expression":{"type":"string","description":"NetSuite search expression evaluated at runtime to select matching records (for example, `[\"recipient\",\"is\",\"123\"]`). Use this or `searchField`."},"resultField":{"type":"string","description":"Field on the matched NetSuite record whose value the lookup returns (for example, `internalid`).\nSupports dot notation for nested fields."},"map":{"type":["object","null"],"description":"Static lookup table with input values as keys and their corresponding output values. Use instead of a NetSuite search for fixed value translations. The platform stores `null` here on search-driven lookups."},"default":{"type":["string","null"],"description":"Fallback value returned when the lookup finds no match. Used together with `allowFailures`. Stored as `null` when not configured."},"allowFailures":{"type":"boolean","description":"When true, an unmatched lookup falls back to `default` and processing continues; when false, an unmatched lookup fails the record."},"_id":{"type":"object","description":"Server-assigned unique identifier for this lookup entry.","readOnly":true}}},"description":"Lookup definitions used to resolve reference values during the import, using either a static `map` or a dynamic NetSuite record search."},"operation":{"type":"string","enum":["add","update","addupdate","attach","detach","delete"],"description":"Operation to perform on NetSuite records, as a plain string (the API lowercases the value).\nFor `update`, `addupdate`, and `delete`, also set internalIdLookup so existing records can be found; for `add` with ignoreExisting, set internalIdLookup to check for duplicates before creating.\nDefault to `addupdate` when the intent is \"sync\", \"upsert\", or \"create or update\"; use `add` for plain \"create\" or \"insert\".\nDo not wrap the value in an object like `{\"type\": \"addupdate\"}` — that fails validation."},"isFileProvider":{"type":"boolean","description":"When true, enables file operations against NetSuite — browsing, uploading, downloading, updating, and deleting files.\nWhen false or omitted, file-related operations are unavailable through this import."},"customFieldMetadata":{"type":"object","description":"Metadata describing the custom fields defined on the target NetSuite record type, used to validate and process custom field data during the import."},"recordType":{"type":"string","description":"NetSuite record type this import writes to (e.g. customer, salesOrder, invoice).\nUse the exact NetSuite internal record type identifier; both standard and custom record types are supported."},"recordTypeId":{"type":"string","description":"The unique identifier specifying the record type within NetSuite. Determines the applicable schema, fields, validation rules, and processing logic. Use exact NetSuite record type identifiers such as standard types or custom record IDs."},"retryUpdateAsAdd":{"type":"boolean","description":"When true, an update that fails because the target record does not exist is automatically retried as an add.\nWhen false or unset, update failures error immediately without retrying.\nMay create duplicate records if the update failed for a reason other than a missing record."},"batchSize":{"type":"number","description":"Number of records sent to NetSuite per API call.\nLarger batches reduce the number of calls but increase memory use and timeout risk per call."},"internalIdLookup":{"type":"object","properties":{"extract":{"type":"string","description":"Path used to extract the value for the internal ID lookup."},"searchField":{"type":"string","description":"The NetSuite record field used as the key attribute for the internal ID lookup search. Should be a unique or indexed field to ensure accurate matches. Must correspond to a valid, searchable field on the target record type."},"expression":{"type":"string","description":"NetSuite search expression that filters which records match the lookup.\nSupports logical operators (AND, OR, NOT), comparison operators, and nested conditions, evaluated at runtime against current data."}},"description":"Configuration for locating existing NetSuite records by internal ID.\nRequired when operation is `update`, `addupdate`, or `delete`; also used with `add` plus ignoreExisting to check for duplicates."},"preferences":{"type":"object","properties":{"ignoreReadOnlyFields":{"type":"boolean","description":"When true, silently skips read-only fields during update operations instead of raising errors."},"warningAsError":{"type":"boolean","description":"When true, treats NetSuite warnings as errors, causing the operation to fail immediately."},"skipCustomMetadataRequests":{"type":"boolean","description":"When true, skips custom metadata requests during NetSuite API operations to improve performance."}},"description":"Preferences that control how NetSuite handles the import operation."},"file":{"type":"object","properties":{"name":{"type":"string","description":"Filename for the file in the NetSuite File Cabinet, without any folder or path information.\nMust be unique within its folder; include the file extension (e.g. .pdf, .csv)."},"fileType":{"type":"string","description":"NetSuite file type that governs how the file is processed, stored, and displayed.\nMust match the actual content format of the file."},"folder":{"type":"string","description":"Folder in the NetSuite File Cabinet where the file is stored, as a numeric folder ID or a folder path.\nUpdating this on an existing file moves the file to the specified folder."},"folderInternalId":{"type":"string","description":"Internal ID of the File Cabinet folder where the file is stored.\nMust reference an existing folder; changing it moves the file to a different folder."},"internalId":{"type":"string","description":"NetSuite internal ID of an existing file, used to target it for retrieval, updates, or deletion.\nAssigned by NetSuite when the file is created and cannot be changed."},"backupFolderInternalId":{"type":"string","description":"Internal ID of the File Cabinet folder where backup files are stored."}},"description":"Configuration for the file to upload to or update in the NetSuite File Cabinet."}},"if":{"not":{"propertyNames":{"enum":["preferences","lookups"]}}},"then":{"required":["operation"],"if":{"properties":{"isFileProvider":{"const":true}},"required":["isFileProvider"]},"else":{"required":["recordType"]}}},"NetsuiteDistributed":{"type":"object","description":"Configuration for NetSuite Distributed (SuiteApp 2.0) import operations — the primary sub-schema for the NetSuiteDistributedImport adaptorType. The API field name is \"netsuite_da\".\noperation and recordType are required. The server may attach empty bookkeeping stubs of\nthis object (empty lookups/mapping, or missingOrCorruptedDAConfig) to imports of other\nadaptor types; stubs omit these fields. Set internalIdLookup so existing records\ncan be found when operation is update, addupdate, or delete.","properties":{"operation":{"type":"string","enum":["add","update","addupdate","attach","detach","delete"],"description":"Operation to perform on the target NetSuite record, as a plain string. Required.\nFor `update`, `addupdate`, and `delete`, also set internalIdLookup so existing records can be found."},"recordType":{"type":"string","description":"NetSuite record type to import into (e.g. \"customer\", \"salesorder\", \"customrecord_myrecord\" for custom record types). Required.\nMust match a valid NetSuite record type identifier."},"recordIdentifier":{"type":"string","description":"Custom record identifier, used to identify the specific record type when\nimporting into custom record types.\n"},"restletVersion":{"type":"string","enum":["suitebundle","suiteapp1.0","suiteapp2.0"],"description":"Version of the NetSuite RESTlet to use, as a plain string.\nDefaults to \"suiteapp2.0\" when useSS2Restlets is true, \"suitebundle\" otherwise; rarely needs to be set explicitly for modern integrations.\nThe version is fixed when the step is created — the Advanced selector is disabled on existing steps, and migrating an existing step to a different version means recreating or cloning it."},"useSS2Restlets":{"type":"boolean","description":"When true, uses SuiteScript 2.0 RESTlets and restletVersion defaults to \"suiteapp2.0\".\nDefaults to true for modern integrations.\nSet at step creation together with restletVersion — the same creation-time-only constraint applies (see restletVersion)."},"missingOrCorruptedDAConfig":{"type":"boolean","description":"When true, the Distributed Adaptor configuration is missing or corrupted.\nSet by the system — do not set manually."},"batchSize":{"type":"number","description":"Number of records to process per batch. Controls how many records are sent\nto NetSuite in a single API call. Typical values: 50-200.\n"},"internalIdLookup":{"type":"object","description":"Configuration for looking up existing NetSuite records to match against incoming data.\nRequired when operation is \"update\", \"addupdate\", or \"delete\".","properties":{"extract":{"type":"string","description":"Path in the source record to extract the lookup value from (e.g. \"internalId\" or \"externalId\")."},"searchField":{"type":"string","description":"NetSuite field to search against (e.g. \"externalId\", \"email\", \"name\", \"tranId\")."},"operator":{"type":"string","description":"Comparison operator for the lookup (e.g. \"is\", \"contains\", \"startswith\")."},"expression":{"type":"string","description":"NetSuite search expression for complex lookup conditions, used for multi-field or conditional lookups."}}},"hooks":{"type":"object","description":"Script hooks for custom processing at different stages of the import.\nEach hook references a SuiteScript file and function.\n","properties":{"preMap":{"type":"object","description":"Runs before field mapping is applied.","properties":{"fileInternalId":{"type":["string","null"],"description":"NetSuite internal ID of the SuiteScript file. Null on hook stubs where no SuiteScript file has been selected."},"function":{"type":"string","description":"Name of the function to execute."},"configuration":{"type":["object","null"],"description":"Configuration object passed to the hook function. Null when the hook carries no static parameters."}}},"postMap":{"type":"object","description":"Runs after field mapping, before submission to NetSuite.","properties":{"fileInternalId":{"type":["string","null"],"description":"NetSuite internal ID of the SuiteScript file. Null on hook stubs where no SuiteScript file has been selected."},"function":{"type":"string","description":"Name of the function to execute."},"configuration":{"type":["object","null"],"description":"Configuration object passed to the hook function. Null when the hook carries no static parameters."}}},"postSubmit":{"type":"object","description":"Runs after the record is submitted to NetSuite.","properties":{"fileInternalId":{"type":["string","null"],"description":"NetSuite internal ID of the SuiteScript file. Null on hook stubs where no SuiteScript file has been selected."},"function":{"type":"string","description":"Name of the function to execute."},"configuration":{"type":["object","null"],"description":"Configuration object passed to the hook function. Null when the hook carries no static parameters."}}}}},"mapping":{"type":"object","description":"NetSuite-specific Mapper 1.0 field mappings (the \"DA\" dialect).\nUsed by `NetSuiteDistributedImport` exclusively — NetSuite's\nDistributed Adaptor cannot consume generic v1 mappings or the\nMapper 2.0 shape because it needs sublist, subrecord, and\nconditional lookup semantics the generic schemas don't model.\n\nField-level shape: `fields` for body columns, `lists` for\nsublists / line-item arrays.","properties":{"fields":{"type":"array","items":{"type":"object","properties":{"extract":{"type":["string","null"],"description":"Path in the source record to extract the value from.\nUse dot notation for nested fields (e.g., \"address.city\").\nThe platform stores `null` here on fields driven by\n`hardCodedValue` instead of an extract path.\n"},"generate":{"type":"string","description":"The NetSuite field ID to write the value to (e.g., \"companyname\", \"email\", \"subsidiary\").\n"},"hardCodedValue":{"type":"string","description":"A static value to always use instead of extracting from source data.\nMutually exclusive with extract.\n"},"lookupName":{"type":"string","description":"Reference to a lookup defined in netsuite_da.lookups by name."},"dataType":{"type":"string","description":"Data type hint for the field value (e.g., \"string\", \"number\", \"date\", \"boolean\").\n"},"internalId":{"type":"boolean","description":"When true, the value is a NetSuite internal ID reference."},"immutable":{"type":"boolean","description":"When true, this field is only set on record creation, not on updates."},"discardIfEmpty":{"type":"boolean","description":"When true, skip this field mapping if the extracted value is empty."},"extractDateFormat":{"type":"string","description":"Date format of the extracted value (e.g., \"MM/DD/YYYY\", \"ISO8601\").\nUsed to parse date strings from source data.\n"},"extractDateTimezone":{"type":["string","null"],"description":"Timezone of the extracted date value (e.g., \"America/New_York\"). Stored as `null` when not configured."},"subRecordMapping":{"type":"object","description":"Nested Mapper 1.0 mapping for a NetSuite subrecord\nembedded in this field.  A subrecord is a child\nrecord that only exists inside a parent (e.g. the\nInventory Detail on a serialized-item line of a\nsales order); NetSuite reaches it through the\nparent.\n\n**Recursive structure.**  The nested `mapping`\nobject has the same `{fields, lists}` shape as the\ntop-level `netsuite_da.mapping`.  Recursion is\nallowed but rare.","properties":{"recordType":{"type":"string","description":"NetSuite's internal record-type id of the\nSUBRECORD (not the parent).  E.g.\n`inventorydetail`, `itempricing`,\n`mainaddress`.\n"},"jsonPath":{"type":"string","description":"JSONPath into the source record that selects the\ndata feeding this subrecord.  Semantics depend\non placement:\n* Line-level (on `lists[].fields[].`\n  `subRecordMapping`) — often `$` to reuse\n  the enclosing sublist line's row, or a path\n  into a nested array on that row.\n* Body-level (on `fields[].subRecordMapping`)\n  — a path into the source record root.\nDefaults to `$`.\n"},"mapping":{"type":"object","description":"Nested `{fields, lists}` mapping for the\nsubrecord.  Identical shape to the top-level\n`netsuite_da.mapping`.  Subrecords often have\nempty `fields` and only a sublist (e.g.\n`inventorydetail` → `inventoryassignment`).\n"},"lookups":{"type":"array","description":"Subrecord-scoped lookups.  Same shape as the\ntop-level `netsuite_da.lookups`.  Fields inside\nthis `subRecordMapping.mapping` reference them\nvia `lookupName`.  Often `[]`.\n"},"referenceFieldId":{"type":"string","description":"Secondary reference-field identifier used on a\nsmall fraction of subrecord blocks.  Exact\nsemantics are not publicly\ndocumented by Celigo — match the value on\nexisting resources when present; do NOT\nsynthesize one.  Leave unset unless a template\nclearly requires it.\n"}}},"conditional":{"type":"object","description":"Conditional logic for when to apply this field mapping\n(\"Only perform mapping when\" in the UI). Lookup-based\nconditions evaluate the lookup named by `lookupName`,\nwhich must be defined on this import.\n","properties":{"lookupName":{"type":"string","description":"Lookup to evaluate for the condition. Must exactly match the\n`name` of a lookup defined on this import."},"when":{"type":"string","enum":["record_created","record_updated","extract_not_empty","lookup_not_empty","lookup_empty","ignore_if_set"],"description":"Condition that controls when this field mapping is applied."}}}}},"description":"Body-level field mappings. Each entry maps a source field to a NetSuite body field.\n"},"lists":{"type":"array","items":{"type":"object","properties":{"generate":{"type":"string","description":"The NetSuite sublist ID (e.g., \"item\" for sales order line items,\n\"addressbook\" for address sublists).\n"},"jsonPath":{"type":"string","description":"LEGACY — do not set. Nominally the JSON path to the array of sublist records, but NetSuite DA ignores a top-level jsonPath here."},"fields":{"type":"array","items":{"type":"object","properties":{"extract":{"type":["string","null"],"description":"Path from the source record root that produces\nthe value for this sublist column. Paths are\nalways anchored at the source record root —\nnever relative to a pre-iterated row. The\nplatform stores `null` here on fields driven by\n`hardCodedValue` instead of an extract path."},"generate":{"type":"string","description":"NetSuite sublist field ID to write to."},"hardCodedValue":{"type":"string","description":"Static value for this sublist field."},"lookupName":{"type":"string","description":"Reference to a lookup by name."},"dataType":{"type":"string","description":"Data type hint for the field value."},"internalId":{"type":"boolean","description":"When true, the value is a NetSuite internal ID reference."},"isKey":{"type":"boolean","description":"When true, this field is a key field for matching existing sublist lines."},"immutable":{"type":"boolean","description":"When true, this field is only set on record creation, not on updates."},"discardIfEmpty":{"type":"boolean","description":"When true, skips this field mapping if the extracted value is empty."},"extractDateFormat":{"type":"string","description":"Date format of the extracted value."},"extractDateTimezone":{"type":["string","null"],"description":"Timezone of the extracted date value. Stored as `null` when not configured."},"subRecordMapping":{"type":"object","description":"Nested Mapper 1.0 mapping for a NetSuite\nsubrecord embedded in this sublist-field.  A\nsubrecord is a child record that only exists\ninside a parent (e.g. the Inventory Detail on\na serialized-item line of a sales order);\nNetSuite reaches it through the parent.\n\n**Recursive structure.**  The nested\n`mapping` object has the same\n`{fields, lists}` shape as the top-level\n`netsuite_da.mapping`.  Recursion is allowed\nbut rare.","properties":{"recordType":{"type":"string","description":"NetSuite's internal record-type id of the\nSUBRECORD (not the parent).  E.g.\n`inventorydetail`, `itempricing`,\n`componentinventorydetail`.\n"},"jsonPath":{"type":"string","description":"JSONPath (relative to the current sublist\nline) to the rows that populate the\nsubrecord.  Typically `$` when the\nsubrecord data is carried on the same\nline item (iteration over the sublist\nhas already selected the line).\nDefaults to `$`.\n"},"mapping":{"type":"object","description":"Nested `{fields, lists}` mapping for the\nsubrecord.  Identical shape to the\ntop-level `netsuite_da.mapping`.\nSubrecords often have empty `fields` and\nonly a sublist (e.g. `inventorydetail` →\n`inventoryassignment`).\n"},"lookups":{"type":"array","description":"Subrecord-scoped lookups.  Same shape as\nthe top-level `netsuite_da.lookups`.\nFields inside this\n`subRecordMapping.mapping` reference\nthem via `lookupName`.  Often `[]`.\n"},"referenceFieldId":{"type":"string","description":"Secondary reference-field identifier used\non a small fraction of subrecord blocks.\nExact semantics are not publicly\ndocumented by Celigo — match\nthe value on existing resources when\npresent; do NOT synthesize one.  Leave\nunset unless a template clearly requires\nit.\n"}}},"conditional":{"type":"object","description":"Conditional logic for when to apply this sublist field mapping\n(\"Only perform mapping when\" in the UI). Lookup-based conditions\nevaluate the lookup named by `lookupName`, which must be defined\non this import.","properties":{"lookupName":{"type":"string","description":"Lookup to evaluate for the condition. Must exactly match the\n`name` of a lookup defined on this import."},"when":{"type":"string","description":"Condition that controls when this field mapping is applied.","enum":["record_created","record_updated","extract_not_empty","lookup_not_empty","lookup_empty","ignore_if_set"]}}}}},"description":"Field mappings for each column in the sublist."}}},"description":"Sublist (line-item) mappings. Each entry maps source data to a NetSuite sublist.\n"}}},"lookups":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this lookup. Mapping fields reference it\nvia `lookupName` and Handlebars expressions reference it via\n`{{lookup \"name\" value}}`. Must be unique within this\nresource's `lookups` array."},"recordType":{"type":"string","description":"NetSuite record type to search. Use the internal record-type\nid (lowercase, underscored), e.g. `customer`, `item`,\n`salesorder`, `customlist_xxx`."},"searchField":{"type":"string","description":"NetSuite field on `recordType` to match against. Use the\ninternal field id — e.g. `email`, `externalid`,\n`internalid`, `entityid`, `custitem_xxx`."},"resultField":{"type":"string","description":"NetSuite field whose value is returned when a record matches.\nTypically `internalid` when the lookup resolves to a record\nreference, or any queryable scalar field."},"expression":{"type":"string","description":"Optional advanced-search expression (NetSuite saved-search\nDSL) for multi-criterion lookups. When present, supersedes\n`searchField` / `operator` — those fields are ignored.\nHandlebars inside the expression is rendered against the\nwhole source record before the search runs; a lookup on a\nsublist row reaches the current line through\n`<array>[*].<field>` (for example `line_items[*].sku`)."},"operator":{"type":"string","description":"Comparison operator for the `searchField` match. Common\nvalues: `is`, `contains`, `startswith`, `anyof`,\n`equalto`, `greaterthan`."},"includeInactive":{"type":"boolean","description":"When true, includes inactive records in lookup results."},"useDefaultOnMultipleMatches":{"type":"boolean","description":"When true and `allowFailures` is also true, a search that\nmatches more than one record resolves to `default` instead\nof failing the record. Has no effect while `allowFailures`\nis false — an ambiguous match then fails the record whatever\nthis flag says."},"allowFailures":{"type":["boolean","null"],"description":"When true, a lookup miss (no matching record and no\n`default`) resolves to `null` and the import continues, and\na search matching more than one record resolves to one of\nthem unless `useDefaultOnMultipleMatches` routes it to\n`default`. When false (default), a miss and an ambiguous\nmatch both halt the record. May be stored as null (treated\nas unset)."},"map":{"type":["object","null"],"description":"Optional static key→value object evaluated BEFORE the\nNetSuite search. When the incoming value matches a key, the\nmapped value is returned without hitting NetSuite. Null on\ndynamic (search-based) lookups with no static map."},"default":{"type":["string","null"],"description":"Value returned when the search matches no records. When\nomitted and `allowFailures` is false, an unmatched lookup\nhalts the record. May be stored as null (no default)."}}},"description":"Lookup definitions that resolve reference values from NetSuite.\nReferenced by name from field mappings via `lookupName` and from\nHandlebars expressions via `{{lookup \"name\" value}}`."},"rawOverride":{"type":"object","description":"Raw override object for advanced use cases. When useRawOverride is true,\nthis object is sent directly to the NetSuite API, bypassing normal mapping.\n"},"useRawOverride":{"type":"boolean","description":"When true, uses rawOverride instead of the normal mapping configuration.\n"},"isMigrated":{"type":"boolean","description":"When true, this import was migrated from a legacy format. Set by the system."},"retryUpdateAsAdd":{"type":"boolean","description":"When true, if an update fails because the record doesn't exist, automatically\nretry as an add operation. Useful for initial syncs where records may not exist yet.\n"},"customFieldMetadata":{"type":"object","description":"Metadata about custom fields on the target record type.\nPopulated by the system from NetSuite metadata — do not set manually.\n"},"file":{"type":"object","description":"File cabinet configuration for file-based imports into NetSuite.\n","properties":{"name":{"type":"string","description":"Filename for the file in NetSuite File Cabinet."},"fileType":{"type":"string","description":"NetSuite file type (e.g., \"PDF\", \"CSV\", \"PLAINTEXT\", \"EXCEL\", \"XML\").\n"},"folder":{"type":"string","description":"Folder path or name in the NetSuite File Cabinet."},"folderInternalId":{"type":"string","description":"Internal ID of the target folder in NetSuite File Cabinet."},"internalId":{"type":"string","description":"Internal ID of an existing file to update."},"backupFolderInternalId":{"type":"string","description":"Internal ID of a backup folder for file versioning."}}},"isFileProvider":{"type":"boolean","description":"Whether this import handles files in the NetSuite File Cabinet.\n"},"preferences":{"type":"object","properties":{"ignoreReadOnlyFields":{"type":"boolean","description":"When true, silently skip read-only fields instead of raising errors."},"warningAsError":{"type":"boolean","description":"When true, treat NetSuite warnings as errors that stop the import."},"skipCustomMetadataRequests":{"type":"boolean","description":"When true, skip fetching custom field metadata to improve performance."}},"description":"Import behavior preferences that control how NetSuite handles the import operation.\n"},"recordTypeId":{"type":"string","description":"The internal record type ID. Used for custom record types in NetSuite where\nthe numeric ID is needed in addition to the recordType string.\n"}},"if":{"not":{"propertyNames":{"enum":["lookups","mapping","missingOrCorruptedDAConfig"]}}},"then":{"required":["operation","recordType"],"if":{"properties":{"operation":{"enum":["update","addupdate","delete"]}},"required":["operation"]},"then":{"required":["internalIdLookup"]}}},"Rdbms":{"type":"object","description":"Configuration for RDBMS imports into SQL Server, MySQL, PostgreSQL, Snowflake, Oracle, MariaDB, and other relational databases.\nqueryType determines which companion field is required: per_record, per_page, and first_page use the query field; bulk_insert uses the bulkInsert object; bulk_load uses the bulkLoad object — never set query and bulkInsert together.\n(The server itself stores an empty `query: []` next to bulk_insert/bulk_load configs and may leave an empty `bulkInsert: {}` stub next to others — empty companions are normal in responses; only a populated conflicting companion is invalid.)\nThe query field is an array of plain SQL strings, not a single string and not an array of objects.","properties":{"lookups":{"$ref":"#/components/schemas/Lookups","description":"Lookup definitions executed against the RDBMS connection. Each\nentry runs a `SELECT` and extracts a value used by field mappings\n(referenced via `lookupName`) or Handlebars expressions\n(referenced via `{{lookup \"name\" value}}`).\n\n**Mirror invariant**\nThe Celigo platform persists lookups in TWO locations on an RDBMS\nimport: top-level `lookups` and `rdbms.lookups`. Both arrays\nmust hold byte-for-byte identical content, so write both on every\nsave.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this lookup. Mapping fields reference it\nvia `lookupName` and Handlebars expressions reference it via\n`{{lookup \"name\" value}}`. Must be unique within this\nresource's `lookups` array.\n"},"query":{"type":"string","description":"SQL `SELECT` statement that resolves the lookup value.\nTypically returns a single row with a single column; use\n`extract` to pick the column when the query returns multiple.\nInject values from the incoming record with Handlebars and the\n`record.` prefix — double braces for strings, which the platform\nquotes, escaping their apostrophes, and triple braces for numbers:\n`SELECT id FROM users WHERE email = {{record.email}}`. On Snowflake\nthe quoting leaves backslashes, which Snowflake reads as escape\nsequences (see `query`).\n"},"extract":{"type":"string","description":"JSONPath-style expression that selects the value from the query\nresult row (e.g. `id`, `details.price`). Omit when the\nquery returns a single scalar — the first column is used\nautomatically.\n"},"_lookupCacheId":{"type":"string","format":"objectId","description":"Optional reference to a LookupCache resource. When set, the\nlookup reads from the cache instead of issuing a live SQL\nquery — useful for stable reference data that should be\npre-loaded.\n"},"map":{"type":"object","description":"Optional static key→value object evaluated BEFORE the SQL query.\nWhen the incoming value matches a key, the mapped value is\nreturned without querying the database. Useful for small\ncontrolled enumerations.\n"},"default":{"type":"string","description":"Value returned when the query matches no rows. When omitted and\n`allowFailures` is false, an unmatched lookup halts the\nrecord.\n"},"allowFailures":{"type":"boolean","description":"When true, a lookup miss (no matching row and no `default`)\nresolves to `null` and the import continues. When false\n(default), a miss halts the record.\n"}}},"type":"array"},"query":{"type":"array","items":{"type":"string"},"description":"SQL statements to execute; required when queryType is `[\"per_record\"]`, `[\"per_page\"]`, or `[\"first_page\"]`, and typically contains a single statement.\nEach element is a complete SQL statement as a plain string — never a single bare string and never an array of objects (objects cause a Cast error).\nInject values from the incoming record with Handlebars using the `record.` prefix: double braces for strings and dates (`{{record.name}}`; the platform quotes the value and escapes its apostrophes) and triple braces for numbers (`{{{record.quantity}}}`).\nRendering is strict: a reference the render model cannot resolve (a bare `{{name}}` without prefix, or `record.*` when the arriving record is a grouped array) fails the step with `cannot_evaluate_handlebars` — it does not render an empty value.\n`record` and `rows` are aliases chosen by the arriving data's shape: an object record binds `record` (`rows` undefined); a grouped array-record (e.g. upstream `groupByFields`) binds `rows` (`record` undefined) — address grouped rows as `{{rows.0.field}}` or iterate `{{#each rows}}`.\nDouble braces (`{{record.field}}`) output a string value wrapped in single quotes with embedded quotes doubled, while triple braces output it raw; block helpers such as `{{#each}}` and `{{#if}}` use double braces as normal. In Snowflake queries a JSON number or boolean renders bare under double braces too (observed in export queries).\nOn Snowflake, the quoting leaves backslashes, which Snowflake reads as escape sequences: a double-brace value holding a backslash arrives changed, and `PARSE_JSON({{jsonSerialize …}})` fails on a double quote or line break in the data (observed in export queries). Pass such values in a dollar-quoted constant with triple braces (`$${{{record.field}}}$$`), which Snowflake reads as is, but only when the value can't contain `$$`: a `$$` in it ends the constant, and the rest is read as SQL."},"queryType":{"type":["array","null"],"items":{"type":"string","enum":["INSERT","UPDATE","bulk_insert","per_record","per_page","first_page","bulk_load"]},"description":"Execution strategy for the SQL operation, which determines the required companion field: per_record, per_page, and first_page require `query`; bulk_insert requires `bulkInsert`; bulk_load requires `bulkLoad`.\nPrefer the highest-performance type the operation supports: bulk_load (Snowflake, Google BigQuery, Microsoft SQL Server on a cloud connection, Azure Synapse and Amazon Redshift — handles INSERT, upsert via `bulkLoad.primaryKeys`, and custom merge or ignore-existing logic via `bulkLoad.overrideMergeQuery`), then bulk_insert for pure INSERTs, then per_page, then per_record only when the logic cannot be expressed as a batch operation.\nFor UPDATE, UPSERT, or ignore-existing logic on databases without bulk_load support, use per_page or per_record with `query` — bulk_insert has no duplicate-checking logic. Prefer per_page when the statement can loop `batch_of_records`, and use per_record when the SQL can't be written as one batch statement.\nSet a single value; do not combine types or use the legacy INSERT and UPDATE values.\nper_page statements render against the whole batch (`batch_of_records`) rather than a single record — loop with `{{#each batch_of_records}}...{{record.fieldName}}...{{/each}}`, with the same quoting as `query` (double braces for strings, triple for numbers).\n\n`bulk_load` — Stages the records as files and loads them with the database's native COPY / bulk mechanism for maximum throughput; supports INSERT, upsert via bulkLoad.primaryKeys, and custom merge or ignore-existing logic via bulkLoad.overrideMergeQuery; available on Snowflake, Google BigQuery, Microsoft SQL Server (cloud connections), Azure Synapse and Amazon Redshift (NetSuite Analytics Warehouse has it through the JDBC import). The import form calls it \"Use optimized bulk load (recommended for larger imports)\" and, beside it, calls bulk_insert \"Use batch insert SQL query\".\n`bulk_insert` — Batch INSERT via multi-row VALUES clause for efficient bulk data loading; has no upsert or ignore-existing logic and is available for all RDBMS types.\n`per_page` — Executes one SQL statement per page (batch) of records; available for all RDBMS types.\n`per_record` — Executes one SQL statement per record, giving full control over individual record SQL; available for all RDBMS types."},"bulkInsert":{"type":"object","description":"Configuration for batch INSERTs via a multi-row VALUES clause; required when queryType is `[\"bulk_insert\"]` and must not be set otherwise.\nOnly suited to pure INSERTs — for UPDATE or UPSERT, use queryType `[\"per_page\"]` (a statement that loops `batch_of_records`) or `[\"per_record\"]` with the `query` field instead, and for ignore-existing/skip-duplicates logic `[\"per_record\"]`.","properties":{"tableName":{"type":"string","description":"The name of the database table into which the bulk insert operation will be executed. This value must correspond to a valid, existing table within the target relational database management system (RDBMS). It serves as the primary destination for inserting multiple rows of data efficiently in a single operation. The table name can include schema or namespace qualifiers if supported by the database (e.g., \"schemaName.tableName\"), allowing precise targeting within complex database structures. Proper validation and sanitization of this value are essential to ensure the operation's success and to prevent SQL injection or other security vulnerabilities."},"batchSize":{"type":"string","description":"The number of records to be inserted into the database in a single batch during a bulk insert operation. This parameter is crucial for optimizing the performance and efficiency of bulk data loading by controlling how many records are grouped together before being sent to the database. Proper tuning of batchSize balances memory consumption, transaction overhead, and throughput, enabling the system to handle large volumes of data efficiently without overwhelming resources or causing timeouts. Adjusting batchSize directly impacts transaction size, network utilization, error handling granularity, and recovery strategies, making it essential to tailor this value based on the specific database capabilities, system resources, and workload characteristics."}}},"bulkLoad":{"type":"object","properties":{"tableName":{"type":"string","description":"Target table for the bulk load.\nMust reference an existing table accessible with the connection's credentials; schema-qualified names are supported."},"primaryKeys":{"type":"array","items":{"type":"string"},"description":"Columns that uniquely identify each record in the target table, used to match existing rows during the load.\nSet for upsert behavior (a MERGE is auto-generated); omit (or empty) for a pure INSERT.\nComposite keys are supported — list every key column, and names must exactly match the target schema."},"overrideMergeQuery":{"type":"boolean","description":"When true, a custom merge query replaces the auto-generated MERGE statement, enabling ignore-existing logic, conditional updates, or multi-table operations (\"Override merge/insert statement\" on the import form).\nThe override SQL lives in `query` and references `{{preMergeTemporaryTable}}` (also written `{{import.rdbms.bulkLoad.preMergeTemporaryTable}}`) for the staging table and `{{tableName}}` for the destination table.\nThe SQL itself in this field fails the save: 422 `invalid_field` \"Invalid value '<the statement>' for field 'rdbms.bulkLoad.overrideMergeQuery'. Reason: CastError.\""},"aggregation":{"type":"object","description":"How the staged files are cut and handed to the database. The import form writes it for a new bulk-load import when the account's `enableSizeBasedAggregation` flag is on for that database (Snowflake included — its form offers bulk_load without the flag but writes `aggregation` only with it); the server fills `type` and `maxFileSizeInMB` when they are omitted, so `loadDataAsAvailable` is the only value to author.","properties":{"type":{"type":"string","enum":["size"],"description":"Filled by the server on save; the only mode is size-based cutting."},"maxFileSizeInMB":{"type":"integer","description":"Size of each staged file in MB; the server fills 250 when the field is omitted and the import form never sets it — leave it out."},"loadDataAsAvailable":{"type":"boolean","default":false,"description":"When true, each staged file is sent for loading as soon as it is written, so the dataset arrives as several smaller loads (\"Load data as available\" on the import form). When false, every file is held until all are ready and they are loaded together."}}}},"description":"Configuration for bulk loading: the records are staged as files and loaded with the database's native COPY / bulk mechanism for maximum throughput.\nRequired when queryType is `[\"bulk_load\"]`; available on Snowflake, Google BigQuery, Microsoft SQL Server (cloud connections), Azure Synapse and Amazon Redshift."},"updateLookupName":{"type":["string","null"],"description":"Name of the lookup used for update operations in the legacy composite (queryType1) flow."},"updateExtract":{"type":["string","null"],"description":"Path used to extract the value that drives update operations in the legacy composite flow."},"ignoreLookupName":{"type":["string","null"],"description":"Name of an entry in `lookups` that probes the database per record to decide whether it\nalready exists, so `ignoreExisting` can skip it (or `ignoreMissing` can skip records with\nno match). Use it when existence must be read from the database; use `ignoreExtract` when\nthe incoming record already carries the field that signals existence."},"ignoreExtract":{"type":["string","null"],"description":"Path to a field on the incoming record whose presence decides the skip: with\n`ignoreExisting`, a populated value marks the record as existing and it is skipped; with\n`ignoreMissing`, an empty value marks it as missing and it is skipped. Zero per-record\noverhead; set exactly one of `ignoreExtract` / `ignoreLookupName`."}},"if":{"not":{"propertyNames":{"enum":["lookups"]}}},"then":{"required":["queryType"],"if":{"required":["queryType"],"properties":{"queryType":{"type":"array","contains":{"enum":["per_record","per_page"]}}}},"then":{"required":["query"],"properties":{"bulkInsert":{"maxProperties":0}}},"else":{"if":{"required":["queryType"],"properties":{"queryType":{"type":"array","contains":{"const":"first_page"}}}},"then":{"required":["query"]},"else":{"if":{"required":["queryType"],"properties":{"queryType":{"type":"array","contains":{"const":"bulk_insert"}}}},"then":{"required":["bulkInsert"],"properties":{"query":{"maxItems":0}}},"else":{"if":{"required":["queryType"],"properties":{"queryType":{"type":"array","contains":{"const":"bulk_load"}}}},"then":{"required":["bulkLoad"],"if":{"required":["bulkLoad"],"properties":{"bulkLoad":{"type":"object","required":["overrideMergeQuery"],"properties":{"overrideMergeQuery":{"const":true}}}}},"then":{"properties":{"query":{"type":"array"}}},"else":{"properties":{"query":{"maxItems":0}}}}}}}}},"Lookups":{"type":"array","description":"Configuration for value-to-value transformations using lookup tables.\n\n**Purpose**\n\nLookups provide a way to translate values from one system to another. They transform\ninput values into output values using either static mapping tables or\ndynamic lookup caches.\n\n**Lookup mechanisms**\n\nThere are two distinct lookup mechanisms available:\n\n1. **Static Lookups**: Define a simple key-value map object and store it as part of your resource\n   - Best for: Small, fixed sets of values that rarely change\n   - Implementation: Configure the `map` object with input-to-output value mappings\n   - Example: Country codes, status values, simple translations\n\n2. **Dynamic Lookups**: Reference an existing 'Lookup Cache' resource in your Celigo account\n   - Best for: Large datasets, frequently changing values, or complex reference data\n   - Implementation: Configure `_lookupCacheId` to reference cached data maintained independently\n   - Example: Product catalogs, customer databases, pricing information\n\n**Property usage**\n\nThere are two mutually exclusive ways to configure lookups, depending on which mechanism you choose:\n\n1. **For Static Mappings**: Configure the `map` property with a direct key-value object\n   ```json\n   \"map\": {\"US\": \"United States\", \"CA\": \"Canada\"}\n   ```\n\n2. **For Dynamic Lookups**: Configure the following properties:\n   - `_lookupCacheId`: Reference to the lookup cache resource\n   - `extract`: JSON path to extract specific value from the returned lookup object\n\n**When to use**\n\nLookups are ideal for:\n\n1. **Value Translation**: Mapping codes or IDs to human-readable values\n\n2. **Data Enrichment**: Adding related information to records during processing\n\n3. **Normalization**: Ensuring consistent formatting of values across systems\n\n**Implementation details**\n\nLookups can be referenced in:\n\n1. **Field Mappings**: Direct use in field transformation configurations\n\n2. **Handlebars Templates**: Use within templates with the syntax:\n   ```\n   {{lookup 'lookupName' record.fieldName}}\n   ```\n\n**Example usage**\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"countryCodeToName\",\n    \"map\": {\n      \"US\": \"United States\",\n      \"CA\": \"Canada\",\n      \"UK\": \"United Kingdom\"\n    },\n    \"default\": \"Unknown Country\",\n    \"allowFailures\": true\n  },\n  {\n    \"name\": \"productDetails\",\n    \"_lookupCacheId\": \"60a2c4e6f321d800129a1a3c\",\n    \"extract\": \"$.details.price\",\n    \"allowFailures\": false\n  }\n]\n```\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for the lookup table within this configuration.\n\nThis name must be unique within the scope where the lookup is defined and is used to reference\nthe lookup in handlebars templates with the syntax {{lookup 'name' value}}.\n\nChoose descriptive names that indicate the transformation purpose, such as:\n- \"countryCodeToName\" for country code to full name conversion\n- \"statusMapping\" for status code translations\n- \"departmentCodes\" for department code to name mapping\n"},"map":{"type":["object","null"],"description":"The lookup mapping table as key-value pairs. The platform stores `null`\nhere on dynamic lookups, which resolve values at runtime instead of\nfrom a static table.\n\nThis object contains the input values as keys and their corresponding\noutput values. When a input value matches a key in this object,\nit will be replaced with the corresponding value.\n\nThe map should be kept to a reasonable size (typically under 100 entries)\nfor optimal performance. For larger mapping requirements, consider using\ndynamic lookups instead.\n\nMaps can include:\n- Simple code to name conversions: {\"US\": \"United States\"}\n- Status transformations: {\"A\": \"Active\", \"I\": \"Inactive\"}\n- ID to name mappings: {\"100\": \"Marketing\", \"200\": \"Sales\"}\n\nValues can be strings, numbers, or booleans, but all are stored as strings\nin the configuration.\n"},"_lookupCacheId":{"type":"string","description":"Reference to a LookupCache resource that contains the reference data for the lookup.\n\n**Purpose**\n\nThis field connects the lookup to an external data source that has been cached in the system.\nUnlike static lookups that use the `map` property, dynamic lookups can reference large datasets\nor frequently changing information without requiring constant updates to the integration.\n\n**Implementation details**\n\nThe LookupCache resource referenced by this ID contains:\n- The data records to be used as a reference source\n- Configuration for how the data should be indexed and accessed\n- Caching parameters to balance performance with data freshness\n\n**Usage patterns**\n\nCommonly used to reference:\n- Product catalogs or SKU databases\n- Customer or account information\n- Pricing tables or discount rules\n- Complex business logic lookup tables\n\nFormat: 24-character hexadecimal string (MongoDB ObjectId)\n","format":"objectid"},"extract":{"type":"string","description":"JSON path expression that extracts a specific value from the cached lookup object.\n\n**Purpose**\n\nWhen using dynamic lookups with a LookupCache, this JSON path identifies which field to extract\nfrom the cached object after it has been retrieved using the lookup key.\n\n**Implementation details**\n\n- Must use JSON path syntax (similar to mapping extract fields)\n- Operates on the cached object returned by the lookup operation\n- Examples:\n  - \"$.name\" - Extract the name field from the top level\n  - \"$.details.price\" - Extract a nested price field\n  - \"$.attributes[0].value\" - Extract a value from the first element of an array\n\n**Usage scenario**\n\nWhen a lookup cache contains complex objects:\n```json\n// Cache entry for key \"PROD-123\":\n{\n  \"id\": \"PROD-123\",\n  \"name\": \"Premium Widget\",\n  \"details\": {\n    \"price\": 99.99,\n    \"currency\": \"USD\",\n    \"inStock\": true\n  }\n}\n```\n\nSetting extract to \"$.details.price\" would return 99.99 as the lookup result.\n\nIf no extract is provided, the entire cached object is returned as the lookup result.\n"},"default":{"type":["string","null"],"description":"Default value to use when the source value is not found in the lookup map.\nThe platform stores `null` here when no default is configured.\n\nThis value is used as a fallback when:\n1. The source value doesn't match any key in the map\n2. allowFailures is true or omitted\n\nSetting an appropriate default helps prevent flow failures due to unexpected\nvalues and provides predictable behavior for edge cases.\n\nCommon default patterns include:\n- Descriptive unknowns: \"Unknown Country\", \"Unspecified Status\"\n- Original value indicators: \"{Original Value}\", \"No mapping found\"\n- Neutral values: \"Other\", \"N/A\", \"Miscellaneous\"\n\nA save refuses a default together with an explicit `allowFailures: false`\n(422 `Lookups with default set can not have allowFailures explicitly set to\nfalse.`): a lookup either fails a miss (`allowFailures: false`, no default) or\nfalls back to its default.\n"},"allowFailures":{"type":["boolean","null"],"description":"When true, missing lookup values will use the default value rather than causing an error.\n\n**Behavior control**\n\nThis field determines how the system handles source values that don't exist in the map:\n\n- true: Use the default value for missing mappings and continue processing\n- false: Treat missing mappings as errors, failing the record (`lookup_error` for a\n  static map, `lookupcache_evaluation_failed` for a lookup cache)\n- omitted or null: the record doesn't fail either. On an import a static lookup writes\n  its default if it has one, and a static or cache lookup without one leaves the\n  mapped field out of the output, with no error\n\n**Recommendation**\n\nSet this to true when:\n- New source values might appear over time\n- Data quality issues could introduce unexpected values\n- Processing should continue even with imperfect mapping\n\nSet this to false when:\n- Complete data accuracy is critical\n- All possible source values are known and controlled\n- Missing mappings indicate serious data problems that should be addressed\n\nA default raises no error: records that miss go out with the default value and\nnothing reports them. Use true only when that fallback is acceptable at the\ndestination, and set false explicitly when a missing value must stop the record.\n"}}}},"S3-2":{"type":"object","description":"Defines where files are written to an Amazon S3 bucket. Required when the _connectionId\nfield references an AWS S3 connection; must not be included for other connection types.\nregion and bucket locate the destination, fileKey sets the object key, and backupBucket\nretains a copy after a successful import.","required":["region","bucket"],"properties":{"region":{"type":"string","default":"us-east-1","description":"AWS region where the S3 bucket resides. Must match the bucket's actual location to avoid\nconnectivity errors and misrouted requests."},"bucket":{"type":"string","description":"S3 bucket that receives the imported files. The bucket must already exist and the\nconnection's AWS credentials must have s3:PutObject permission on it."},"fileKey":{"type":"string","description":"Object key under which each file is stored in the bucket. Include slashes to organize\nfiles in a folder-style hierarchy (for example, imports/2023/12/orders.json); keys are\ncase-sensitive. Supports handlebars placeholders such as orders-{{timestamp}}.json to\ngenerate a unique key per run."},"backupBucket":{"type":"string","description":"S3 bucket where a copy of each imported file is retained after a successful import; if\nomitted, no backup copy is kept. Must reference an existing bucket the connection's\ncredentials have s3:PutObject permission on."},"serverSideEncryptionType":{"type":"string","description":"Server-side encryption applied to uploaded objects. The connection form sets `AES256`\n(SSE-S3, S3-managed keys) when encryption is enabled; leave unset to use the bucket's\ndefault encryption."}}},"Wrapper-2":{"type":"object","description":"Configuration for Wrapper imports, which delegate writing records to custom connector code\n(typically a stack-hosted function) rather than a built-in adaptor. Required when the\n_connectionId field references a wrapper connection.","properties":{"function":{"type":"string","description":"Name of the function the wrapper invokes to process records.\nMust match a callable function in the wrapper's execution context; names are case-sensitive."},"configuration":{"type":"object","additionalProperties":true,"description":"Free-form settings passed to the wrapper function at runtime (connector-specific keys such\nas method, apiVersion, headers, or handler). Structure is defined by the wrapper code, not\nby this schema."},"lookups":{"$ref":"#/components/schemas/Lookups","description":"Lookup definitions for this wrapper import. Wrapper lookups use\nthe generic Celigo shape: either a static `map` (key→value\nobject) or a dynamic `_lookupCacheId` + `extract` pair. See\n`common/schemas/lookups.yml` for the full item shape."}},"if":{"not":{"propertyNames":{"enum":["lookups"]}}},"then":{"required":["function"]}},"Salesforce-2":{"type":"object","description":"Salesforce-specific configuration for the import: the operation to perform, the API to use, and object-level settings such as `sObjectType`, `idLookup`, and `upsert`. Properties like `ignoreExisting`, `ignoreMissing`, `name`, and `description` are not part of this object — they belong at the import level.","properties":{"lookups":{"description":"Lookup definitions used to resolve Salesforce record references or\nstatic value mappings. Each lookup has a unique ``name`` that mapping\nfields reference via ``lookupName`` and that Handlebars expressions\nreference via ``{{lookup \"name\" value}}``.\n\n**Mirror invariant**\nThe Celigo platform stores lookups in TWO locations on a Salesforce\nimport resource: top-level ``lookups`` and ``salesforce.lookups``.\nBoth arrays must hold byte-for-byte identical content, so write\nboth on every save.\n\n**Static vs dynamic lookups**\n- **Static** — only ``map`` + optional ``default``. No Salesforce\n  query runs. Use for small controlled vocabularies (country codes,\n  status labels, etc.).\n- **Dynamic** — ``sObjectType`` + ``resultField`` + ``whereClause``.\n  Issues a SOQL query at runtime. Use ``map`` alongside the query to\n  short-circuit known values before hitting Salesforce.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Unique identifier for this lookup entry. Mapping fields reference\nit via ``lookupName``, and Handlebars expressions reference it\nvia ``{{lookup \"name\" value}}``. Must be unique within this\nresource's ``lookups`` array. Conventionally a short slug or a\ngenerated hash — the value is only meaningful as a reference\ntoken.\n"},"sObjectType":{"type":"string","description":"Salesforce sObject API name to query (e.g. ``Account``,\n``Contact``, ``Solution__c``). For custom objects include the\n``__c`` suffix. Case-sensitive — must match exactly as declared\nin the target Salesforce org.\n"},"resultField":{"type":"string","description":"sObject field whose value is returned when the SOQL ``whereClause``\nmatches a record. Typically ``Id`` when the lookup resolves to a\nSalesforce record reference, but any queryable field is valid.\n"},"whereClause":{"type":"string","description":"SOQL ``WHERE`` clause that identifies the matching Salesforce\nrecord. Use Handlebars expressions (triple braces recommended:\n``{{{field}}}``) to inject values from the incoming source record.\nWrap the whole expression in parentheses.\n\nExamples:\n- ``(Id = {{{id [Customer SFDC ID]}}})``\n- ``(Email = '{{{email}}}')``\n- ``(External_Id__c = {{{string externalId}}} AND IsActive = true)``\n\nCeligo's Handlebars helpers (``string``, ``id``, ``double``,\n``bool``, ``date``) produce correctly-typed SOQL literals —\nprefer them over manual quoting. String values wrap in single\nquotes; numeric/id values do not.\n"},"map":{"type":["object","null"],"description":"Optional static key→value map evaluated BEFORE the SOQL query.\nWhen the incoming field value matches a key, the mapped value\nis returned without hitting Salesforce. Useful for small\ncontrolled enumerations where a full query would be overkill.\n"},"default":{"type":["string","null"],"description":"Value returned when the SOQL query matches no records. When\nomitted and ``allowFailures`` is false, an unmatched lookup\nfails the record.\n"},"allowFailures":{"type":"boolean","description":"When true, a lookup miss (no matching record and no ``default``)\nis tolerated — the mapped field resolves to ``null`` and the\nimport continues. When false (default), a miss halts processing\nfor the record.\n"},"useDefaultOnMultipleMatches":{"type":"boolean","description":"Controls behaviour when the SOQL query matches more than one\nrecord. When true, the ``default`` value is returned instead of\nraising an ambiguous-match error. When false (default), multiple\nmatches are treated as a failure.\n"}}},"type":"array"},"operation":{"type":"string","enum":["insert","update","upsert","upsertpicklistvalues","delete","addupdate"],"description":"Controls how records are written to Salesforce. Each value pairs with a lookup strategy: `upsert` requires `upsert.externalIdField` plus `idLookup.extract`, while `update`, `delete`, `addupdate`, and `insert` with `ignoreExisting` require `idLookup.whereClause`. Values are converted to lowercase automatically."},"api":{"type":"string","enum":["soap","rest","metadata","compositerecord"],"description":"Selects the Salesforce API used to execute the import. Use `soap` for most imports and another API only when its specific capability is needed; default to `soap` when no API is specified. Values are converted to lowercase automatically."},"soap":{"type":"object","properties":{"headers":{"type":"object","properties":{"allOrNone":{"type":"boolean","description":"When true, the batch is processed atomically: if any record fails, the entire transaction is rolled back and nothing is committed. When false or omitted, records that succeed are committed even if others in the batch fail."}},"description":"SOAP headers included in requests sent to Salesforce, such as the `allOrNone` transaction-control header."},"batchSize":{"type":"number","description":"Number of records sent to Salesforce in each SOAP API batch. Larger batches reduce the number of API calls; smaller batches reduce per-call load and the chance of timeouts."}},"description":"Settings that apply when `api` is `soap`, including SOAP request headers and batch size."},"sObjectType":{"type":"string","description":"Salesforce object the import writes to, such as `Account`, `Contact`, or a custom object like `Vendor__c`. Must exactly match the object's case-sensitive API name; custom objects include the `__c` suffix."},"idLookup":{"type":"object","properties":{"extract":{"type":"string","description":"Field in the incoming data whose value is matched against the Salesforce External ID field named in `upsert.externalIdField`. Set only when `operation` is `upsert`; all other operations use `whereClause` instead. Choose a field that is unique and always populated — missing or null values cause the upsert to fail. If both `extract` and `whereClause` are set, `extract` takes precedence for upsert."},"whereClause":{"type":"string","description":"SOQL condition (without the `WHERE` keyword) that finds existing Salesforce records for each incoming record. Required when `operation` is `update`, `delete`, or `addupdate`, and for `insert` with `ignoreExisting`; for `upsert`, use `extract` instead. Reference incoming fields with triple-brace Handlebars and wrap string values in single quotes (e.g. `Email = '{{{Email}}}'`) — triple braces emit the raw value without HTML-escaping, which would corrupt characters like `&` or `'`. Combine conditions with `AND`/`OR`."}},"description":"Controls how existing Salesforce records are matched before writing. Set exactly one of `extract` (for `upsert`) or `whereClause` (for `update`, `delete`, `addupdate`, and `insert` with `ignoreExisting`); omit for plain inserts.\n\nRequired when operation is addupdate, delete, update, or upsert."},"upsert":{"type":"object","properties":{"externalIdField":{"type":"string","description":"API name of the Salesforce field, marked as External ID, that upsert operations match against. This is the target side of the match; `idLookup.extract` names the source field in the incoming data. If the value matches multiple Salesforce records, the upsert fails."}},"description":"Upsert matching configuration, required when `operation` is `upsert` and omitted for all other operations. Contains only `externalIdField`, which pairs with `idLookup.extract` at the parent level.\n\nRequired when operation is upsert."},"upsertpicklistvalues":{"type":"object","properties":{"type":{"type":"string","enum":["picklist","multipicklist"],"description":"Picklist field type being managed. Values are converted to lowercase automatically."},"fullName":{"type":"string","description":"Fully qualified API name of the picklist field, in `ObjectName.FieldName__c` form (for example, `Account.MyPicklist__c`). Must match the case-sensitive Salesforce API name."},"label":{"type":"string","description":"Display label for the picklist field in the Salesforce UI. Can contain spaces and does not need to match the API name."},"visibleLines":{"type":"number","description":"Number of values visible without scrolling in the Salesforce UI selection box. Only applies when `type` is `multipicklist`."}},"description":"Picklist field definition managed when `operation` is `upsertpicklistvalues`; omit for all other operations. Manages the field's metadata — name, label, and display settings — and requires Salesforce Metadata API permissions.\n\nRequired when operation is upsertpicklistvalues."},"removeNonSubmittableFields":{"type":"boolean","description":"When true, strips fields Salesforce will not accept — read-only, system-generated, and formula fields — from the payload before submission, preventing rejection errors. When false or omitted, all fields are submitted as-is. Only the outgoing payload is affected; the source data is unchanged."},"document":{"type":"object","properties":{"id":{"type":"string","description":"Salesforce-assigned ID of the Document record. The 15-character form is case-sensitive; the 18-character form adds a case-insensitivity checksum."},"name":{"type":"string","description":"Display name of the document in Salesforce, used to identify it in folders, search, and API operations."},"folderId":{"type":"string","description":"Salesforce folder that stores the document. The document inherits the folder's sharing and visibility settings; changing this value moves the document to a different folder."},"contentType":{"type":"string","description":"MIME type of the document content, such as `application/pdf` or `image/png`."},"developerName":{"type":"string","description":"Unique API name used to reference the document in code, metadata, and integrations. Distinct from the display name; uses only letters, numbers, and underscores with no spaces."},"isInternalUseOnly":{"type":"boolean","description":"When true, marks the document for internal use only — it must not be shared with parties outside the organization."},"isPublic":{"type":"boolean","description":"When true, the document is accessible to all users; when false, access is limited to authorized users by Salesforce permissions and sharing settings."}},"description":"Salesforce Document record written by the import, combining the file content with metadata such as name, folder, and content type."},"attachment":{"type":"object","properties":{"id":{"type":"string","description":"Salesforce-assigned ID of the Attachment record. Prefer the 18-character case-insensitive form over the 15-character case-sensitive form in integrations."},"name":{"type":"string","description":"Filename of the attachment, including the file extension (for example, `contract_agreement.pdf`)."},"parentId":{"type":"string","description":"Salesforce record the attachment is linked to, such as an Account, Contact, or Opportunity. Required when creating an attachment; access to the attachment follows the parent record's permissions."},"contentType":{"type":"string","description":"MIME type of the attachment content, such as `application/pdf` or `image/png`."},"isPrivate":{"type":"boolean","description":"When true, the attachment is visible only to users explicitly authorized to access it; when false or omitted, it inherits the visibility of its parent record. This setting only restricts access further — it never grants access beyond the parent record's permissions."},"description":{"type":"string","description":"Free-text note describing the attachment's content or purpose, shown alongside the attachment details in Salesforce."}},"description":"Salesforce Attachment record written by the import — a file linked to a parent record via `parentId`, with metadata such as name and content type."},"contentVersion":{"type":"object","properties":{"contentDocumentId":{"type":"string","description":"ContentDocument this version belongs to; all versions of the same document share this ID. Assigned by Salesforce when the content version is created and immutable afterward."},"title":{"type":"string","description":"Display title of this content version, shown in content libraries, search results, and version history."},"pathOnClient":{"type":"string","description":"Original file path on the client machine before upload. Used for reference and display only; it does not affect how the file is stored or accessed in Salesforce."},"tagCsv":{"type":"string","description":"Comma-separated tags applied to the content version for categorization and search. Updating this field replaces the entire tag set, so include every tag the version should keep."},"contentLocation":{"type":"string","description":"Specifies where the content file for this ContentVersion record is stored — \"S\" for Salesforce internal storage or \"E\" for an external system (the two values the import form offers). Salesforce also defines \"L\" for content shared via a link, but the form does not set it. Typically assigned automatically based on the upload method."}},"description":"Salesforce ContentVersion record written by the import — a specific version of a content item in Salesforce Files. Each modification produces a new version, and all versions of a document are linked through `contentDocumentId`."}},"if":{"anyOf":[{"required":["attachment"]},{"required":["contentVersion"]}]},"then":{"required":["operation"]},"else":{"if":{"not":{"propertyNames":{"enum":["lookups","api"]}}},"then":{"required":["operation","api"],"if":{"properties":{"operation":{"const":"upsert"}},"required":["operation"]},"then":{"required":["idLookup","upsert"],"properties":{"idLookup":{"required":["extract"]},"upsert":{"required":["externalIdField"]}}},"else":{"if":{"properties":{"operation":{"enum":["update","delete","addupdate"]}},"required":["operation"]},"then":{"required":["idLookup"],"properties":{"idLookup":{"required":["whereClause"]}}},"else":{"if":{"properties":{"operation":{"const":"upsertpicklistvalues"}},"required":["operation"]},"then":{"required":["upsertpicklistvalues"],"properties":{"upsertpicklistvalues":{"required":["fullName"]}}}}}}}},"Tool-2":{"type":"object","description":"Configuration for a `ToolImport`, which executes a reusable Celigo Tool resource as the\nimport action. The referenced tool defines its own input contract, processing pipeline, and\noutput; this object binds the tool to the import, along with the connections the tool's steps\nuse for this invocation.","required":["_toolId"],"properties":{"_toolId":{"type":"string","format":"objectId","description":"Tool resource this import executes. The tool supplies the input schema, routing, page\nprocessors, and output mapping; the import runs that tool when records reach it. Required —\na `ToolImport` has no behavior without a tool to run."},"overrides":{"type":"object","description":"Per-import overrides applied to the referenced tool at execution time. Tools use a\nbring-your-own-keys connection model: the tool declares abstract connection slots, and the\nconsumer binds concrete connections here so one tool definition can run against different\nsystems without being modified. Bind every connection the tool's steps use; to keep the\nconnection a step was authored against, bind it to itself.","properties":{"connections":{"type":"array","description":"Connection bindings that replace the tool's abstract connection slots for this import.\nEach entry maps one of the tool's abstract connections to the concrete connection the\nimport should use.","items":{"type":"object","required":["_id","_abstractId"],"properties":{"_id":{"type":"string","format":"objectId","description":"Concrete connection bound to the abstract slot for this import's tool run."},"_abstractId":{"type":"string","format":"objectId","description":"Abstract connection slot, declared by the tool, that this binding replaces. Matches\nthe abstract connection id defined on the referenced tool."},"_borrowConcurrencyFromConnectionId":{"type":"string","format":"objectId","description":"Optional pointer to another connection whose\nconcurrency budget this binding shares. Use\nwhen several wrappers / steps should share rate\nlimits against the same downstream system but\nuse different credentials. Mutually exclusive\nwith `_id` on the same entry.\n"}}}}}}}},"Topic":{"type":"object","description":"Publishing settings for an Event Streams publisher (`adaptorType: TopicImport`). Each\nincoming record is published as a message to the topic; the platform delivers it\ninternally, so the import carries no `_connectionId`. Present when `adaptorType` is\n`TopicImport`; the server fills `batchSize` with its default when it is omitted.","properties":{"_topicId":{"type":"string","format":"objectId","description":"Topic the records are published to. Create or look it up with `GET /v1/topics`; the\ntopic must belong to the same account. A clone of the import made in the same\nenvironment keeps `_topicId`."},"batchSize":{"type":"integer","minimum":1,"default":1000,"description":"Number of records published per request to the topic. Lower it when individual\nmessages are large; the default keeps publishing efficient for typical record sizes."}}},"File":{"type":"object","description":"**CRITICAL: This object is REQUIRED for all file-based import adaptor types.**\n\n**When to include this object**\n\n✅ **MUST SET** when `adaptorType` is one of:\n- `S3Import`\n- `FTPImport`\n- `AS2Import`\n- `HTTPImport` in file mode (`http.type: \"file\"` — the folder mode of the cloud file\n  providers such as Google Drive, Box, Dropbox, Azure Blob, Google Cloud Storage and Celigo\n  Storage); the same `file` object, including `type: filedefinition` with `_ediProfileId`\n  for EDI, applies as on S3 / FTP\n\n❌ **DO NOT SET** for non-file-based imports like:\n- `SalesforceImport`\n- `NetSuiteImport`\n- `HTTPImport` in records mode (`http.type` unset — a REST endpoint receiving records)\n- `MongodbImport`\n- `RDBMSImport`\n- `JDBCImport`\n\n**Read-only on database bulk loads**\n\nAn `RDBMSImport` whose `rdbms.queryType` is `bulk_load` returns a `file` object anyway: the\nplatform's description of the staging file it generates for the load (delimiter, quoting,\nnewline handling, and how the load is batched into files). It is derived on every read,\nnever stored — a save that changes it is accepted and the change discarded (the audit log\nstill records the request), and the import editor has no control for it. Change what\nreaches the destination with a preMap/postMap hook, the mapping, or a transform, or correct\nthe source data.\n\n**Minimum required fields**\n\nFor most file imports, you need at minimum:\n- `fileName`: The output file name (supports Handlebars like `{{timestamp}}`)\n- `type`: The file format (json, csv, xml, xlsx)\n- `aggregation.type`: How the run's records are batched into files (`all` for one file per run)\n\n**Example (S3 Import)**\n\n```json\n{\n  \"file\": {\n    \"fileName\": \"customers-{{timestamp}}.json\",\n    \"aggregation\": { \"type\": \"all\" },\n    \"type\": \"json\"\n  },\n  \"s3\": {\n    \"region\": \"us-east-1\",\n    \"bucket\": \"my-bucket\",\n    \"fileKey\": \"customers-{{timestamp}}.json\"\n  },\n  \"adaptorType\": \"S3Import\"\n}\n```","properties":{"fileName":{"type":"string","description":"**REQUIRED for file-based imports.**\n\nThe name of the file to be created/written. Supports Handlebars expressions for dynamic naming,\nrendered against the incoming record — a file-transfer import keeps the source file's name from\nthe record's `fileMeta`, whose field names are the exporting provider's\n(`{{record.fileMeta.fileName}}` from an FTP source, `fileMeta.name` from Google Drive).\n\n**Default behavior**\n- When the user does not specify a file naming convention, **always default to timestamped filenames** using `{{timestamp}}` (e.g., `\"items-{{timestamp}}.csv\"`). This ensures each run produces a unique file and avoids overwriting previous exports.\n- Only use a fixed filename (without timestamp) if the user explicitly requests overwriting or a fixed name.\n\n**Common patterns**\n- `\"data-{{timestamp}}.json\"` - Timestamped JSON file (DEFAULT — use this pattern when not specified)\n- `\"export-{{date}}.csv\"` - Date-stamped CSV file\n- `\"{{recordType}}-backup.xml\"` - Dynamic record type naming\n\n**Examples**\n- `\"customers-{{timestamp}}.json\"`\n- `\"orders-export.csv\"`\n- `\"inventory-{{date}}.xlsx\"`\n\n**Important**\n- For S3 imports, this should typically match the `s3.fileKey` value\n- For FTP imports, this should typically match the `ftp.fileName` value; the one blob file-transfer run on record set only this field and left `ftp.fileName` unset"},"skipAggregation":{"type":"boolean","description":"Legacy companion of `aggregation.type`: `true` when the run's records are written one\nfile per page, `false` when they are combined across the run or grouped by key.\nValidation reads the value sent — an EDI file definition needs `true` unless\n`aggregation.type` is `key`, and `batchSize` needs `true` — then the save rewrites it\nfrom `aggregation.type`, so send the two as a consistent pair.","default":false},"type":{"type":"string","enum":["json","csv","xml","xlsx","filedefinition"],"description":"**REQUIRED for file-based imports.**\n\nThe format of the output file.\n\nSelect the format that matches the target system's requirements."},"encoding":{"type":"string","enum":["utf8","win1252","utf-16le","gb18030","macroman","iso88591","shiftjis"],"description":"Character encoding for the output file.\n\n**Default:** `\"utf8\"`\n\nChange from the default only when the target system requires a specific encoding.","default":"utf8"},"delete":{"type":"boolean","description":"Whether to delete the source file after successful import.\n\n**Values**\n- `true`: Delete source file after processing\n- `false`: Keep source file\n\n**Default:** `false`","default":false},"compressionFormat":{"type":"string","enum":["gzip","zip"],"description":"Compression format for the output file.\n\n**When to use**\n- Large files that benefit from compression\n- When target system expects compressed files"},"backupPath":{"type":"string","description":"Path where backup copies of files should be stored.\n\n**Examples**\n- `\"backup/\"` - Relative backup folder\n- `\"/archive/2024/\"` - Absolute backup path"},"purgeInternalBackup":{"type":"boolean","description":"Whether to purge internal backup copies after successful processing.\n\n**Default:** `false`","default":false},"batchSize":{"type":"integer","description":"Number of records to include per batch/file when processing large datasets.\n\n**When to use**\n- Large imports that need to be split into multiple files\n- When target system has file size limitations\n\n**Note**\n- Offered when records are written one file per page (`aggregation.type: page` with\n  `skipAggregation: true`)"},"encrypt":{"type":"boolean","description":"Whether to encrypt the output file.\n\n**Values**\n- `true`: Encrypt the file (requires PGP configuration)\n- `false`: No encryption\n\n**Default:** `false`","default":false},"csv":{"type":"object","description":"CSV-specific configuration. Only used when `type` is `\"csv\"`.","properties":{"rowDelimiter":{"type":"string","description":"Character(s) used to separate rows. Default is newline.","default":"\n"},"columnDelimiter":{"type":"string","description":"Character(s) used to separate columns. Default is comma.","default":","},"includeHeader":{"type":"boolean","description":"When true, includes a header row with column names in the output.","default":true},"wrapWithQuotes":{"type":"boolean","description":"When true, wraps field values in quotes.","default":false},"replaceTabWithSpace":{"type":"boolean","description":"Replace tab characters with spaces.","default":false},"replaceNewlineWithSpace":{"type":"boolean","description":"Replace newline characters with spaces within fields.","default":false},"truncateLastRowDelimiter":{"type":"boolean","description":"Remove trailing row delimiter from the file.","default":false}}},"json":{"type":"object","description":"JSON-specific configuration. Only used when `type` is `\"json\"`.","properties":{"resourcePath":{"type":"string","description":"JSONPath expression to locate records within the JSON structure."}}},"xml":{"type":"object","description":"XML-specific configuration. Only used when `type` is `\"xml\"`.","properties":{"resourcePath":{"type":"string","description":"XPath expression to locate records within the XML structure."}}},"fileDefinition":{"type":"object","description":"Configuration settings for parsing files using a predefined file definition. This object enables processing of complex, non-standard, or proprietary file formats that require specialized parsing logic beyond what the standard parsers (CSV, JSON, XML, etc.) can handle.\n\n**When to use**\n\nConfigure this object when the `type` field is set to \"filedefinition\". This approach is required for properly handling:\n- Legacy or proprietary file formats with complex structures\n- Fixed-width text files where field positions are defined by character positions\n- Electronic Data Interchange (EDI) documents (X12, EDIFACT, etc.)\n- Multi-record type files where different lines have different formats\n- Files requiring complex preprocessing or custom parsing logic\n\n**File definition characteristics**\n\n- **Custom Parsing Rules**: Applies predefined parsing logic to complex file formats\n- **Reusable Configurations**: References externally defined parsing rules that can be reused\n- **Complex Format Support**: Handles formats that standard parsers cannot process\n- **Specialized Processing**: Often used for industry-specific or legacy formats\n\n**Implementation strategy for ai agents**\n\n1. **Format Analysis**:\n    - Determine if the file format is standard (CSV, JSON, XML) or requires custom parsing\n    - Check if the format follows industry standards like EDI, SWIFT, or fixed-width\n    - Assess if there are multiple record types within the same file\n    - Identify if specialized logic is needed to interpret the file structure\n\n2. **File Definition Selection**:\n    - Verify that a suitable file definition has already been created in the system\n    - Check if existing file definitions match the format requirements\n    - Confirm the file definition ID from system administrators if needed\n    - Ensure the file definition is compatible with the export's needs\n","properties":{"_fileDefinitionId":{"type":"string","format":"objectId","description":"Reference to the file definition resource."}}},"aggregation":{"type":"object","description":"Controls how a flow run's records are batched into output files — the transfer form's\n\"How many files would you like to generate?\" setting. The legacy `skipAggregation` flag\nmirrors `type` and is maintained by the platform.","properties":{"type":{"type":"string","enum":["all","page","key"],"description":"Which batches of a run's records become separate output files. EDI file definitions\n(`file.type: filedefinition`) are written per page or per key, never per run; `key`\nis accepted only for them and only when the account has B2B Manager. The grouping\nkey and the line-item loop it merges come from the EDI file definition's document\ntype, so there is no separate key field to configure."}}},"lookups":{"type":"array","description":"Named value-substitution maps available to this import's field mappings; each lookup\ntranslates source values to target values, with an optional default for unmatched values.\nReferenced by name from mapping rules.","items":{"type":"object","properties":{"name":{"type":"string","description":"Identifier used to reference this lookup from mapping rules."},"map":{"type":"object","description":"Source-value to target-value pairs applied during mapping.","additionalProperties":{"type":"string"}},"default":{"type":["string","null"],"description":"Value substituted when a source value is not found in map; empty string when omitted. May be stored as null (no default)."},"allowFailures":{"type":"boolean","description":"When true, a value missing from map does not fail the record and the default (or original value) is used."}}}},"pgp":{"type":"object","description":"PGP encryption settings applied when encrypt is true; selects the symmetric cipher and the\nsigning hash. The connection must hold the recipient's public key.","properties":{"symmetricKeyAlgorithm":{"type":"string","enum":["twofish","cast5","3des","aes128","aes192","aes256"],"default":"aes256","description":"Symmetric cipher used to encrypt the file body; aes256 unless the recipient requires another."},"hashAlgorithm":{"type":"string","enum":["sha256","sha384","sha512","sha224"],"description":"Hash algorithm used when signing the encrypted file; set only when the recipient requires a specific one."}}},"skipRename":{"type":"boolean","description":"When true, the import writes directly to the final file name instead of writing to a\ntemporary name and renaming on completion. Use only when the destination does not support\natomic rename; the default (false) uses the safe write-then-rename behavior."},"appendToExistingFile":{"type":"boolean","default":false,"description":"When true, an Azure Data Lake Storage Gen2 import (`assistant: azuredatalakestoragegen2`)\nappends each run's CSV output to the file named by `file.fileName` instead of replacing\nit. Appended content cannot be compressed or encrypted, so leave `compressionFormat`,\n`encrypt` and `pgp` unset when this is on."},"contentType":{"type":"string","description":"MIME type recorded on the file written to a cloud file provider such as Azure Data Lake\nStorage Gen2. Accepts a Handlebars expression so the type can follow the record (e.g.\n`{{record.mimeType}}`). Omit to let the destination assign its default."},"directory":{"type":"object","required":["pathMode"],"description":"Structured destination location for cloud file-provider imports (Google Drive shared\ndrives, Box, Dropbox, Google Cloud Storage, Azure Data Lake Storage Gen2), replacing the\nflat path used by classic FTP/S3 destinations. Select the location by folder ID or by a\npath relative to a configured storage root via pathMode. For the drive-style providers,\ndirectoryId mode addresses the folder by id alone — the flat destination-path field is\nnot consulted at runtime. Google Cloud Storage puts the bucket in `storageRootId`;\nAzure Data Lake Storage Gen2 puts the container in `name` and the directory prefix of the\nfile path in `http.relativeURI`.","properties":{"pathMode":{"type":"string","enum":["relativePath","directoryId"],"description":"How the directory is addressed — by provider folder ID, or by a path relative to a configured storage root."},"id":{"type":"string","description":"Provider-native folder ID of the destination directory (e.g. a Google Drive folder ID);\nset when pathMode is directoryId. This is the file provider's own ID, not a Celigo resource ID."},"name":{"type":"string","description":"Display name of the folder identified by id, kept for readability in the UI; the\nfolder browser fills it when the location is picked visually. For Azure Data Lake\nStorage Gen2 it is the authoritative container name instead — the form saves the\ncontainer here and no id."},"storageRootId":{"type":"string","description":"Provider-native ID of the storage root the relative path resolves against; set when\npathMode is relativePath. The provider's own ID, not a Celigo resource ID. For Google\nCloud Storage it is the bucket name."},"storageRootName":{"type":"string","description":"Display name of the storage root identified by storageRootId, retained for reference in the UI."}}},"backupDirectory":{"type":"object","required":["pathMode"],"description":"Structured location for backup copies on cloud file providers — the file-provider\ncounterpart to backupPath. Addressed the same way as directory.","properties":{"pathMode":{"type":"string","enum":["relativePath","directoryId"],"description":"How the backup location is addressed — by provider folder ID, or by a path relative to a configured storage root."},"id":{"type":"string","description":"Provider-native folder ID of the backup directory; set when pathMode is directoryId."},"name":{"type":"string","description":"Display name of the backup folder identified by id, kept for readability in the UI;\nthe folder browser fills it when the location is picked visually."},"storageRootId":{"type":"string","description":"Provider-native ID of the storage root the backup relative path resolves against; set when pathMode is relativePath."}}}}},"FileSystem":{"type":"object","description":"Defines where files are written to a local or mounted folder on the host running the\non-premise agent. Required when the _connectionId field references a file-system (on-premise)\nconnection; must not be included for other connection types.","required":["directoryPath"],"properties":{"directoryPath":{"type":"string","description":"Folder on the on-premise agent's host where generated files are written; the agent's OS\naccount must have write permission on it. Accepts a local OS path or a UNC network share,\nand supports handlebars templates for dynamic folders."}}},"AiAgentConfig":{"type":"object","description":"AI agent configuration for an AiAgentImport. An `ai_agent` guardrail is configured by\nthe guardrail spec's `GuardrailAiAgentConfig` instead, which has no `tools` arrays.\n\nConfigures which AI provider and model to use, along with instructions, parameter\ntuning, output format, and available tools. Providers come in two families with\ntwo configuration formats:\n\n- **Built-in providers** — each has its own configuration block.\n  - **openai**: OpenAI models (GPT-4.1, GPT-5, etc.). Configure via the `openai` object.\n  - **gemini**: Google Gemini models via the LiteLLM proxy. Configure via `litellm` with overrides in `litellm._overrides.gemini`.\n  - **anthropic**: Anthropic Claude models via the LiteLLM proxy. Configure via `litellm` with overrides in `litellm._overrides.anthropic`.\n- **Catalog providers** — `mistral`, `xai`, `huggingface`, `deepseek`, `cohere`, `groq`. Added\n  as catalog data rather than platform code, they share one generic flat format:\n  `model`, `modelOptions`, `instructions`, `output`, and the `tools` / `prompts` /\n  `resources` arrays directly on this object.\n\nA `_connectionId` on the parent import selects bring-your-own-key (BYOK)\ncredentials. Without one, built-in providers run on platform-managed\ncredentials; catalog providers have no platform-managed credentials, so the\nsave requires one — without it the agent fails to save with 422\n`connection_required` on `_connectionId`.\n","required":["provider"],"properties":{"provider":{"type":"string","enum":["openai","gemini","anthropic","mistral","xai","huggingface","deepseek","cohere","groq"],"description":"AI provider to use."},"model":{"type":"string","description":"Model identifier for a catalog provider (generic flat format) — for example\n`grok-4.6`. Built-in providers set the model inside their own block\n(`openai.model`, `litellm.model`) instead."},"modelOptions":{"type":"object","description":"Model tuning for a catalog provider (generic flat format). Which keys, values, and\nranges are accepted is defined per catalog model and validated on save when the\nmodel is a catalog entry.","properties":{"maxOutputTokens":{"type":"number","description":"Maximum number of tokens the model may generate."},"temperature":{"type":"number","description":"Sampling temperature."},"topP":{"type":"number","description":"Nucleus sampling threshold."},"reasoning_effort":{"type":"string","enum":["low","medium","high","xhigh"],"description":"Reasoning depth for models that expose it."}}},"instructions":{"type":"string","description":"System prompt for a catalog provider (generic flat format). Built-in providers\nset it inside their own block (`openai.instructions`,\n`litellm._overrides.anthropic.systemInstruction`)."},"output":{"type":"object","description":"Output format for a catalog provider (generic flat format). Built-in providers\nconfigure it inside their own block (`openai.output`, `litellm.responseFormat`).","properties":{"format":{"type":"object","description":"Controls the structure of the model's output.","properties":{"type":{"type":"string","enum":["text","json_schema"],"description":"Output type."},"name":{"type":"string","description":"Name of the JSON schema, for `json_schema` output."},"strict":{"type":"boolean","description":"When true, the model must conform exactly to `jsonSchema`."},"jsonSchema":{"type":"object","additionalProperties":true,"description":"JSON Schema the structured output must conform to, for `json_schema` output."}}},"verbose":{"type":"string","description":"Level of detail in the model's response, for models that expose it."}}},"tools":{"type":"array","description":"Tools available to a catalog-provider agent (generic flat format). Entries mirror\nthe built-in providers' tool entries: a Celigo Tool by `_toolId`, an MCP server by\n`_mcpConnectionId`, or a vendor web-search tool.","items":{"type":"object","properties":{"type":{"type":"string","enum":["tool","mcp","web_search"],"description":"Type of tool entry."},"tool":{"type":"object","description":"Reference to a Celigo Tool resource, used when type is \"tool\".","properties":{"_toolId":{"type":"string","format":"objectId","description":"The Celigo Tool to call."}}},"mcp":{"type":"object","description":"MCP server connection, used when type is \"mcp\".","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server."},"allowedTools":{"type":"array","description":"Specific tools to allow from the MCP server (all if omitted). Each entry\nis either a plain tool name or an object carrying display metadata.","items":{"type":["string","object"]}}}},"config":{"type":"object","additionalProperties":true,"description":"Tool-specific options for vendor-native tools (for example web-search settings)."}}}},"prompts":{"type":"array","description":"MCP prompt entries available to a catalog-provider agent (generic flat format).\nEach entry references one MCP connection and the prompt names allowed from it.","items":{"type":"object","properties":{"type":{"type":"string","enum":["mcp"],"description":"Type of prompt entry. Always \"mcp\"."},"mcp":{"type":"object","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server that exposes the prompts."},"allowedPrompts":{"type":"array","description":"Prompt names the agent may fetch from the server.","items":{"type":"string"}}}}}}},"resources":{"$ref":"#/components/schemas/McpResources"},"openai":{"type":"object","description":"OpenAI-specific configuration. Used when `provider` is \"openai\".\n","required":["model","instructions"],"properties":{"instructions":{"type":"string","maxLength":1000000,"description":"System prompt that defines the AI agent's behavior, goals, and constraints.\n"},"model":{"type":"string","description":"OpenAI model identifier. Open string (not an enum) — model names change frequently."},"reasoning":{"type":"object","description":"Controls depth of reasoning for complex tasks.","properties":{"effort":{"type":"string","enum":["none","minimal","low","medium","high","xhigh","max"],"description":"How much reasoning effort the model should invest"},"summary":{"type":"string","enum":["concise","auto","detailed"],"description":"Level of detail in reasoning summaries"}}},"temperature":{"type":"number","minimum":0,"maximum":2,"description":"Sampling temperature. Higher values (e.g. 1.5) produce more creative output,\nlower values (e.g. 0.2) produce more focused and deterministic output.\n"},"topP":{"type":"number","minimum":0.1,"maximum":1,"description":"Nucleus sampling parameter"},"topLogprobs":{"type":"number","minimum":0,"maximum":20,"description":"Number of most likely tokens to return log probabilities for at each output position."},"maxOutputTokens":{"type":"number","minimum":100,"maximum":128000,"default":5000,"description":"Caps the length of the model's response, in tokens. An agent created without it is\nsaved with 5000; raise it for long outputs such as large extractions."},"serviceTier":{"type":"string","enum":["auto","default","priority"],"default":"default","description":"OpenAI service tier. \"priority\" provides higher rate limits and\nlower latency at increased cost."},"output":{"type":"object","description":"Output format configuration","properties":{"format":{"type":"object","description":"Controls the structure of the model's output.\n","properties":{"type":{"type":"string","enum":["text","json_schema","blob"],"default":"text","description":"Output format type."},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `jsonSchema` in; the schema sent to the provider\nis the same either way, and the field is only meaningful when `type` is\n\"json_schema\". Unset opens the visual editor, and an edit there rebuilds\n`jsonSchema` from the field list, keeping only `type`, `properties`,\n`required`, `items`, `description`, `enum`, `pattern`, length and numeric\nbounds, `multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — other\nkeywords are dropped without a prompt, and `integer` becomes `number`."},"name":{"type":"string","description":"Name for the output format (used with json_schema)"},"strict":{"type":"boolean","default":false,"description":"When true, enforces strict schema validation on output."},"jsonSchema":{"type":"object","description":"JSON Schema for structured output. Required when `format.type` is \"json_schema\".\n","properties":{"type":{"type":"string","description":"Root JSON Schema type of the structured output; use \"object\" for record-shaped results.","enum":["object","array","string","number","integer","boolean"]},"properties":{"type":"object","additionalProperties":true,"description":"JSON Schema definitions for each field the structured output may contain."},"required":{"type":"array","description":"Property names the model must include in the structured output.","items":{"type":"string"}},"additionalProperties":{"type":"boolean","description":"When true, the structured output may include properties beyond those defined in `properties`."}}},"schema":{"type":"object","readOnly":true,"additionalProperties":true,"description":"Server-written copy of `jsonSchema`, returned whenever `jsonSchema` is set — on\nAI agent imports and on guardrail agents alike. Send `jsonSchema`, not this field."}},"if":{"properties":{"type":{"const":"json_schema"}},"required":["type"]},"then":{"required":["name","jsonSchema"]}},"verbose":{"type":"string","enum":["low","medium","high"],"default":"medium","description":"Level of detail in the model's response"}}},"tools":{"type":"array","description":"Tools available to the AI agent during processing.\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["web_search","mcp","image_generation","code_execution","tool"],"description":"Type of tool. Every type except `code_execution` is configured through the\nsibling object of the same name; OpenAI spells this one `code_execution`, while\nthe Gemini and Anthropic overrides spell theirs `codeExecution` — the other spelling\nfails with 422 `enum`."},"webSearch":{"type":"object","description":"Web search configuration (empty object to enable)"},"imageGeneration":{"type":"object","description":"Image generation configuration","properties":{"background":{"type":"string","description":"Controls whether generated images have a transparent or opaque background; use transparent only with output formats that support it (png, webp).","enum":["transparent","opaque"]},"quality":{"type":"string","description":"Rendering quality of generated images, trading detail for generation speed and file size.","enum":["low","medium","high"]},"size":{"type":"string","description":"Pixel dimensions of generated images; choose square, portrait, or landscape to match the intended use.","enum":["1024x1024","1024x1536","1536x1024"]},"outputFormat":{"type":"string","description":"File format of generated images; use png or webp when transparency is needed.","enum":["png","webp","jpeg"]}}},"mcp":{"type":"object","description":"MCP server tool configuration","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server"},"allowedTools":{"type":"array","description":"Specific tools to allow from the MCP server (all if\nomitted). Each entry is either a plain tool name (legacy\nform) or an object carrying display metadata.","items":{"type":["string","object"],"properties":{"name":{"type":"string","maxLength":256,"description":"Tool name as exposed by the MCP server."},"title":{"type":"string","maxLength":300,"description":"Display title shown for the tool."},"description":{"type":"string","maxLength":1000,"description":"Display description shown for the tool."}},"required":["name"]}}}},"tool":{"type":"object","description":"Reference to a Celigo Tool resource.\n","properties":{"_toolId":{"type":"string","format":"objectId","description":"Reference to the Tool resource"},"overrides":{"type":"object","description":"Per-agent overrides for the tool's internal resources","properties":{"connections":{"type":"array","description":"Remaps the tool's abstract connections for this agent. Each entry pairs\nthe tool's abstract connection placeholder (`_abstractId`) with the\nconcrete connection (`_id`) to use for this agent; entries without\n`_id` keep the tool's own default connection.\n","items":{"type":["object","null"],"required":["_abstractId"],"properties":{"_abstractId":{"type":"string","format":"objectId","description":"The tool's abstract connection placeholder being overridden."},"_id":{"type":"string","format":"objectId","description":"Concrete connection to use in place of the abstract placeholder."}}}}}}}}}}},"prompts":{"type":"array","description":"MCP prompt entries available to the agent. Each item references one MCP connection\nand the prompt names allowed from it. Configured alongside `tools` in the form but\nstored separately; an entry's `allowedPrompts` is what distinguishes a prompt entry\nfrom an MCP tool entry (which carries `allowedTools`).\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["mcp"],"description":"Type of prompt entry. Always \"mcp\"."},"mcp":{"type":"object","description":"MCP server prompt configuration.","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server."},"allowedPrompts":{"type":"array","description":"Prompt names to allow from the MCP server.","items":{"type":"string"}}}}}}},"resources":{"$ref":"#/components/schemas/McpResources"}}},"litellm":{"type":"object","description":"LiteLLM proxy configuration. Used when `provider` is \"gemini\" or \"anthropic\".\n\nLiteLLM provides a unified interface to multiple AI providers. Gemini-specific\nsettings are in `_overrides.gemini`; Claude-specific settings are in\n`_overrides.anthropic`.\n\n`model` is required when litellm is the active provider path.\n","properties":{"model":{"type":"string","description":"LiteLLM model identifier. For Gemini, models are stored without the `gemini/`\nprefix; for Anthropic, use the Claude model id (e.g. `claude-sonnet-4-6`)."},"temperature":{"type":"number","minimum":0,"maximum":2,"description":"Sampling temperature"},"maxCompletionTokens":{"type":"number","minimum":100,"maximum":128000,"default":5000,"description":"Maximum number of tokens in the response"},"topP":{"type":"number","minimum":0.1,"maximum":1,"description":"Nucleus sampling parameter"},"seed":{"type":"number","description":"Random seed for reproducible outputs"},"responseFormat":{"type":"object","description":"Output format configuration","properties":{"type":{"type":"string","description":"Output format type.","enum":["text","json_schema","blob"],"default":"text"},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `jsonSchema` in; the schema sent to the provider is\nthe same either way, and the field is only meaningful when `type` is\n\"json_schema\". Unset opens the visual editor, and an edit there rebuilds\n`jsonSchema` from the field list, keeping only `type`, `properties`, `required`,\n`items`, `description`, `enum`, `pattern`, length and numeric bounds,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — other keywords are\ndropped without a prompt, and `integer` becomes `number`."},"name":{"type":"string","description":"Name for the output format (used with json_schema)."},"strict":{"type":"boolean","description":"When true, enforces strict schema validation on output.","default":false},"jsonSchema":{"type":"object","description":"JSON Schema for structured output. Required when `responseFormat.type` is \"json_schema\".","properties":{"type":{"type":"string","description":"Root JSON Schema type of the structured output; use \"object\" for record-shaped results.","enum":["object","array","string","number","integer","boolean"]},"properties":{"type":"object","additionalProperties":true,"description":"JSON Schema definitions for each field the structured output may contain."},"required":{"type":"array","description":"Property names the model must include in the structured output.","items":{"type":"string"}},"additionalProperties":{"type":"boolean","description":"When true, the structured output may include properties beyond those defined in `properties`."}}}},"if":{"properties":{"type":{"const":"json_schema"}},"required":["type"]},"then":{"required":["name","jsonSchema"]}},"_overrides":{"type":"object","description":"Provider-specific overrides","properties":{"gemini":{"type":"object","description":"Gemini-specific configuration overrides.\n","required":["systemInstruction"],"properties":{"systemInstruction":{"type":"string","maxLength":1000000,"description":"System instruction for Gemini models. Equivalent to OpenAI's `instructions`.\nMaximum 1,000,000 characters.\n"},"tools":{"type":"array","description":"Gemini-specific tools","items":{"type":"object","properties":{"type":{"type":"string","enum":["googleSearch","urlContext","fileSearch","codeExecution","mcp","tool"],"description":"Type of Gemini tool. `codeExecution` is the Gemini spelling — `code_execution`\nfails with 422 `enum`."},"googleSearch":{"type":"object","description":"Google Search configuration (empty object to enable)"},"urlContext":{"type":"object","description":"URL context configuration (empty object to enable)"},"fileSearch":{"type":"object","description":"File search configuration, used when type is \"fileSearch\".","properties":{"fileSearchStoreNames":{"type":"array","description":"Names of the file search stores the model can query.","items":{"type":"string"}}}},"mcp":{"type":"object","description":"MCP server tool configuration, used when type is \"mcp\".","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server."},"allowedTools":{"type":"array","description":"Specific tools to allow from the MCP server (all if\nomitted). Each entry is either a plain tool name\n(legacy form) or an object carrying display metadata\n— same contract as the OpenAI `allowedTools`.","items":{"type":["string","object"],"properties":{"name":{"type":"string","maxLength":256,"description":"Tool name as exposed by the MCP server."},"title":{"type":"string","maxLength":300,"description":"Display title shown for the tool."},"description":{"type":"string","maxLength":1000,"description":"Display description shown for the tool."}},"required":["name"]}}}},"tool":{"type":"object","description":"Reference to a Celigo Tool resource, used when type is \"tool\".","properties":{"_toolId":{"type":"string","format":"objectId","description":"Reference to the Tool resource."},"overrides":{"type":"object","description":"Per-agent overrides for the tool's internal resources.","properties":{"connections":{"type":"array","description":"Remaps the tool's abstract connections for this agent. Each entry\npairs the tool's abstract connection placeholder (`_abstractId`)\nwith the concrete connection (`_id`) to use for this agent;\nentries without `_id` keep the tool's own default connection.\n","items":{"type":["object","null"],"required":["_abstractId"],"properties":{"_abstractId":{"type":"string","format":"objectId","description":"The tool's abstract connection placeholder being overridden."},"_id":{"type":"string","format":"objectId","description":"Concrete connection to use in place of the abstract placeholder."}}}}}}}}}}},"prompts":{"type":"array","description":"MCP prompt entries available to the Gemini agent. Each item references one\nMCP connection and the prompt names allowed from it. The presence of\n`allowedPrompts` distinguishes a prompt entry from an MCP tool entry.\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["mcp"],"description":"Type of prompt entry. Always \"mcp\"."},"mcp":{"type":"object","description":"MCP server prompt configuration.","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server."},"allowedPrompts":{"type":"array","description":"Prompt names to allow from the MCP server.","items":{"type":"string"}}}}}}},"resources":{"$ref":"#/components/schemas/McpResources"},"responseModalities":{"type":"array","description":"Response output modalities","items":{"type":"string","enum":["text","image"]},"default":["text"]},"topK":{"type":"number","description":"Top-K sampling parameter for Gemini"},"thinkingConfig":{"type":"object","description":"Controls Gemini's extended thinking capabilities","properties":{"includeThoughts":{"type":"boolean","description":"When true, includes the model's thinking steps in the response."},"thinkingBudget":{"type":"number","minimum":100,"maximum":4000,"description":"Maximum tokens allocated for thinking"},"thinkingLevel":{"type":"string","description":"Controls how much thinking effort the model applies; use higher levels for complex, multi-step tasks at the cost of latency and tokens.","enum":["minimal","low","medium","high"]}}},"imageConfig":{"type":"object","description":"Gemini image generation configuration","properties":{"aspectRatio":{"type":"string","description":"Aspect ratio of generated images; choose a ratio matching the intended display format.","enum":["1:1","2:3","3:2","3:4","4:3","4:5","5:4","9:16","16:9","21:9"]},"imageSize":{"type":"string","description":"Output resolution of generated images; higher resolutions increase detail and file size.","enum":["1K","2K","4K"]}}},"mediaResolution":{"type":"string","enum":["low","medium","high"],"description":"Resolution for media inputs (images, video)"}}},"anthropic":{"type":"object","description":"Claude-specific configuration overrides. Used when `provider` is \"anthropic\".\n","required":["systemInstruction"],"properties":{"systemInstruction":{"type":"string","maxLength":1000000,"description":"System instruction for Claude models. Equivalent to OpenAI's `instructions`.\nMaximum 1,000,000 characters.\n"},"topK":{"type":"number","minimum":0,"description":"Top-K sampling parameter. Deprecated on Claude models released after Claude Opus\n4.6, which reject any value at runtime; set it only on older models."},"thinkingConfig":{"type":"object","description":"Controls Claude's extended thinking capabilities.","properties":{"type":{"type":"string","enum":["enabled","disabled","adaptive"],"default":"disabled","description":"Extended thinking mode."},"budgetTokens":{"type":"number","minimum":1024,"description":"Maximum tokens allocated for thinking. Required when `type` is \"enabled\" —\nomitting it fails the save with 422 `invalid_thinking_config`."},"display":{"type":"string","enum":["summarized","omitted"],"default":"summarized","description":"How thinking output is surfaced in the response."},"effort":{"type":"string","enum":["low","medium","high","xhigh","max"],"description":"How much thinking effort the model applies; use with `type` \"adaptive\". Higher\nvalues (`xhigh`, `max`) may be gated to specific Claude models by the provider."}},"if":{"properties":{"type":{"const":"enabled"}},"required":["type"]},"then":{"required":["budgetTokens"]}},"serviceTier":{"type":"string","enum":["auto","standard_only"],"default":"auto","description":"Anthropic service tier for the request."},"tools":{"type":"array","description":"Claude-specific tools.","items":{"type":"object","properties":{"type":{"type":"string","enum":["tool","mcp","webSearch","codeExecution"],"description":"Type of Claude tool. `codeExecution` is the Anthropic spelling — `code_execution`\nfails with 422 `enum`."},"tool":{"type":"object","description":"Reference to a Celigo Tool resource, used when type is \"tool\".","properties":{"_toolId":{"type":"string","format":"objectId","description":"Reference to the Tool resource."},"overrides":{"type":"object","description":"Per-agent overrides for the tool's internal resources.","properties":{"connections":{"type":"array","description":"Remaps the tool's abstract connections for this agent. Each entry\npairs the tool's abstract connection placeholder (`_abstractId`)\nwith the concrete connection (`_id`) to use for this agent;\nentries without `_id` keep the tool's own default connection.\n","items":{"type":["object","null"],"required":["_abstractId"],"properties":{"_abstractId":{"type":"string","format":"objectId","description":"The tool's abstract connection placeholder being overridden."},"_id":{"type":"string","format":"objectId","description":"Concrete connection to use in place of the abstract placeholder."}}}}}}}},"mcp":{"type":"object","description":"MCP server tool configuration, used when type is \"mcp\".","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server."},"allowedTools":{"type":"array","description":"Specific tools to allow from the MCP server (all if omitted). Each\nentry is either a plain tool name or an object carrying display\nmetadata — same contract as the OpenAI and Gemini `allowedTools`.","items":{"oneOf":[{"title":"name","type":"string","description":"Tool name as exposed by the MCP server."},{"title":"tool","type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":256,"description":"Tool name as exposed by the MCP server."},"title":{"type":"string","maxLength":300,"description":"Display title shown for the tool."},"description":{"type":"string","maxLength":1000,"description":"Display description shown for the tool."}}}]}}}},"webSearch":{"type":"object","description":"Web search configuration, used when type is \"webSearch\".","properties":{"version":{"type":"string","pattern":"^\\d{8}$","description":"Anthropic web search tool version (YYYYMMDD). Selects the tool version sent\non the wire; unsupported versions surface as an Anthropic 400."},"allowedDomains":{"type":"array","description":"Domains the search may return results from. Mutually exclusive with `blockedDomains`.","items":{"type":"string"}},"blockedDomains":{"type":"array","description":"Domains to exclude from search results. Mutually exclusive with `allowedDomains`.","items":{"type":"string"}},"userLocation":{"type":"object","description":"Approximate user location used to localize search results. When present, at\nleast one of `city`, `country`, `region`, or `timezone` must be set.","properties":{"type":{"type":"string","enum":["approximate"],"description":"Location type. Always \"approximate\"."},"city":{"type":"string","maxLength":256,"description":"City name for localizing search results."},"country":{"type":"string","maxLength":8,"description":"ISO 3166-1 alpha-2 country code for localizing search results."},"region":{"type":"string","maxLength":256,"description":"Region or state for localizing search results."},"timezone":{"type":"string","maxLength":64,"description":"IANA timezone for localizing search results."}}}}}}}},"prompts":{"type":"array","description":"MCP prompt entries available to the Claude agent. Each item references one MCP\nconnection and the prompt names allowed from it. The presence of `allowedPrompts`\ndistinguishes a prompt entry from an MCP tool entry.\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["mcp"],"description":"Type of prompt entry. Always \"mcp\"."},"mcp":{"type":"object","description":"MCP server prompt configuration.","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server."},"allowedPrompts":{"type":"array","description":"Prompt names to allow from the MCP server.","items":{"type":"string"}}}}}}},"resources":{"$ref":"#/components/schemas/McpResources"}}}}}}}},"if":{"properties":{"provider":{"const":"openai"}},"required":["provider"]},"then":{"required":["openai"],"properties":{"openai":{"required":["model","instructions"]}}},"else":{"if":{"properties":{"provider":{"enum":["gemini","anthropic"]}},"required":["provider"]},"then":{"required":["litellm"],"properties":{"litellm":{"required":["model"]}}},"else":{"if":{"properties":{"provider":{"enum":["mistral","xai","huggingface","deepseek","cohere","groq"]}},"required":["provider"]},"then":{"required":["model"]}}}},"McpResources":{"type":"array","description":"Governed MCP resources — read-only reference content (policies, schemas, documentation)\npulled from connected MCP servers and made available to the agent as a consistent source\nof truth. Each entry references one MCP connection and the specific resources allowed from it.","items":{"type":"object","required":["type","mcp"],"properties":{"type":{"type":"string","enum":["mcp"],"description":"Type of resource entry. Always \"mcp\"."},"mcp":{"type":"object","required":["_mcpConnectionId","allowedResources"],"description":"MCP server resource configuration.","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server that exposes the resources."},"allowedResources":{"type":"array","minItems":1,"description":"Resources to expose to the agent from the MCP server. Must contain at least one\nentry; each entry identifies one resource by name and URI.","items":{"type":"object","required":["name","uri"],"properties":{"name":{"type":"string","description":"Display name of the MCP resource."},"uri":{"type":"string","description":"URI that identifies the resource on the MCP server."}}}}}}}}},"GuardrailConfig":{"type":"object","description":"Configuration for GuardrailImport adaptor type.\n\nGuardrails evaluate data flowing through integrations for safety and\ncompliance. The `type` field selects which check to apply, and the\ncorresponding sub-object (`aiAgent`, `pii`, or `moderation`) provides\nthe configuration.\n\nA `_connectionId` on the parent import is only needed for BYOK\n`ai_agent` guardrails. In responses the server echoes the active type's\nsub-object and applies the `confidenceThreshold` default; it also\nreturns inactive sibling sub-objects (e.g. `moderation: {categories: []}`\non a `pii` guardrail, or a populated `pii` left over from a type switch),\nbut only the active type's sub-object is meaningful. Legacy documents\nmay carry a server-written default `aiAgent` stub on `pii`/`moderation`\nguardrails; current servers strip the inactive `aiAgent` on write.","properties":{"type":{"type":"string","enum":["ai_agent","pii","moderation"],"description":"The type of guardrail to apply. Each type requires its corresponding\nsub-configuration object (`aiAgent`, `pii`, or `moderation`)."},"confidenceThreshold":{"type":"number","minimum":0,"maximum":1,"default":0.7,"description":"Confidence threshold (0 to 1). Detections below this threshold are\nignored. Lower values catch more issues but increase false positives."},"aiAgent":{"type":"object","description":"AI agent check configuration; set when `type` is `ai_agent`. On\n`pii`/`moderation` guardrails a legacy server-written stub may\nappear here — it is inert, and current servers strip it on write."},"pii":{"type":"object","required":["entities"],"description":"PII detection configuration. Required when `type` is `pii`.","properties":{"entities":{"type":"array","description":"PII entity types to detect. When `type` is `pii`, at least one\nentry is required; the inactive sibling on other guardrail types\nmay be served with an empty list.","items":{"type":"string","enum":["credit_card_number","card_security_code_cvv_cvc","cryptocurrency_wallet_address","date_and_time","email_address","iban_code","bic_swift_bank_identifier_code","ip_address","location","medical_license_number","national_registration_number","persons_name","phone_number","url","us_bank_account_number","us_drivers_license","us_itin","us_passport_number","us_social_security_number","uk_nhs_number","uk_national_insurance_number","spanish_nif","spanish_nie","italian_fiscal_code","italian_drivers_license","italian_vat_code","italian_passport","italian_identity_card","polish_pesel","finnish_personal_identity_code","singapore_nric_fin","singapore_uen","australian_abn","australian_acn","australian_tfn","australian_medicare","indian_pan","indian_aadhaar","indian_vehicle_registration","indian_voter_id","indian_passport","korean_resident_registration_number"]}},"mask":{"type":"boolean","default":false,"description":"When true, the result also carries `masked`: the evaluated input with each detected\nvalue replaced by asterisks. The record itself is unchanged. With whole-record input\n(no `mapping`, and no top-level `text` field on the record), `masked` is the record\nserialized as one JSON string. When false, PII is flagged only."}}},"moderation":{"type":"object","required":["categories"],"description":"Content moderation configuration. Required when `type` is `moderation`.","properties":{"categories":{"type":"array","description":"Content moderation categories to check. When `type` is\n`moderation`, at least one entry is required; the inactive\nsibling on other guardrail types may be served with an empty\nlist.","items":{"type":"string","enum":["sexual","sexual_minors","hate","hate_threatening","harassment","harassment_threatening","self_harm","self_harm_intent","self_harm_instructions","violence","violence_graphic","illicit","illicit_violent"]}}}}},"required":["type","confidenceThreshold"],"if":{"required":["type"],"properties":{"type":{"const":"pii"}}},"then":{"required":["pii"],"properties":{"pii":{"required":["entities"],"properties":{"entities":{"minItems":1}}}}},"else":{"if":{"required":["type"],"properties":{"type":{"const":"moderation"}}},"then":{"required":["moderation"],"properties":{"moderation":{"required":["categories"],"properties":{"categories":{"minItems":1}}}}},"else":{"required":["aiAgent"],"properties":{"aiAgent":{"$ref":"#/components/schemas/GuardrailAiAgentConfig"}}}}},"GuardrailAiAgentConfig":{"type":"object","description":"LLM configuration for an `ai_agent` guardrail — which provider and model evaluate each\nrecord, with instructions, parameter tuning, and output format. Guardrails never invoke\ntools, so unlike an AI agent import no block here accepts a `tools` array. MCP `prompts`\nand `resources` are accepted on save but never used at run time, so put the policy the\nguardrail must apply in its instructions. Providers come in two families with two\nconfiguration formats:\n\n- **Built-in providers** — each has its own configuration block.\n  - **openai**: OpenAI models (GPT-4.1, GPT-5, etc.). Configure via the `openai` object.\n  - **gemini**: Google Gemini models via the LiteLLM proxy. Configure via `litellm` with overrides in `litellm._overrides.gemini`.\n  - **anthropic**: Anthropic Claude models via the LiteLLM proxy. Configure via `litellm` with overrides in `litellm._overrides.anthropic`.\n- **Catalog providers** — `mistral`, `xai`, `huggingface`, `deepseek`, `cohere`, `groq`. Added\n  as catalog data rather than platform code, they share one generic flat format:\n  `model`, `modelOptions`, `instructions`, `output`, and the `prompts` / `resources`\n  arrays directly on this object.\n\nA `_connectionId` on the parent import selects bring-your-own-key (BYOK)\ncredentials. Without one, built-in providers run on platform-managed\ncredentials; catalog providers have no platform-managed credentials, so the\nsave requires one — without it the guardrail fails to save with 422\n`connection_required` on `_connectionId`.\n","required":["provider"],"properties":{"provider":{"type":"string","enum":["openai","gemini","anthropic","mistral","xai","huggingface","deepseek","cohere","groq"],"description":"AI provider to use."},"model":{"type":"string","description":"Model identifier for a catalog provider (generic flat format) — for example\n`grok-4.6`. Built-in providers set the model inside their own block\n(`openai.model`, `litellm.model`) instead."},"modelOptions":{"type":"object","description":"Model tuning for a catalog provider (generic flat format). Which keys, values, and\nranges are accepted is defined per catalog model and validated on save when the\nmodel is a catalog entry.","properties":{"maxOutputTokens":{"type":"number","description":"Maximum number of tokens the model may generate."},"temperature":{"type":"number","description":"Sampling temperature."},"topP":{"type":"number","description":"Nucleus sampling threshold."},"reasoning_effort":{"type":"string","enum":["low","medium","high","xhigh"],"description":"Reasoning depth for models that expose it."}}},"instructions":{"type":"string","description":"System prompt for a catalog provider (generic flat format). Built-in providers\nset it inside their own block (`openai.instructions`,\n`litellm._overrides.anthropic.systemInstruction`)."},"output":{"type":"object","description":"Output format for a catalog provider (generic flat format). Built-in providers\nconfigure it inside their own block (`openai.output`, `litellm.responseFormat`).","properties":{"format":{"type":"object","description":"Controls the structure of the model's output.","properties":{"type":{"type":"string","enum":["text","json_schema"],"description":"Output type."},"name":{"type":"string","description":"Name of the JSON schema, for `json_schema` output."},"strict":{"type":"boolean","description":"When true, the model must conform exactly to `jsonSchema`."},"jsonSchema":{"type":"object","additionalProperties":true,"description":"JSON Schema the structured output must conform to, for `json_schema` output."}}},"verbose":{"type":"string","description":"Level of detail in the model's response, for models that expose it."}}},"prompts":{"type":"array","description":"MCP prompt entries saved with a catalog-provider guardrail (generic flat format).\nEach entry references one MCP connection and prompt names; a guardrail's run never fetches them.","items":{"type":"object","properties":{"type":{"type":"string","enum":["mcp"],"description":"Type of prompt entry. Always \"mcp\"."},"mcp":{"type":"object","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server that exposes the prompts."},"allowedPrompts":{"type":"array","description":"Prompt names saved with the entry; a guardrail's run never fetches them.","items":{"type":"string"}}}}}}},"resources":{"$ref":"#/components/schemas/McpResources"},"openai":{"type":"object","description":"OpenAI-specific configuration. Used when `provider` is \"openai\".\n","required":["model","instructions"],"properties":{"instructions":{"type":"string","maxLength":1000000,"description":"System prompt that defines the check's behavior, goals, and constraints.\n"},"model":{"type":"string","description":"OpenAI model identifier. Open string (not an enum) — model names change frequently."},"reasoning":{"type":"object","description":"Controls depth of reasoning for complex tasks.","properties":{"effort":{"type":"string","enum":["none","minimal","low","medium","high","xhigh","max"],"description":"How much reasoning effort the model should invest"},"summary":{"type":"string","enum":["concise","auto","detailed"],"description":"Level of detail in reasoning summaries"}}},"temperature":{"type":"number","minimum":0,"maximum":2,"description":"Sampling temperature. Higher values (e.g. 1.5) produce more creative output,\nlower values (e.g. 0.2) produce more focused and deterministic output.\n"},"topP":{"type":"number","minimum":0.1,"maximum":1,"description":"Nucleus sampling parameter"},"topLogprobs":{"type":"number","minimum":0,"maximum":20,"description":"Number of most likely tokens to return log probabilities for at each output position."},"maxOutputTokens":{"type":"number","minimum":100,"maximum":128000,"description":"Maximum number of tokens in the model's response. Unlike an AI agent import, a guardrail does not receive a server-written value when this is omitted."},"serviceTier":{"type":"string","enum":["auto","default","priority"],"description":"OpenAI service tier. \"priority\" provides higher rate limits and\nlower latency at increased cost."},"output":{"type":"object","description":"Output format configuration. A guardrail stores `format` but doesn't use it: the\nrequest to the model always carries the guardrail's own `validation_result` JSON\nschema (`flagged`, `reasoning`, `confidence`), and the result returns `flagged` and\n`reasoning` whatever `format` holds.","properties":{"format":{"type":"object","description":"Stored output structure. The request to the model uses the guardrail's own\nverdict schema instead (see `output`).\n","properties":{"type":{"type":"string","enum":["text","json_schema","blob"],"description":"Output format type, as stored."},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which view a schema editor opens `jsonSchema` in. Stored and echoed on\nguardrails for parity with the AI-agent format block — the guardrail form\ndoes not set it today, and neither view changes the model request."},"name":{"type":"string","description":"Name for the output format (used with json_schema)"},"strict":{"type":"boolean","description":"Stored strict-validation flag for `jsonSchema`. A guardrail's model request uses its own verdict schema, so this doesn't apply there."},"jsonSchema":{"type":"object","description":"Stored JSON Schema. Required when `format.type` is \"json_schema\". A guardrail\ndoesn't send it to the model; the request carries the `validation_result`\nverdict schema instead.\n","properties":{"type":{"type":"string","description":"Root type of the stored JSON Schema.","enum":["object","array","string","number","integer","boolean"]},"properties":{"type":"object","additionalProperties":true,"description":"Field definitions in the stored JSON Schema."},"required":{"type":"array","description":"Property names the stored JSON Schema marks as required.","items":{"type":"string"}},"additionalProperties":{"type":"boolean","description":"Whether the stored JSON Schema allows properties beyond those in `properties`."}}},"schema":{"type":"object","readOnly":true,"additionalProperties":true,"description":"Server-written copy of `jsonSchema`, present in responses once `jsonSchema`\nis set. Send `jsonSchema`, not this field."}},"if":{"properties":{"type":{"const":"json_schema"}},"required":["type"]},"then":{"required":["name","jsonSchema"]}},"verbose":{"type":"string","enum":["low","medium","high"],"description":"Level of detail in the model's response"}}},"prompts":{"type":"array","description":"MCP prompt entries saved with the guardrail. Each item references one\nMCP connection and prompt names; a guardrail's run never fetches them.","items":{"type":"object","properties":{"type":{"type":"string","enum":["mcp"],"description":"Type of prompt entry. Always \"mcp\"."},"mcp":{"type":"object","description":"MCP server prompt configuration.","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server."},"allowedPrompts":{"type":"array","description":"Prompt names saved with the entry; a guardrail's run never fetches them.","items":{"type":"string"}}}}}}},"resources":{"$ref":"#/components/schemas/McpResources"}}},"litellm":{"type":"object","description":"LiteLLM proxy configuration. Used when `provider` is \"gemini\" or \"anthropic\".\n\nLiteLLM provides a unified interface to multiple AI providers. Gemini-specific\nsettings are in `_overrides.gemini`; Claude-specific settings are in\n`_overrides.anthropic`.\n\n`model` is required when litellm is the active provider path.\n","properties":{"model":{"type":"string","description":"LiteLLM model identifier. For Gemini, models are stored without the `gemini/`\nprefix; for Anthropic, use the Claude model id (e.g. `claude-sonnet-4-6`)."},"temperature":{"type":"number","minimum":0,"maximum":2,"description":"Sampling temperature"},"maxCompletionTokens":{"type":"number","minimum":100,"maximum":128000,"description":"Maximum number of tokens in the response"},"topP":{"type":"number","minimum":0.1,"maximum":1,"description":"Nucleus sampling parameter"},"seed":{"type":"number","description":"Random seed for reproducible outputs"},"responseFormat":{"type":"object","description":"Output format configuration","properties":{"type":{"type":"string","description":"Output format type.","enum":["text","json_schema","blob"]},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which view a schema editor opens `jsonSchema` in; the schema sent to the provider\nis the same either way. Stored and echoed on guardrails for parity with the\nAI-agent format block — the guardrail form does not set it today."},"name":{"type":"string","description":"Name for the output format (used with json_schema)."},"strict":{"type":"boolean","description":"When true, enforces strict schema validation on output."},"jsonSchema":{"type":"object","description":"JSON Schema for structured output. Required when `responseFormat.type` is \"json_schema\".","properties":{"type":{"type":"string","description":"Root JSON Schema type of the structured output; use \"object\" for record-shaped results.","enum":["object","array","string","number","integer","boolean"]},"properties":{"type":"object","additionalProperties":true,"description":"JSON Schema definitions for each field the structured output may contain."},"required":{"type":"array","description":"Property names the model must include in the structured output.","items":{"type":"string"}},"additionalProperties":{"type":"boolean","description":"When true, the structured output may include properties beyond those defined in `properties`."}}}},"if":{"properties":{"type":{"const":"json_schema"}},"required":["type"]},"then":{"required":["name","jsonSchema"]}},"_overrides":{"type":"object","description":"Provider-specific overrides","properties":{"gemini":{"type":"object","description":"Gemini-specific configuration overrides.\n","required":["systemInstruction"],"properties":{"systemInstruction":{"type":"string","maxLength":1000000,"description":"System instruction for Gemini models. Equivalent to OpenAI's `instructions`.\nMaximum 1,000,000 characters.\n"},"prompts":{"type":"array","description":"MCP prompt entries saved with a Gemini guardrail. Each item references one\nMCP connection and prompt names; a guardrail's run never fetches them.","items":{"type":"object","properties":{"type":{"type":"string","enum":["mcp"],"description":"Type of prompt entry. Always \"mcp\"."},"mcp":{"type":"object","description":"MCP server prompt configuration.","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server."},"allowedPrompts":{"type":"array","description":"Prompt names saved with the entry; a guardrail's run never fetches them.","items":{"type":"string"}}}}}}},"resources":{"$ref":"#/components/schemas/McpResources"},"responseModalities":{"type":"array","description":"Response output modalities","items":{"type":"string","enum":["text","image"]}},"topK":{"type":"number","description":"Top-K sampling parameter for Gemini"},"thinkingConfig":{"type":"object","description":"Controls Gemini's extended thinking capabilities","properties":{"includeThoughts":{"type":"boolean","description":"When true, includes the model's thinking steps in the response."},"thinkingBudget":{"type":"number","minimum":100,"maximum":4000,"description":"Maximum tokens allocated for thinking"},"thinkingLevel":{"type":"string","description":"Controls how much thinking effort the model applies; use higher levels for complex, multi-step tasks at the cost of latency and tokens.","enum":["minimal","low","medium","high"]}}},"imageConfig":{"type":"object","description":"Gemini image generation configuration","properties":{"aspectRatio":{"type":"string","description":"Aspect ratio of generated images; choose a ratio matching the intended display format.","enum":["1:1","2:3","3:2","3:4","4:3","4:5","5:4","9:16","16:9","21:9"]},"imageSize":{"type":"string","description":"Output resolution of generated images; higher resolutions increase detail and file size.","enum":["1K","2K","4K"]}}},"mediaResolution":{"type":"string","enum":["low","medium","high"],"description":"Resolution for media inputs (images, video)"}}},"anthropic":{"type":"object","description":"Claude-specific configuration overrides. Used when `provider` is \"anthropic\".\n","required":["systemInstruction"],"properties":{"systemInstruction":{"type":"string","maxLength":1000000,"description":"System instruction for Claude models. Equivalent to OpenAI's `instructions`.\nMaximum 1,000,000 characters.\n"},"topK":{"type":"number","minimum":0,"description":"Top-K sampling parameter. Deprecated on Claude models released after Claude Opus\n4.6, which reject any value at runtime; set it only on older models."},"thinkingConfig":{"type":"object","description":"Controls Claude's extended thinking capabilities.","properties":{"type":{"type":"string","enum":["enabled","disabled","adaptive"],"description":"Extended thinking mode."},"budgetTokens":{"type":"number","minimum":1024,"description":"Maximum tokens allocated for thinking. Required when `type` is \"enabled\" —\nomitting it fails the save with 422 `invalid_thinking_config`."},"display":{"type":"string","enum":["summarized","omitted"],"description":"How thinking output is surfaced in the response."},"effort":{"type":"string","enum":["low","medium","high","xhigh","max"],"description":"How much thinking effort the model applies; use with `type` \"adaptive\". Higher\nvalues (`xhigh`, `max`) may be gated to specific Claude models by the provider."}},"if":{"properties":{"type":{"const":"enabled"}},"required":["type"]},"then":{"required":["budgetTokens"]}},"serviceTier":{"type":"string","enum":["auto","standard_only"],"description":"Anthropic service tier for the request."},"prompts":{"type":"array","description":"MCP prompt entries saved with a Claude guardrail. Each item references one\nMCP connection and prompt names; a guardrail's run never fetches them.","items":{"type":"object","properties":{"type":{"type":"string","enum":["mcp"],"description":"Type of prompt entry. Always \"mcp\"."},"mcp":{"type":"object","description":"MCP server prompt configuration.","properties":{"_mcpConnectionId":{"type":"string","format":"objectId","description":"Connection to the MCP server."},"allowedPrompts":{"type":"array","description":"Prompt names saved with the entry; a guardrail's run never fetches them.","items":{"type":"string"}}}}}}},"resources":{"$ref":"#/components/schemas/McpResources"}}}}}}}},"if":{"properties":{"provider":{"const":"openai"}},"required":["provider"]},"then":{"required":["openai"],"properties":{"openai":{"required":["model","instructions"]}}},"else":{"if":{"properties":{"provider":{"enum":["gemini","anthropic"]}},"required":["provider"]},"then":{"required":["litellm"],"properties":{"litellm":{"required":["model"]}}},"else":{"if":{"properties":{"provider":{"enum":["mistral","xai","huggingface","deepseek","cohere","groq"]}},"required":["provider"]},"then":{"required":["model"]}}}},"OneToMany":{"type":"boolean","description":"When true, the step runs once per child record instead of once per incoming record.\n`pathToMany` names the array field that holds the children inside an object record; when\nthe incoming record is itself an array (grouped or row-based data), leave `pathToMany` blank\nand each element becomes a record. The fan-out is scoped to this step: afterwards the children\nare re-joined into the record's original shape — the object with its array, or the array of rows —\ncarrying any response-mapping enrichment, and that re-joined record is what the next step\nreceives. Not for locating records in an export's HTTP response; use\n`http.response.resourcePath` for that. Applies to steps that receive a record — imports and\nlookups (an export referenced as a page processor of a flow, API, or Tool). A standalone export\n(a flow's page generator) has no incoming record, so the fields are saved but have no effect on\nthe records it produces; to emit one record per array element from a standalone export, use a\n`hooks.preSavePage` script, or for a file-definition export make the repeating segment the\nrecord boundary in the definition's rules.\n","default":false},"PathToMany":{"type":"string","description":"Path to the array of child records inside an object record when `oneToMany` is true, in dot\nnotation (`items`, `lines.lineItems`). Leave blank when the incoming record is itself an array\n(grouped or row-based data) — each element is then a child record. A path that does not resolve\nto an array processes zero records and reports success. Read only on steps that receive a\nrecord (imports, lookup exports); on a standalone export it is saved and ignored — see `oneToMany`.\n"},"Filter":{"type":"object","description":"Configuration for selectively processing records based on specified criteria. This object enables\nprecise control over which items are included or excluded from processing operations.\n\n**Filter behavior**\n\nWhen configured, the filter is applied before processing begins:\n- Items that match the filter criteria are processed\n- Items that don't match are completely skipped\n- No partial processing is performed\n\nSend the whole object. A rules array sent alone as the `filter` value is not\nstored, and a full update of an export that carries one removes the filter the\nexport had, with no error.\n\n**Implementation approaches**\n\nThere are two distinct filtering mechanisms available:\n\n**Rule-Based Filtering (`type: \"expression\"`)**\n- **Best For**: Common filtering patterns based on standard attributes\n- **Capabilities**: Filter by names, values, dates, numerical ranges, text patterns\n- **Advantages**: Declarative, no coding required, consistent performance\n- **Configuration**: Define rules in the `expression` object\n- **Use When**: You have clear, static criteria for selection\n\n**Script-Based Filtering (`type: \"script\"`)**\n- **Best For**: Complex logic, dynamic criteria, or business rules\n- **Capabilities**: Full programmatic control, access to complete metadata\n- **Advantages**: Maximum flexibility, can implement any filtering logic\n- **Configuration**: Reference a script in the `script` object\n- **Use When**: Simple rules aren't sufficient or logic needs to be dynamic\n","properties":{"type":{"type":"string","description":"Determines which filtering mechanism to use. This choice affects which properties\nmust be configured and how filtering logic is implemented.\n\n**Available types**\n\n**Rule-Based Filtering (`\"expression\"`)**\n- **Required Config**: The `expression` object with rule definitions\n- **Behavior**: Evaluates declarative rules against item attributes\n- **Best For**: Common patterns like name matching, date ranges, value limits\n- **Advantages**: Simpler to configure, no custom code required\n\n**Script-Based Filtering (`\"script\"`)**\n- **Required Config**: The `script` object with _scriptId and function\n- **Behavior**: Executes custom JavaScript to determine which items to process\n- **Best For**: Complex conditions, business logic, dynamic criteria\n- **Advantages**: Maximum flexibility, can implement any logic\n\n**Implementation guidance**\n\n1. For standard filtering needs (name, size, date), use `\"expression\"`\n2. For complex logic or conditions not covered by expressions, use `\"script\"`\n3. When selecting a type, you must configure the corresponding object:\n    - `type: \"expression\"` requires the `expression` object\n    - `type: \"script\"` requires the `script` object\n","enum":["expression","script"]},"expression":{"type":"object","description":"Configuration for declarative rule-based filtering. This object enables filtering\nitems based on common attributes without requiring custom code.\n\n**Usage context**\n\nThis object is REQUIRED when `filter.type` is set to \"expression\" and should not be\nconfigured otherwise. It provides a standardized way to define filtering rules that\ncan match against item attributes like name, type, value, date, and other properties.\n\n**Implementation guidance**\n\nThe expression system uses a rule-based approach where:\n- Rules can be combined with AND/OR logic\n- Each rule can check a specific attribute\n- Multiple conditions can be applied (ranges, pattern matching, exact matches)\n\n**Common filter patterns**\n\n1. **Pattern matching**: Regular expressions with `matches`\n2. **Value range filtering**: Numbers between min and max values\n3. **Date range filtering**: Items created/modified within specific time ranges\n4. **Status checking**: Items with specific status values or properties\n\nFor AI agents: Rule-based filtering should be your first choice when the filtering criteria\ncan be expressed in terms of standard attributes. Only use script-based filtering when\nmore complex logic is required.\n","properties":{"version":{"type":"string","description":"Version identifier for the expression format. Currently only version \"1\" is supported.\n\nThis field ensures future compatibility if the expression format evolves. Always set to \"1\"\nfor current implementations.\n","enum":["1"]},"rules":{"type":"array","description":"Expression array defining filter conditions using prefix notation. The first element is the operator,\nfollowed by its operands which may themselves be nested expression arrays.\n\nThe rule expression follows this pattern:\n- First element: Operator name (string)\n- Remaining elements: Operands for that operator (values or nested expressions)\n\n**Expression structure**\n\nFilter expressions use a prefix notation where operators appear before their operands:\n```\n[operator, operand1, operand2, ...]\n```\n\n**Comparison Operators**\n- `\"equals\"`: Exact match (equals)\n- `\"notequals\"`: Not equal to value (not equals)\n- `\"greaterthan\"`: Value is greater than specified value (is greater than)\n- `\"greaterthanequals\"`: Value is greater than or equal to specified value (is greater than or equals)\n- `\"lessthan\"`: Value is less than specified value (is less than)\n- `\"lessthanequals\"`: Value is less than or equal to specified value (is less than or equals)\n- `\"startswith\"`: String starts with specified prefix (starts with)\n- `\"notstartswith\"`: String does not start with specified prefix (does not start with)\n- `\"endswith\"`: String ends with specified suffix (ends with)\n- `\"notendswith\"`: String does not end with specified suffix (does not end with)\n- `\"contains\"`: String contains specified substring (contains)\n- `\"doesnotcontain\"`: String does not contain specified substring (does not contain)\n- `\"empty\"`: Field is missing or empty (is empty)\n- `\"notempty\"`: Field contains a value (is not empty)\n- `\"matches\"`: Matches a regular expression, ignoring case (matches)\n\nOperator names are case-insensitive: `NOTEMPTY` and `endsWith` work and are stored\nas sent. `isempty`, `isnotempty`, `anyof`, `notcontains`, `doesnotstartwith`,\n`doesnotendwith`, `notmatches`, `doesnotmatch` and `notequal` are not operators:\nthey are refused with 422 \"Invalid operation: <name>\", with the name in lowercase,\nboth when the filter is saved and by `POST /v1/processors/filter`. To negate a\ncondition, wrap it in `not`.\n\nThe string comparisons (`equals`, `contains`, `startswith`, `endswith` and their\nnegations) are case-sensitive; wrap the field in `lowercase` to compare without case.\n`matches` is not: every pattern is compiled with the flags `gi`, so `^paid$` also\nmatches `PAID` and `[a-z]` matches capital letters. An inline flag such as `(?i)`\nmakes the pattern invalid. The filter still saves, but in a run every record fails\nwith `FILTER_EVALUATION_FAILED` (\"Invalid regular expression: /(?i)…/gi: Invalid\ngroup\"), and `POST /v1/processors/filter` refuses the call with 422 (live-verified\n2026-10-22).\n\n**Logical Operators**\n- `\"and\"`: All conditions must be true\n- `\"or\"`: At least one condition must be true\n- `\"not\"`: Negates the condition\n\n**Field Access and Type Conversion**\n- `\"extract\"`: Access a field from the item by name\n- `\"settings\"`: Access a custom setting from the flow, flow step, or integration configuration\n- `\"boolean\"`: Convert value to Boolean type\n- `\"epochtime\"`: Convert value to Epoch Time (Unix timestamp)\n- `\"number\"`: Convert value to Number type\n- `\"string\"`: Convert value to String type\n\n**Field Access Details**\n\n**Using `extract` to access record fields:**\n- Retrieves values from the current record being processed\n- Can access nested properties using dot notation (e.g., `\"customer.email\"`)\n- Paths are relative to the record itself: `[\"extract\", \"record.status\"]`\n  matches nothing\n- In a record filter, must be wrapped in a type conversion (`string`, `number`,\n  `boolean` or `epochtime`): a bare `[\"extract\", ...]` operand is refused with 422\n  \"Field operation extract must be called by a Type Conversion Operation but was called\n  by <operator>\", both when an import's filter is saved and by\n  `POST /v1/processors/filter`\n\n**Using `settings` to access configuration values:**\n- Retrieves values from the integration's configuration settings\n- Supports different scopes with prefix notation:\n  - `flow.settingName`: Access flow-level settings\n  - `flowGrouping.settingName`: Access the flow group's settings\n  - `export.settingName`: Access the export's (or lookup's) own settings\n  - `import.settingName`: Access the import's own settings\n  - `integration.settingName`: Access integration-level settings\n  - `connection.settingName`, `iClient.settingName`: Access the step's\n    connection and iClient settings\n- A scope whose resource has no settings can be absent; a rule that reads\n  it matches nothing\n- Must be wrapped in a type conversion like `extract`: a bare\n  `[\"settings\", ...]` operand is refused with 422 \"Field operation settings\n  must be called by a Type Conversion Operation\"\n- Useful for dynamic filtering based on configuration\n\n**Field Transformations**\n- `\"lowercase\"`: Convert string to lowercase\n- `\"uppercase\"`: Convert string to uppercase\n- `\"ceiling\"`: Round number up to the nearest integer\n- `\"floor\"`: Round number down to the nearest integer\n- `\"abs\"`: Get absolute value of a number\n\n**Arithmetic** (number operands)\n- `\"add\"`, `\"subtract\"`, `\"multiply\"`, `\"divide\"`, `\"modulo\"`: for example\n  `[\"subtract\", [\"number\", [\"extract\", \"total\"]], [\"number\", [\"extract\", \"discount\"]]]`.\n  `mod` is not an operator (422 \"Invalid operation: mod.\").\n\n**Dates**\n- `epochtime` reads ISO 8601 strings and epoch-millisecond numbers. An empty\n  string, a string that isn't a date, or epoch milliseconds sent as a string\n  fail it with \"Invalid date. Cannot convert given string or number to date\n  time.\", followed by `; record=` and the record as JSON: in a run that record fails (`FILTER_EVALUATION_FAILED`, source\n  `output_filter` on an export), and `POST /v1/processors/filter` and an\n  export preview refuse the whole call with 422. `and` and `or` evaluate\n  every operand, so a `notempty` test beside it doesn't prevent the failure.\n  A missing or null field doesn't match.\n- `datetime`, `date` and `timestamp` are not type conversions: 422\n  \"Invalid operation: <name>.\"\n- `number` reads a missing or empty field as 0.\n\n**Handlebars operands**\n- An operand can also be a string holding exactly one Handlebars expression, such as\n  `\"{{record.status}}\"` or `\"{{lowercase record.code}}\"`, on either side of the\n  operator. It renders against the record (`record.<field>`; an unprefixed\n  `{{status}}` renders empty) before the comparison.\n- The rendered value is a string: wrap it in `[\"number\", ...]` to compare it with a\n  number, or the rule is refused with 422.\n- `epochtime` does not render Handlebars: `[\"epochtime\", \"{{timestamp}}\"]` is refused\n  with 422 \"invalid datetime format\".\n- A string with text around the expression, or with more than one expression, is\n  compared as a literal.\n- `[\"number\", \"{{timestamp \\\"x\\\"}}\"]` is the current time in epoch milliseconds,\n  so `[\"greaterthanequals\", [\"epochtime\", [\"extract\", \"created\"]], [\"subtract\",\n  [\"number\", \"{{timestamp \\\"x\\\"}}\"], 604800000]]` keeps records created in the\n  last 7 days.\n- An unknown helper fails the rule with 422 \"Failed to evaluate handlebars\n  expression\".\n\nChoose the type conversion that matches the comparison — `number` for numeric\ncomparisons, `epochtime` for dates, `string` for text. For example:\n\n```json\n[\n  \"equals\",\n  [\n    \"number\",  // Convert to number before comparison\n    [\n      \"extract\",\n      \"quantity\"\n    ]\n  ],\n  100\n]\n```\n\nExample with datetime conversion:\n```json\n[\n  \"greaterthan\",\n  [\n    \"epochtime\",  // Convert to Unix timestamp before comparison\n    [\n      \"extract\",\n      \"createdDate\"\n    ]\n  ],\n  1609459200000  // January 1, 2021 as Unix timestamp in milliseconds\n]\n```\n\nExample with transformations:\n```json\n[\n  \"and\",\n  [\n    \"matches\",\n    [\n      \"lowercase\",  // Convert to lowercase before matching\n      [\n        \"string\",\n        [\n          \"extract\",\n          \"categories\"\n        ]\n      ]\n    ],\n    \"netsuite\"\n  ],\n  [\n    \"notequals\",\n    [\n      \"string\",\n      [\n        \"extract\",\n        \"recurrence.pattern.type\"\n      ]\n    ],\n    \"\"\n  ]\n]\n```\n\nExample comparing a record field with a flow setting:\n```json\n[\n  \"equals\",\n  [\n    \"string\",\n    [\n      \"extract\",\n      \"trantype\"\n    ]\n  ],\n  [\n    \"string\",\n    [\n      \"settings\",\n      \"flow.trantype\"\n    ]\n  ]\n]\n```\n\n**Examples**\n\nExample 1: Status field is not equal to \"cancelled\"\n```json\n[\n  \"notequals\",\n  [\n    \"string\",\n    [\n      \"extract\",\n      \"status\"\n    ]\n  ],\n  \"cancelled\"\n]\n```\n\nExample 2: Filename starts with \"HC\"\n```json\n[\n  \"startswith\",\n  [\n    \"string\",\n    [\n      \"extract\",\n      \"filename\"\n    ]\n  ],\n  \"HC\"\n]\n```\n\nExample 3: Amount is greater than 100\n```json\n[\n  \"greaterthan\",\n  [\n    \"number\",\n    [\n      \"extract\",\n      \"amount\"\n    ]\n  ],\n  100\n]\n```\n\nExample 4: Order date is after January 1, 2023 (UTC). Compare dates as `epochtime`\nagainst epoch milliseconds: a `string` comparison of ISO timestamps orders values with\ndifferent UTC offsets wrongly.\n```json\n[\n  \"greaterthan\",\n  [\n    \"epochtime\",\n    [\n      \"extract\",\n      \"orderDate\"\n    ]\n  ],\n  1672531200000\n]\n```\n","items":{"oneOf":[{"title":"String","type":"string"},{"title":"Number","type":"number"},{"title":"Boolean","type":"boolean"},{"title":"Object","type":"object"},{"title":"Array","type":"array"},{"title":"Null","type":"null"}]}}}},"script":{"type":"object","description":"Configuration for programmable script-based filtering. This object enables complex, custom\nfiltering logic beyond what expression-based filtering can provide.\n\n**Usage context**\n\nThis object is REQUIRED when `filter.type` is set to \"script\" and should not be configured\notherwise. It provides a way to execute custom JavaScript code to determine which items\nshould be processed.\n\n**Implementation approach**\n\nScript-based filtering works by:\n1. Executing the specified function from the referenced script\n2. Passing item data to the function\n3. Using the function's return value (true/false) to determine inclusion\n\n**Common use cases**\n\nScript filtering is ideal for:\n- Complex business logic that can't be expressed as simple rules\n- Dynamic filtering criteria that change based on external factors\n- Content-based filtering that requires deep inspection\n- Advanced pattern matching beyond simple wildcards\n- Multi-stage filtering with intermediate logic\n\nFor AI agents: Only use script-based filtering when expression-based filtering is insufficient.\nScript filtering requires maintaining custom code, which adds complexity to the integration.\n","properties":{"_scriptId":{"type":"string","description":"Reference to the Script resource that contains the filtering logic. This must be a valid\nObjectId of a Script resource in this account: any other id fails the save with 422\n(\"Only a script owned by the user or a script shared by the Integration App can be used\nat filter.script._scriptId.\").\n\nThe referenced script must contain the function specified in the `function` field\nand must be written to handle filtering specifically. The script receives\nitem data as its input and must return a boolean value indicating whether\nto process the item (true) or skip it (false).\n\nFormat: 24-character hexadecimal string (MongoDB ObjectId)\n"},"function":{"type":"string","description":"Name of the function within the script to execute for filtering decisions. This function\nmust exist in the script referenced by _scriptId.\n\n**Function requirements**\n\nThe specified function must:\n- Accept item data as its first parameter\n- Return a boolean value (true to process the item, false to skip it)\n- Handle errors gracefully\n- Execute efficiently (as it may run for many items)\n\n**Function signature**\n\n```javascript\nfunction filterItems(itemData) {\n  // itemData contains properties of the item being evaluated\n  // Custom logic here\n  return true; // or false to skip the item\n}\n```\n\nFor AI agents: Ensure the function name exactly matches a function defined in the\nreferenced script, as mismatches will cause the filter to fail.\n"}}}}},"Hook":{"type":"object","description":"A single lifecycle hook — a JavaScript function invoked from a script (or hosted on a\nstack) at a fixed point in an import or AI-agent run. Shared by every hook slot; the\nslot's own description says when in the lifecycle it fires.","properties":{"function":{"type":"string","description":"Function to invoke: one in the script named by `_scriptId`, or one the code deployed on\nthe stack named by `_stackId` exposes."},"_scriptId":{"type":"string","format":"objectId","description":"Script containing the hook function named in `function`. Send this or `_stackId`, not both.\nMust be a script in this account: any other id fails the save with 422, naming the hook,\nsuch as \"Only a script owned by the user or a script shared by the Integration App can\nbe used at hooks.preMap._scriptId.\" on an import's `preMap`."},"_stackId":{"type":"string","format":"objectId","description":"Stack that hosts the hook logic, used instead of a script for stack-based deployments.\nThe API doesn't enforce the either/or: a save carrying both `_scriptId` and `_stackId`\nanswers 200 and stores both, and the script and the stack then each list the step as a\ndependent."},"configuration":{"type":["object","null"],"description":"Static parameters passed to the hook function at runtime, letting one script be\nreused with different settings. Null when the hook carries no static parameters."}}},"Transform":{"type":"object","description":"Configuration for transforming data during processing operations. This object enables\nreshaping of records.\n\n**Transformation capabilities**\n\nCeligo's transformation engine offers powerful features for data manipulation:\n- Precise field mapping with JSONPath expressions\n- Support for any level of nested arrays\n- Formula-based field value generation\n- Dynamic references to flow and integration settings\n\n**Implementation approaches**\n\nThere are two distinct transformation mechanisms available:\n\n**Rule-Based Transformation (`type: \"expression\"`)**\n- **Best For**: Most transformation scenarios from simple to complex\n- **Capabilities**: Field mapping, formula calculations, lookups, nested data handling\n- **Advantages**: Visual configuration, no coding required, intuitive interface\n- **Configuration**: Define rules in the `expression` object\n- **Use When**: You have clear mapping requirements or need to reshape data structure\n\n**Script-Based Transformation (`type: \"script\"`)**\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Capabilities**: Full programmatic control, custom processing, complex business rules\n- **Advantages**: Maximum flexibility, can implement any transformation logic\n- **Configuration**: Reference a script in the `script` object\n- **Use When**: Visual transformation tools aren't sufficient for your use case\n","properties":{"type":{"type":"string","description":"Determines which transformation mechanism to use. This choice affects which properties\nmust be configured and how transformation logic is implemented.\n\n**Available types**\n\n**Rule-Based Transformation (`\"expression\"`)**\n- **Required Config**: The `expression` object with mapping definitions\n- **Behavior**: Applies declarative rules to reshape data\n- **Best For**: Most transformation scenarios from simple to complex\n- **Advantages**: Visual configuration, no coding required\n\n**Script-Based Transformation (`\"script\"`)**\n- **Required Config**: The `script` object with _scriptId and function\n- **Behavior**: Executes custom JavaScript to transform data\n- **Best For**: Extremely complex logic or proprietary algorithms\n- **Advantages**: Maximum flexibility, can implement any logic\n\n**Implementation guidance**\n\n1. For standard data transformations, use `\"expression\"`\n2. For complex logic or specialized processing, use `\"script\"`\n3. When selecting a type, you must configure the corresponding object:\n    - `type: \"expression\"` requires the `expression` object\n    - `type: \"script\"` requires the `script` object\n","enum":["expression","script"]},"expression":{"type":"object","description":"Configuration for declarative rule-based transformations. This object enables reshaping data\nwithout requiring custom code.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"expression\" and should not be\nconfigured otherwise. It provides a standardized way to define transformation rules that\ncan map, modify, and generate data elements.\n\n**Implementation guidance**\n\nThe expression system uses a rule-based approach where:\n- Field mappings define how input data is transformed to target fields\n- Formulas can be used to calculate or generate new values\n- Lookups can enrich data by fetching related information\n- Mode determines how records are processed (create new or modify existing)\n","properties":{"version":{"type":"string","description":"Version of the expression format. Determines which rules\nproperty contains the transformation logic.\n","enum":["1","2"]},"rules":{"type":"array","description":"Transformation rules for version 1 expressions. An array of\nrule groups; each group is an array of field-mapping objects.\nMost transforms have a single group. Present when `version`\nis `\"1\"`. The output record contains ONLY the generated\nfields — every unmapped field is dropped (v1 has no\nequivalent of Transform 2.0's `modify` mode), and the\nrecord's trace key does not survive the rebuild.\n","items":{"type":"array","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path to read from. Supports multiple\nsyntaxes: bare field names (`id`), dot notation\n(`fulfillment.shipment_id`), slash-prefixed paths\nfor XML (`/FeedProcessingStatus`), wildcards (`*.id`,\n`*.[Internal ID]`), and array indexing (`SDF[0]`).\n"},"generate":{"type":"string","description":"Target field name to write to. Typically a bare name\n(`id`) or dot path (`SDF.Filter.ID`).\n"},"key":{"type":"string","description":"Auto-generated identifier for this rule, used by the\nUI to track individual rules for editing and reordering.\n"}},"required":["extract","generate"]}}},"rulesTwoDotZero":{"type":"object","description":"Configuration for version 2 transformation rules. This object contains the core logic\nfor how data is mapped, enriched, and transformed.\n\n**Capabilities**\n\nTransformation 2.0 provides:\n- Precise field mapping with JSONPath expressions\n- Support for deeply nested data structures\n- Formula-based field generation\n- Dynamic lookups for data enrichment\n- Multiple operating modes to fit different scenarios\n","properties":{"mode":{"type":"string","description":"Transformation mode that determines how records are handled during processing.\n\n**Available modes**\n\n**Create Mode (`\"create\"`)**\n- **Behavior**: Builds entirely new output records from inputs\n- **Use When**: Output structure differs significantly from input\n- **Advantage**: Clean slate approach, no field inheritance\n\n**Modify Mode (`\"modify\"`)**\n- **Behavior**: Makes targeted edits to existing records\n- **Use When**: Output structure should remain similar to input\n- **Advantage**: Preserves unmapped fields from the original record\n","enum":["create","modify"]},"mappings":{"$ref":"#/components/schemas/Mappings"},"lookups":{"allOf":[{"description":"Shared lookup tables used across all mappings defined in the transformation rules.\n\n**Purpose**\n\nLookups provide centralized value translation that can be referenced from any mapping\nin your transformation configuration. They enable consistent translation of codes, IDs,\nand values between systems without duplicating translation logic.\n\n**Usage in transformations**\n\nLookups are particularly valuable in transformations for:\n\n- **Data Normalization**: Standardizing values from diverse source systems\n- **Code Translation**: Converting between different coding systems (e.g., status codes)\n- **Field Enrichment**: Adding descriptive values based on ID or code lookups\n- **Cross-Reference Resolution**: Mapping identifiers between integrated systems\n\n**Implementation**\n\nLookups are defined once in this array and referenced by name in mappings:\n\n```json\n\"lookups\": [\n  {\n    \"name\": \"statusMapping\",\n    \"map\": {\n      \"A\": \"Active\",\n      \"I\": \"Inactive\",\n      \"P\": \"Pending\"\n    },\n    \"default\": \"Unknown Status\"\n  }\n]\n```\n\nThen referenced in mappings using the lookupName property:\n\n```json\n{\n  \"generate\": \"status\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.statusCode\",\n  \"lookupName\": \"statusMapping\"\n}\n```\n\nThe system automatically applies the lookup during transformation processing.\n\nFor complete details on lookup properties and behavior, see the Lookups schema.\n"},{"$ref":"#/components/schemas/Lookups"}]},"inputContext":{"type":"string","enum":["record","envelope"],"description":"Controls the JSON shape the transformTwoDotZero processor\nevaluates `mappings[].extract` JSONPath values against at\nflow runtime. Applies only to Transform 2.0 (v2,\n`rulesTwoDotZero`); v1 transforms (the `rules` array on\n`transform.expression.rules`) and script-mode transforms\nignore this field.\n"}}}}},"script":{"type":"object","description":"Configuration for programmable script-based transformations. This object enables complex, custom\ntransformation logic beyond what expression-based transformations can provide.\n\n**Usage context**\n\nThis object is REQUIRED when `transform.type` is set to \"script\" and should not be configured\notherwise. It provides a way to execute custom JavaScript code to transform data according to\nspecialized business rules or complex algorithms.\n\n**Implementation approach**\n\nScript-based transformation works by:\n1. Executing the specified function from the referenced script\n2. Passing input data to the function\n3. Using the function's return value as the transformed output\n\n**Common use cases**\n\nScript transformation is ideal for:\n- Complex business logic that can't be expressed through mappings\n- Algorithmic transformations requiring computation\n- Dynamic transformations based on external factors\n- Legacy system data format compatibility\n- Multi-stage processing with intermediate steps\n\nOnly use script-based transformation when expression-based transformation is insufficient.\nScript transformation requires maintaining custom code, which adds complexity to the integration.\n","properties":{"_scriptId":{"type":"string","description":"Reference to a predefined script resource containing the transformation logic.\n\nThe referenced script should contain the function specified in the\n'function' property. It must be a script in this account: any other id fails the save\nwith 422 (\"Only a script owned by the user or a script shared by the Integration App can\nbe used at transform.script._scriptId.\").\n","format":"objectid"},"function":{"type":"string","description":"Name of the function within the script to execute for transformation. This function\nmust exist in the script referenced by _scriptId.\n"}}}}},"Mappings":{"type":"array","description":"Array of field mapping configurations for transforming data from one format into another.\n\n**Guidance**\n\nThis schema is designed around RECURSION as its core architectural principle. Understanding this recursive\nnature is essential for building effective mappings:\n\n1. The schema is self-referential by design - a mapping can contain nested mappings of the same structure\n2. Complex data structures (nested objects and arrays of objects, to any depth) are ALL\n   handled through this recursive pattern; an array of arrays is not (build one in a script hook)\n3. Each mapping handles one level of the data structure; deeper levels are handled by nested mappings\n\nWhen generating mappings programmatically:\n- For simple fields (string, number, boolean): Create single mapping objects\n- For objects: Create a parent mapping with nested 'mappings' array containing child field mappings\n- For arrays: Use 'buildArrayHelper' with extract paths defining array inputs and\n  recursive 'mappings' to define object structures\n\nThe system will process these nested structures recursively during runtime, ensuring proper construction\nof complex hierarchical data while maintaining excellent performance.\n\nOn save, a leaf row with neither a source (`extract`, `hardCodedValue`, `lookupName`) nor a\ndestination (`generate`) is dropped silently, at every nesting level, and so is an object or\nobject-array row left with no source, no destination and no remaining child `mappings` or\n`buildArrayHelper` entries; a row with only one of the two, and a source-less, destination-less\nwrapper that still has children, are kept.\n\nThe shape of the array is the mapper's **JSON structure**. Named top-level items produce an object\nrecord (`record { }`). Exactly one top-level item with `dataType: objectarray` and no `generate` is\nthe `rows [ ]` structure: the mapped record is an array — usually one element per element of the\nsource array that root's `buildArrayHelper` iterates — Handlebars templates such as `http.body`\nbind it as `rows` instead of `record`, and the mapping editor shows that row's destination disabled.\n","items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}}},"items":{"type":"object","properties":{"generate":{"type":"string","description":"**Purpose**\nDefines the target field name in the output object/record.\n\n**Guidance**\nThis is the PRIMARY FIELD that identifies the output property being created:\n\n- For regular fields: Set to the exact property name (e.g., \"firstName\", \"price\", \"isActive\")\n- For object fields: Set to the object property name, then add child mappings in the 'mappings' array\n- For array fields: Set to the array property name, then configure 'buildArrayHelper'\n- One structural blank: the single top-level `objectarray` item of a `rows [ ]` mapping (the\n  array output structure — see the array's description). A child-bearing wrapper and a source-only row that\n  already exist in a saved document also stay unnamed when the tree is re-sent — filling in a\n  name changes the output; it does not complete the row\n\nIMPORTANT: Do NOT use dot notation (e.g., \"customer.firstName\") in this field. Instead, create proper\nhierarchical structure with nested mappings:\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"status\": \"Active\",\n  \"mappings\": [\n    {\"generate\": \"firstName\", \"dataType\": \"string\", \"extract\": \"$.name.first\", \"status\": \"Active\"}\n  ]\n}\n```\n\nWhen parsing existing mappings, an empty `generate` on a row inside an `arrayarray` row's\n`buildArrayHelper` entry is that row's inner array (`arrayarray` fails at run time; see\n`dataType`); other unnamed rows are the wrappers and source-only rows described above; an\nempty `generate` on the ONLY top-level item, with `dataType: objectarray`, is the `rows [ ]` JSON\nstructure — array output, bound as `rows` in templates — and is intentional, not a missing name.\n"},"dataType":{"type":"string","description":"**Purpose**\nExplicitly declares the data type of the output field, controlling how data is processed and structured.\n\n**Guidance**\nThis is a REQUIRED field that fundamentally determines mapping behavior:\n\n**Simple Types (direct value mapping)**\n- `string`: Text values, converts other types to string representation\n- `number`: Numeric values. Converts a numeric string (`\"24\"` → `24`,\n  `\"007\"` → `7`) and leaves the field out for an empty string; a value\n  holding letters or a thousands separator (`\"1,250.00\"`) fails the\n  record with `non_parsable_string` (`<generate>: String contains alpha\n  characters`)\n- `boolean`: True/false values, converts truthy/falsy values\n\nDates are represented as strings — use `string` for date fields and\ndrive the parsing/formatting through the `extractDateFormat` /\n`generateDateFormat` / `extractDateTimezone` / `generateDateTimezone`\nfields. There is no separate `date` enum value.\n\n**Complex Types (require additional configuration)**\n- `object`: Creates a nested object from child mappings in the 'mappings' array, or\n  copies a whole object with `extract` (one or the other, not both)\n\n**Array Types**\n- `stringarray`: Array of strings\n- `numberarray`: Array of numbers\n- `booleanarray`: Array of booleans\n- `objectarray`: Array of objects (most common array type)\n\n`arrayarray` is not a type the mapper recognizes: a save accepts it, but every record\nthen fails with `Destination datatype is a required field.` Build an array of arrays\nin a script hook instead.\n\nArray dataTypes are populated two ways: construct or iterate through\n`buildArrayHelper` entries, or set a constant with `hardCodedValue`. The\nrow's own `extract` is never read on an array type: a primitive-array row\nthat has only an `extract` (JSONPath, Handlebars or a literal) yields\nnothing, with no error, and an `objectarray` row without `buildArrayHelper`\nor `hardCodedValue` is refused with 422 (\"buildArrayHelper\" or\n\"hardCodedValue\" must be present for object array datatype.). To copy a\nsource array through unchanged, use one entry that iterates it\n(`buildArrayHelper: [{\"extract\": \"$.items[*]\"}]`).\n\nIMPORTANT: The dataType controls which additional fields are relevant:\n- For date-like string fields: extractDateFormat, generateDateFormat, etc. become relevant\n- For object types: 'mappings' array becomes relevant\n- For array types: `buildArrayHelper` or `hardCodedValue` (see above)\n\nWhen analyzing existing mappings or generating new ones, always check dataType first\nto understand what additional fields should be present.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray"]},"extract":{"type":"string","description":"**Purpose**\nDefines how to retrieve data from the input record to populate the output field.\n\n**Guidance**\nThis field supports THREE DISTINCT PATTERNS that are easily distinguished:\n\n**1. json Path Syntax**\n- MUST start with '$' — the record root. An object record is addressed as\n  '$.field'; a grouped (array) record as '$[0].field' / '$[*].field'\n- Used for precisely targeting data in structured JSON objects\n- Examples: '$.customer.firstName', '$.items[0].price', '$.addresses[*].street',\n  '$[*].Tax' (every row of a grouped record)\n- Wildcards like [*] extract multiple values/objects\n- A key that contains a dot needs bracket notation: `$['P.O. Number']` (or\n  `$[\"P.O. Number\"]`, `$.[P.O. Number]`). `$.P.O. Number` is read as a nested\n  path: it maps nothing and the record still succeeds. Spaces, hyphens and `#`\n  need no brackets (`$.Ship To Name`, `$.Tracking #`)\n- Filter expressions such as `$.items[?(@.active)]` fail the record with\n  \"Eval [?(expr)] prevented in JSONPath expression.\"\n\n```json\n\"extract\": \"$.customer.addresses[*]\"  // Extracts all addresses\n```\n\n**2. Handlebars Template Syntax**\n- Contains '{{' and '}}' pattern\n- Evaluated by the AFE 2.0 handlebars template engine\n- Can include logic, formatting, and computation\n- Access input record fields with {{record.fieldName}} notation; a grouped\n  (array) record binds as `rows` instead — {{rows.0.fieldName}} / {{#each rows}}\n- Examples: \"{{record.firstName}} {{record.lastName}}\", \"{{#if record.isActive}}Active{{else}}Inactive{{/if}}\"\n- Valid on scalars and on `object` rows, where the rendered output must\n  parse into an object. On an array row the template goes in a\n  `buildArrayHelper` entry instead: on an `objectarray` row's own\n  `extract` it is refused with 422 (\"buildArrayHelper\" or \"hardCodedValue\"\n  must be present for object array datatype.), and on a primitive-array\n  row's own `extract` it yields nothing (the field is left out) without an\n  error\n\n```json\n\"extract\": \"{{record.price}} {{record.currency}}\"  // Combines two fields\n```\n\n**3. Hard-Coded Value (literal string)**\n- Does NOT start with '$'\n- Does NOT contain handlebars '{{' syntax\n- System treats it as a literal string value\n- VERY COMMON for setting static/constant values\n- Examples: \"Active\", \"USD\", \"Completed\", \"true\"\n\n```json\n\"extract\": \"primary\"  // Sets field value to the literal string \"primary\"\n\"extract\": \"true\"     // Sets field value to the literal string \"true\"\n\"extract\": \"N/A\"      // Sets field value to the literal string \"N/A\"\n```\n\nThis third pattern is the simplest and most efficient way to set hard-coded values in your mappings.\nAI agents should use this pattern whenever a field needs a static value that doesn't come from\nthe input record or require computation.\n\n**Important implementation details**\n\n- JSON path patterns ALWAYS execute from the TOP-LEVEL root of the input record\n- The system maintains this context even in deeply nested mappings\n- For object mappings without child mappings, extract should return a complete object\n- An `object` row takes `extract` (to copy a whole object) or child `mappings`, not\n  both: a row with both fails with 422 (`\"extract\" should not be present if\n  \"mappings\" field is present`). A `buildArrayHelper` entry is different: there\n  `extract` and `mappings` work together.\n- The root is the record as it arrives: an object record roots at `$.field`; a\n  grouped record (an array of rows — grouped exports, file key columns, NetSuite\n  grouped saved searches) roots at the array, so its rows are addressed as\n  `$[0].field` (one row) or `$[*].field` (every row). A root that contradicts the\n  record's shape resolves to nothing without an error.\n\nFor most simple field-to-field mappings, prefer JSON path syntax for its clarity and performance.\nFor hard-coded values, simply use the literal string as the extract value.\n"},"extractDateFormat":{"type":"string","description":"Specifies the format pattern of the input date string to ensure proper parsing.\n\nUsed on string-typed mappings whose `extract` yields a date. Uses\nMoment.js-compatible formatting tokens to describe how the incoming date\nstring is structured.\n"},"extractDateTimezone":{"type":"string","description":"Specifies the timezone of the input date string using Olson/IANA timezone identifiers.\n\nUsed on string-typed mappings whose `extract` yields a date; tells the system\nhow to interpret timestamp values from the input system.\n"},"generateDateFormat":{"type":"string","description":"Specifies the output format pattern when generating a date string or converting\nfrom a Date type to String type.\n\nUses Moment.js-compatible formatting tokens to define the structure of the resulting\ndate string.\n"},"generateDateTimezone":{"type":"string","description":"Specifies the timezone to apply when generating or converting timestamp values\nusing Olson/IANA timezone identifiers.\n\nControls timezone conversion when producing date output.\n"},"default":{"type":["string","null"],"description":"Specifies a fallback value to use when extract returns empty/null or when conditional\nlogic fails and no other mapping supplies a value.\n\nExplicit JSON `null` is itself a valid fallback: the destination field is written as\nJSON null when the extract yields nothing (the Mapper UI's \"Use null as default value\"\naction). Omitting the key entirely means no fallback — the field is left out of the\noutput when the extract is missing or an empty string (the UI's \"Do nothing\" action),\nwhile a null source is still written as null unless `conditional.when` is\n`extract_not_empty`. Failing the record instead is `conditional.when:\nfail_record_when_empty`, which excludes a default.\n"},"lookupName":{"type":"string","description":"**Purpose**\nReferences a lookup table for transforming values during the mapping process.\n\n**Usage**\n\nThe lookupName refers to a named lookup defined in the lookups array of the same resource.\nOn an HTTP import that is `http.lookups`: a `lookups` array sent only at the top level\nis dropped on save. On an import, a name that matches no lookup fails every record\nwith \"Unable to find lookup in mapping configs - <name>\", and a key the lookup\ndoesn't hold follows the lookup's `allowFailures`: false fails the record; true, or\nthe field left out, lets the record succeed with the lookup's `default`, or without\nthe mapped field when there is none.\n\n```json\n{\n  \"generate\": \"countryName\",\n  \"dataType\": \"string\",\n  \"extract\": \"$.countryCode\",\n  \"lookupName\": \"countryCodeToName\"\n}\n```\n\nDuring processing, the system:\n1. Extracts the value from the input record (e.g., \"US\")\n2. Finds the lookup table with the specified name\n3. Uses the extracted value as a key in the lookup\n4. Returns the corresponding value (e.g., \"United States\")\n\n**Benefits**\n\n- **Standardization**: Ensures consistent value translation across mappings\n- **Centralization**: Define translations once and reference them in multiple places\n- **Maintainability**: Update all mappings by changing the lookup definition\n- **Readability**: Makes mappings more descriptive and self-documenting\n\nThe specific lookup capabilities depend on the context where mappings are used.\n"},"hardCodedValue":{"type":"string","description":"Constant written to the destination instead of an extracted value. On a primitive\narray (`stringarray`, `numberarray`, `booleanarray`) the string is split on commas,\nverbatim and untrimmed, into elements of the array's type (`\"A,B\"` gives `[\"A\", \"B\"]`).\nOn an `object` it must be a JSON object string and on an `objectarray` a JSON array\nstring (an object string becomes one element); the save parses it and refuses non-JSON\nor the wrong shape with 422, and an array row with a constant needs no `buildArrayHelper`.\n"},"description":{"type":"string","description":"Optional free-text annotation that appears in the Mapper sidebar to provide context about\nthe mapping's purpose for collaboration and documentation.\n\nHas no functional impact on the mapping behavior.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the value produced by `extract`, before any\nconversion to `dataType`. Same enum as `dataType`. Set on leaf mappings\nonly — parent mappings (with child `mappings` or `buildArrayHelper`)\nhave no extracted value of their own; the children carry their own\n`sourceDataType`.\n\nFor date fields use `string` (JSON represents dates as strings); the\nparsing/formatting lives in `extractDateFormat` / `generateDateFormat` /\n`extractDateTimezone` / `generateDateTimezone`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"mappings":{"type":"array","description":"**Purpose**\nEnables recursive definition of nested object structures through child mapping objects.\n\n**Guidance**\nThis is the KEY FIELD that implements the recursive pattern at the core of this schema:\n\n**When to Use**\n- REQUIRED when dataType = \"object\" (unless you are copying an entire object from the input record)\n- REQUIRED in buildArrayHelper.mappings when defining complex object array elements\n- NEVER used with simple types (string, number, boolean)\n\n**Behavior**\n- Each mapping in this array becomes a property of the parent object\n- The full Mappings schema is repeated recursively at each level\n- Can be nested to any depth for complex hierarchical structures\n\n**Context Handling**\n- Each level of nesting changes the mapping CONTEXT for 'generate'\n- The extraction CONTEXT always remains the original input record\n- This means child mappings can pull data from anywhere in the input record\n\n**Common Patterns**\n\n**Nested Objects**\n```json\n{\n  \"generate\": \"customer\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\n      \"generate\": \"contact\",\n      \"dataType\": \"object\",\n      \"mappings\": [\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.customerEmail\"}\n      ]\n    }\n  ]\n}\n```\n\n**Multiple Fields in Object**\n```json\n{\n  \"generate\": \"address\",\n  \"dataType\": \"object\",\n  \"mappings\": [\n    {\"generate\": \"street\", \"dataType\": \"string\", \"extract\": \"$.address.line1\"},\n    {\"generate\": \"city\", \"dataType\": \"string\", \"extract\": \"$.address.city\"},\n    {\"generate\": \"country\", \"dataType\": \"string\", \"extract\": \"$.address.country\"}\n  ]\n}\n```\n\nIMPORTANT: When analyzing or generating mappings, ALWAYS check if parent.dataType = \"object\"\nor if you're inside buildArrayHelper.mappings for objectarray elements. These are the only\nvalid contexts for the mappings array.\n","items":{"$ref":"#/components/schemas/items"}},"buildArrayHelper":{"type":"array","description":"**Purpose**\nConfigures how to construct arrays in the output record, handling various array types and inputs.\n\n**Guidance**\nThis is the mechanism for building array data types:\n\n**When to Use**\n- REQUIRED when dataType ends with \"array\" (stringarray, objectarray, etc.), unless the\n  row sets `hardCodedValue`: an array row's own `extract` is never read (see `dataType`)\n- Each entry in this array contributes elements to the output array\n- Multiple entries allow combining data from different input arrays\n\n**Array Type Handling**\n\n**For Simple Arrays (stringarray, numberarray, booleanarray)**\n- Only the 'extract' field is used to pull values\n- JSON path with wildcards (e.g., $.items[*].name) returns multiple values;\n  on a grouped (array) record the record itself is the array, so the\n  path roots at it ($[*].name — one value per row)\n- Each result is converted to the appropriate primitive type\n```json\n{\n  \"generate\": \"productNames\",\n  \"dataType\": \"stringarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.products[*].name\"}\n  ]\n}\n```\n\n**For Object Arrays (objectarray) - three patterns**\n\n1. Extract Only (existing objects):\n```json\n{\n  \"generate\": \"contacts\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\"extract\": \"$.account.primaryContacts[*]\"},  // Pull primary contact objects\n    {\"extract\": \"$.account.secondaryContacts[*]\"},  // Pull secondary contact objects\n    {\"extract\": \"$.vendor.contactPersons[*]\"},  // Pull vendor contact objects\n    {\"extract\": \"$.subsidiaries[*].mainContact\"}  // Pull main contact from each subsidiary\n  ]\n}\n```\n\n2. Mappings Only (constructed object):\n```json\n{\n  \"generate\": \"contactInfo\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"mappings\": [  // Creates one object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"primary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.primaryEmail\"}\n      ]\n    },\n    {\n      \"mappings\": [  // Creates another object in the array\n        {\"generate\": \"type\", \"dataType\": \"string\", \"extract\": \"secondary\"},\n        {\"generate\": \"email\", \"dataType\": \"string\", \"extract\": \"$.secondaryEmail\"}\n      ]\n    }\n  ]\n}\n```\nEach constructed entry contributes exactly one element, in entry\norder; writing the entry with `\"extract\": \"$\"` is equivalent to\nomitting the extract (both anchor the element at the record root).\nA fixed number of static elements is built from that many sibling\nconstructed entries.\n\n3. Extract AND Mappings (transform input arrays):\n```json\n{\n  \"generate\": \"lineItems\",\n  \"dataType\": \"objectarray\",\n  \"buildArrayHelper\": [\n    {\n      \"extract\": \"$.order.items[*]\",  // For each item in the array\n      \"mappings\": [  // Transform to this structure using the composite object\n        {\"generate\": \"sku\", \"dataType\": \"string\", \"extract\": \"$.order.items.productId\"},  // Notice: items is singular\n        {\"generate\": \"quantity\", \"dataType\": \"number\", \"extract\": \"$.order.items.qty\"},   // Notice: items is singular\n        {\"generate\": \"orderNumber\", \"dataType\": \"string\", \"extract\": \"$.order.id\"},       // Access parent data\n        {\"generate\": \"customerName\", \"dataType\": \"string\", \"extract\": \"$.customerName\"}   // Access root data\n      ]\n    }\n  ]\n}\n```\n\n**Arrays of Arrays**\n- The mapper has no working type for them: `arrayarray` saves, but every record\n  fails with `Destination datatype is a required field.` Build an array of arrays\n  in a script hook instead.\n\n**Important details**\n\n- When both extract and mappings are provided, the system creates special composite objects\n  that maintain hierarchical context during processing\n- This enables accessing both the current array element AND its parent context\n- An ITERATING entry's extract MUST be a JSON path that iterates an array:\n  '$.items[*]' when the array is a field of an object record, or '$[*]'\n  (objectarray) / '$[*].field' (primitive arrays) when the record itself is a\n  grouped array of rows. A CONSTRUCTED entry (child mappings building one\n  element) instead omits extract or sets it to exactly \"$\" — never rewrite \"$\"\n  into an indexed or comma-joined form ('$[0]', '$[0],$[0]'): those render the\n  entire destination array as null without raising a validation error\n- Each array helper entry acts independently, potentially adding multiple elements\n\nThe buildArrayHelper is the most complex part of the mappings system - always analyze the\ndataType first to understand which pattern is appropriate.\n","items":{"type":"object","properties":{"extract":{"type":"string","description":"JSON path expression that identifies the input array or values to extract.\n\nFor objectarray with mappings, this defines which input objects to iterate through.\nThe JSON path must return either a single object or an array of objects.\n\nFor a CONSTRUCTED entry (child mappings building exactly one element from\nrecord-root fields and constants), omit this field or set it to exactly \"$\" —\nthe two spellings are equivalent. Indexed or comma-joined forms ('$[0]',\n'$[0],$[0]') are not valid at the entry level: the processor renders the\nentire destination array as null without raising a validation error.\n\nThe system creates special composite objects during processing to maintain\nhierarchical relationships, allowing easy access to both the current array item\nand its parent contexts.\n\nAn iterating entry keeps the `[*]`: `$.line_items` without it yields one empty\nelement per item, with no error. Child extracts drop the `[*]` but keep the\narray's path (`$.line_items.sku`); `$.line_items[*].sku` maps nothing, and an\nelement-relative `$.sku` reads the record root instead of the element.\n"},"name":{"type":"string","maxLength":200,"description":"Display label for this source object in the mapper, so the sources of\nan object array can be told apart instead of all showing as `$`.\nHas no effect at runtime; stored only when non-empty.\n"},"sourceDataType":{"type":"string","description":"Declares the JSON type of the input array being iterated, to ensure\nproper type handling during array construction. Same enum as `dataType`.\n","enum":["string","number","boolean","object","stringarray","numberarray","booleanarray","objectarray","arrayarray"]},"default":{"type":["string","null"],"description":"Specifies a fallback value when the extracted array element is empty or\nnot found in the input data.\n\nExplicit JSON `null` is itself a valid fallback (the element is written as\nJSON null); omitting the key means no fallback is applied.\n"},"conditional":{"type":"object","description":"Defines conditional rules for including each array element in the result.\n","properties":{"when":{"type":"string","description":"Condition judged on the entry's extract — skip the elements the\nextract does not return, or fail the whole record when the source\narray is missing or empty.\n","enum":["extract_not_empty","fail_record_when_empty"]}}},"mappings":{"type":"array","description":"Contains recursive mapping definitions for complex array element transformations.\n\n**Composite object mechanism**\n\nWhen both 'extract' and 'mappings' are used together, the system implements a sophisticated\n\"composite object\" approach that is crucial for AI agents to understand:\n\n1. The system starts with the complete input record\n\n2. For each array element matched by the extract path, it creates a modified version of\n   the input record where:\n   - Array paths in the extract JSON path are REPLACED with single objects\n   - Each array ([]) in the path is converted to a single object ({})\n   - This preserves the hierarchical relationship between nested arrays\n\n**Example**\n\nGiven an input record:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": [\n      {\n        \"id\": \"O-001\",\n        \"items\": [\n          {\"sku\": \"ABC\", \"qty\": 2},\n          {\"sku\": \"XYZ\", \"qty\": 1}\n        ]\n      },\n      {\n        \"id\": \"O-002\",\n        \"items\": [\n          {\"sku\": \"DEF\", \"qty\": 3}\n        ]\n      }\n    ]\n  }\n}\n```\n\nWith extract path: `$.customer.orders[*].items[*]`\n\nFor each item, the system creates a composite object like:\n```json\n{\n  \"customer\": {\n    \"name\": \"John Doe\",\n    \"orders\": {  // Note: Array replaced with single object\n      \"id\": \"O-001\",\n      \"items\": {  // Note: Array replaced with single object\n        \"sku\": \"ABC\",\n        \"qty\": 2\n      }\n    }\n  }\n}\n```\n\nThen in your mappings, you can access:\n- The current item: `$.customer.orders.items.sku`\n- The parent order: `$.customer.orders.id`\n- Top-level data: `$.customer.name`\n\nThis approach allows for precise mapping from deeply nested structures while maintaining\naccess to all contextual parent data, without requiring complex array index management.\n\n**Implementation guidance**\n\nWhen implementing the composite object mechanism:\n\n1. Analyze the extract path to identify all array patterns (`[*]` or `[number]`)\n2. For each array in the path, understand that it will be replaced with a single object\n3. In the mappings, use paths that reference these arrays as if they were objects\n4. Remember that every mapping still has access to the full input record context\n5. This mechanism is especially powerful when mapping hierarchical data like:\n   - Order → Line Items → Taxes/Discounts\n   - Customer → Addresses → Address Lines\n   - Invoice → Line Items → Serial Numbers\n\nThe extract path effectively tells the system \"iterate through these arrays\",\nwhile the composite object mechanism ensures you can still access both the\ncurrent array item AND its parent context during mapping.\n","items":{"$ref":"#/components/schemas/items"}}},"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"required":["default"]}}}},"status":{"type":"string","description":"**Purpose**\nControls whether the mapping is applied. Send it on every mapping entry.\n\n**Guidance**\nOnly `Active` rows run. `Draft` and `Inactive` rows keep their\nconfiguration, are saved with minimal validation, and are skipped at\nruntime and in preview; a non-Active parent skips its whole subtree\nwhatever its children say. Imports (on save and in the mapper processor)\nand API response mappings reject a row missing this field (validation\nerror: \"Mapping object must have status field present.\"); a Tool's\noutput mapping saves and runs one, which is why `required` doesn't\nlist it.\n","enum":["Active","Draft","Inactive"]},"conditional":{"type":"object","description":"**Purpose**\nDefines conditional processing rules for the entire mapping.\n\n**Guidance**\nThese conditions determine whether the mapping is applied based on record\nstate or field content:\n\n**When to Use**\n- When a mapping should only be applied in specific circumstances\n- To implement conditional logic without using complex handlebars expressions\n- For creating mappings that only run during create or update operations\n\n**Available Conditions**\n\n- `record_created`: Apply only when creating a new record\n  Useful for setting initial values that should not be overwritten during updates\n\n- `record_updated`: Apply only when updating an existing record\n  Useful for transformation logic that should only run during updates\n\n- `extract_not_empty`: Apply only when the extract field returns a value\n  Useful for conditional mapping based on input data availability\n\n- `fail_record_when_empty`: Fail the record when the extract field returns\n  no value (the UI's \"Cause record to fail\" action when the source field\n  has no value)\n\n**Example**\n```json\n{\n  \"generate\": \"statusMessage\",\n  \"dataType\": \"string\",\n  \"status\": \"Active\",\n  \"extract\": \"$.status.message\",\n  \"conditional\": {\n    \"when\": \"extract_not_empty\"  // Only map when status.message exists\n  }\n}\n```\n","properties":{"when":{"type":"string","description":"Condition that gates this mapping — either an operation gate on a\ncomposite (create-or-update) import or the action to take when the\nextract returns no value. Empty means null, undefined or an empty\nstring; `0`, `false` and whitespace are values.\n","enum":["record_created","record_updated","extract_not_empty","fail_record_when_empty"]}}}},"required":["dataType"],"if":{"properties":{"conditional":{"properties":{"when":{"const":"fail_record_when_empty"}},"required":["when"]}},"required":["conditional"]},"then":{"not":{"anyOf":[{"required":["default"]},{"required":["lookupName"]},{"required":["hardCodedValue"]}]}}},"MappingField":{"type":"object","description":"One Mapper 1.0 field-mapping entry.","properties":{"generate":{"type":"string","description":"Target field path to write on the destination record."},"extract":{"type":"string","description":"Source expression. Accepts:\n- A dot path from the record root (e.g. `email`, `address.city`), read from the\n  incoming record. A path that matches no field writes nothing.\n- Handlebars template (contains `{{`) for concatenation /\n  formatting / conditional logic (e.g. `{{firstName}} {{lastName}}`,\n  `{{#if mobile}}{{mobile}}{{else}}{{phone}}{{/if}}`). The template reads the\n  record's fields by bare name: `{{record.firstName}}` and `{{data.firstName}}`\n  render nothing, so the field is left out even when the record has the value.\n\nA `$.` JSONPath (the Mapper 2.0 `mappings` syntax) reads nothing here, and a bare\nstring is read as a field path, not a constant: set constants in `hardCodedValue`.\nOmit when using `hardCodedValue`.\n"},"hardCodedValue":{"type":["string","null"],"description":"Static value written to `generate` instead of extracting from the source record."},"dataType":{"type":"string","enum":["string","number","boolean","numberarray","stringarray","json"],"description":"Data type coercion applied to the mapped value."},"discardIfEmpty":{"type":"boolean","description":"When true, the field is omitted from the output when the extracted value is empty."},"immutable":{"type":"boolean","description":"When true, the mapped value cannot be overwritten by later mapping steps."},"lookupName":{"type":"string","description":"Name of an entry in the import's `lookups` array (`http.lookups` on an HTTP import) used to translate the value."},"default":{"type":["string","null"],"description":"Fallback value written when the source value is an empty string, null or\nmissing. Without a default, an empty string and null are written as they are\nand a missing field is left out."},"extractDateFormat":{"type":"string","description":"Date format of the source value, used to parse it before conversion."},"extractDateTimezone":{"type":"string","description":"Timezone applied when parsing the source date value."},"generateDateFormat":{"type":"string","description":"Date format applied to the value written to the destination."},"generateDateTimezone":{"type":"string","description":"Timezone applied when formatting the destination date value."},"conditional":{"type":"object","description":"Only apply this mapping entry when the given condition is satisfied.\n","properties":{"when":{"type":"string","enum":["record_created","record_updated","extract_not_empty","lookup_not_empty","lookup_empty","expression"],"description":"Condition that gates whether this mapping entry is applied.\n`lookup_not_empty` / `lookup_empty` evaluate the lookup named by the\nsibling `lookupName`; `expression` evaluates the sibling `expression`.\n"},"lookupName":{"type":"string","description":"Lookup to evaluate for the `lookup_not_empty` / `lookup_empty`\nconditions.\n"},"expression":{"type":"string","description":"Expression evaluated when `when` is `expression`.\n"}}}}},"Form":{"type":"object","description":"Configuration for creating user-friendly settings forms that make it easier for less technical users\nto configure integration resources.\n\n**Settings form builder**\n\nThe Settings Form Builder allows you to create or edit user-friendly fields that prompt for text entry\nor selections that will be returned as settings applied to this resource. Your forms can include any\nfield types that you see elsewhere in integrator.io, such as:\n\n- Text fields\n- Dropdown selections\n- Checkboxes\n- Radio buttons\n- Date pickers\n- Multi-select fields\n- Search fields\n\nForm fields make it much easier for less technical users to work with your integration settings by:\n\n- Providing clear labels and help text\n- Enforcing validation rules\n- Offering pre-defined selection options\n- Grouping related settings logically\n- Supporting conditional visibility\n- Creating a consistent user experience\n","properties":{"form":{"type":"object","description":"Configuration that defines the structure, fields, and behavior of the settings form.\n\nThis object contains the complete definition of the form's layout, fields, validation rules,\nand interactive behaviors. The specific structure depends on the form complexity and can include\nfield definitions, sections, conditional display logic, and default values.\n\nThe form configuration is typically created and managed through the visual Form Builder interface\nrather than edited directly as JSON.\n","properties":{"fieldMap":{"type":"object","description":"A mapping of field identifiers to their configuration objects.\nEach key in this object represents a unique field ID, and the value contains\nall the configuration settings for that specific form field.\n","additionalProperties":{"type":"object","description":"Configuration for an individual form field.\n","properties":{"id":{"type":"string","description":"Unique identifier for this field within the form: layout containers and conditions\nrefer to the field by it. Use the same string as the field's fieldMap key and its `name`.\n"},"name":{"type":"string","description":"Key the field's value is saved under: one top-level key of the resource's `settings`,\nwhich the form also reads the value back from when it opens. Nested objects inside\n`settings` are written to it directly, not through a form field's `name`.\n"},"type":{"type":"string","description":"The type of form control to render for this field.\n","enum":["text","checkbox","radiogroup","relativeuri","editor","keyvalue","select","multiselect","toggle","datetime","date","exportSelect","staticMap"]},"label":{"type":"string","description":"Display label shown next to the field in the form.\n"},"description":{"type":"string","description":"Detailed explanation text that appears below the field, providing more context\nthan the label or helpText.\n"},"helpText":{"type":"string","description":"Explanatory text that appears when hovering over the help icon next to the field.\nUsed to provide additional guidance on how to use the field.\n"},"required":{"type":"boolean","description":"When true, the field must have a value before the form can be submitted. Only the form\nenforces it: an API write of `settings` is stored without the value.\n","default":false},"defaultValue":{"description":"Value the form shows while the resource's `settings` holds no value under the field's\n`name`.\n"},"multiline":{"type":"boolean","description":"For text fields, determines whether the input should be a multi-line text area\ninstead of a single-line input.\n","default":false},"rowsMax":{"type":"integer","description":"For multiline text fields, specifies the maximum number of visible rows.\n"},"inputType":{"type":"string","description":"For text fields, specifies the HTML input type attribute to apply additional\nvalidation or specialized input behavior.\n","enum":["text","number","email","password","tel","url"]},"delimiter":{"type":"string","description":"For text fields, specifies a character to use for splitting the input into an array.\nUsed for collecting multiple values in a single text field.\n"},"mode":{"type":"string","description":"For editor fields, specifies the type of content being edited for syntax highlighting.\n","enum":["json","xml","csv","text"]},"keyName":{"type":"string","description":"For keyvalue and staticMap fields, names the first column: each saved row holds that\ncolumn's value under this key. A keyvalue field without it uses `key`.\n"},"valueName":{"type":"string","description":"For keyvalue and staticMap fields, names the second column: each saved row holds that\ncolumn's value under this key. A keyvalue field without it uses `value`.\n"},"keyLabel":{"type":"string","description":"For staticMap fields, heading of the first column.\n"},"valueLabel":{"type":"string","description":"For staticMap fields, heading of the second column.\n"},"keyOptions":{"type":"array","description":"For staticMap fields, suggestions offered in the first column's cells. A cell still\naccepts a typed value.\n","items":{"type":"object","properties":{"label":{"type":"string","description":"Display text for the suggestion.\n"},"value":{"type":"string","description":"Value saved when the suggestion is chosen.\n"}}}},"valueOptions":{"type":"array","description":"For staticMap fields, suggestions offered in the second column's cells. A cell still\naccepts a typed value.\n","items":{"type":"object","properties":{"label":{"type":"string","description":"Display text for the suggestion.\n"},"value":{"type":"string","description":"Value saved when the suggestion is chosen.\n"}}}},"keyResource":{"type":"object","description":"For staticMap fields, fills the first column's suggestions from an export the form\ncarries inline in `virtual`, run the way an exportSelect's `resource` is. With\n`keyResource` or `valueResource` set, the form requires both columns in every row\nunless `keyValueRequired` is false.\n","properties":{"virtual":{"type":"object","description":"Export document to run, inline, shaped like an exportSelect's `resource.virtual`.\n","additionalProperties":true}}},"valueResource":{"type":"object","description":"For staticMap fields, fills the second column's suggestions from an export the form\ncarries inline in `virtual`, run the way an exportSelect's `resource` is.\n","properties":{"virtual":{"type":"object","description":"Export document to run, inline, shaped like an exportSelect's `resource.virtual`.\n","additionalProperties":true}}},"keyValueRequired":{"type":"boolean","description":"For staticMap fields with `keyResource` or `valueResource`, when false, saves rows\nthat fill only one column; by default the form drops them.\n","default":true},"resource":{"type":"object","description":"For exportSelect fields, the export whose records become the options. When the form\nopens, the UI previews the export in `virtual` (with `test.limit` 100 unless the export\nsets its own) and lists the preview's `data`: each record needs a `label` and a `value`\n(a string or number serves as both), so shape them with the export's `transform`. A\nsaved export can't be referenced, and an HTTP export that finds no records leaves the\nfield showing an error instead of options.\n","properties":{"virtual":{"type":"object","description":"Export document to run, inline: `adaptorType`, its adaptor block, and an optional\n`transform`. `_connectionId` defaults to the connection of the export or import the\nform belongs to; set it on any other resource's form.\n","additionalProperties":true}}},"multiselect":{"type":"boolean","description":"For exportSelect fields, when true, the user can choose several options and the value\nis saved as an array.\n","default":false},"supportHandlebars":{"type":"boolean","description":"For exportSelect fields, when true, renders the `resource.virtual` export as a Handlebars\ntemplate before each preview (all but its `transform`, `hooks`, and `filter`). On an\nintegration, flow group, flow, export, or import form, the form's current values are\nunder that form's scope name (`integration`, `flowGrouping`, `flow`, `export`, or\n`import`), beside the saved settings of the scopes above it, with no `settings.`\nprefix; on a connection or iClient form the template has no context. The options then\nload through `refreshOptionsOnChangesTo` or `typeAheadSearch`, or when the user\nrefreshes the field.\n","default":false},"refreshOptionsOnChangesTo":{"type":"array","items":{"type":"string"},"description":"For exportSelect fields with `supportHandlebars`, ids of the fields whose values the\ntemplated export uses: the options load once one of them has a value and reload whenever\none changes.\n"},"typeAheadSearch":{"type":"boolean","description":"For exportSelect fields, when true, re-runs the templated export as the user types, with\nthe typed text as this field's own value; an empty box clears the options, and typed\ntext is accepted as the value.\n","default":false},"showDelete":{"type":"boolean","description":"For keyvalue fields, determines whether to show a delete button for each key-value pair.\n"},"doNotAllowFutureDates":{"type":"boolean","description":"For date and datetime fields, restricts selection to dates not in the future.\n"},"skipTimezoneConversion":{"type":"boolean","description":"For datetime fields, prevents automatic timezone conversion of the date value.\n"},"options":{"type":"array","description":"For fields that present choices (select, multiselect, radiogroup, toggle), defines\nthe available options.\n","items":{"anyOf":[{"title":"Option group","type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"oneOf":[{"title":"String value","type":"string"},{"title":"Label-value pair","type":"object","properties":{"label":{"type":"string","description":"Display text for the option.\n"},"value":{"type":"string","description":"Value to store when this option is selected.\n"}}}]},"description":"Array of option values/labels to display in the selection control.\n"}}},{"title":"Label-value pair","type":"object","required":["label"],"properties":{"label":{"type":"string","description":"Display text for the option.\n"},"value":{"type":"string","description":"Value to store when this option is selected.\n"}}}]}},"visibleWhen":{"type":"array","description":"Conditional display rules that determine when this field should be visible.\nIf empty or not provided, the field is always visible.\n","items":{"type":"object","properties":{"field":{"type":"string","description":"The ID of another field whose value controls the visibility of this field. The\nsave doesn't check it: a rule naming an ID that isn't in the form counts as met,\nso the field stays visible.\n"},"is":{"type":"array","items":{"type":["string","boolean","number","null"]},"description":"Array of values - if the referenced field has any of these values,\nthis field will be visible. Values may be strings, booleans (for\ncheckbox/toggle fields), numbers (for numeric inputs), or null.\n"}}}}}}},"layout":{"type":"object","description":"Defines how the form fields are arranged and grouped in the UI.\nThe layout can organize fields into columns, sections, or other visual groupings.\n","properties":{"type":{"type":"string","description":"The type of layout to use for the form.\n","enum":["column","collapse","box","indent","tabWithoutSave","verticalTabWithoutSave"]},"fields":{"type":"array","items":{"type":"string"},"description":"Field IDs shown above the containers, without a heading.\n"},"containers":{"type":"array","description":"Array of container objects that group fields or contain nested containers.\nEach container can represent a column, box, indented section, or collapsible section.\n","items":{"type":"object","properties":{"type":{"type":"string","description":"The visual style of the container.\n","enum":["indent","box","collapse"]},"label":{"type":"string","description":"The heading text displayed for this container.\n"},"fields":{"type":"array","items":{"type":"string"},"description":"Array of field IDs that should be displayed in this container.\nEach ID must correspond to a key in the fieldMap object: the save doesn't check it,\nand an ID that doesn't renders nothing. A field the layout doesn't list isn't shown.\n"},"containers":{"type":"array","description":"Nested containers within this container. Allows for hierarchical organization\nof fields with different visual styles.\n","items":{"type":"object","properties":{"label":{"type":"string","description":"The heading text displayed for this nested container.\n"},"fields":{"type":"array","items":{"type":"string"},"description":"Array of field IDs that should be displayed in this nested container.\n"}}}}}}}}}},"additionalProperties":true},"init":{"type":"object","description":"Configuration for custom JavaScript initialization that executes when the form is first loaded.\n\nThis object defines a JavaScript hook that prepares the form for use, sets initial field values,\nperforms validation, or otherwise customizes the form behavior before it is displayed to the user.\n\n**Function signature**\n\nThe initialization function is invoked with a single 'options' argument containing contextual information:\n```javascript\nfunction formInit(options) {\n  // Process options and return the form object\n  return options.resource.settingsForm.form;\n}\n```\n\n**Available context**\n\nThe 'options' argument provides access to:\n- `options.resource` - The current resource being configured\n- `options.parentResource` - The parent of the current resource\n- `options.grandparentResource` - The grandparent of the current resource\n- `options.license` - For integration apps, the license provisioned to the integration\n- `options.parentLicense` - For integration apps, the parent of the license\n- `options.sandbox` - Boolean flag indicating whether the script is running in a sandbox environment\n\n\n**Common uses**\n\n- Dynamically generate field options based on resource configuration\n- Pre-populate default values from related resources\n- Apply conditional logic that depends on resource properties\n- Add, remove, or modify form fields based on user permissions or account settings\n- Fetch external data to populate selection options\n- Implement complex validation rules that depend on resource context\n- Create branching form experiences based on user selections\n\n**Return value**\n\nThe function must return a valid form object that the UI can render.\nThrowing an exception will signal an error to the user.\n\nThe UI runs the function each time it opens the form and renders the returned form instead of\n`form`, then fills in the values from `settings`; a thrown error's message replaces the form.\n","properties":{"function":{"type":"string","description":"The name of the function to execute within the referenced script.\n\nThis property specifies which function to invoke from the script\nreferenced by _scriptId. The function will be called when the form\nis initialized and should handle any custom setup logic.\n\nThe function must follow the expected signature and return a valid form object.\n"},"_scriptId":{"type":"string","format":"objectId","description":"Reference to a predefined script resource containing the initialization function.\n\nThe referenced script should contain the function specified in the\n'function' property. On an integration, a flow group, a flow, an export, an import, or a\nconnection, an id that isn't a script in the same account (or, on an Integration App's\nresources, one the app shares) is refused with 422 `invalid_ref`. An iClient save\nstores the id without that check.\n"}}}}},"PreSave":{"type":"object","description":"Defines a JavaScript hook that executes before the resource is saved.\n\nThis hook allows for programmatic validation, transformation, or enrichment of the\nresource itself before it is persisted. It can be used to enforce business rules,\nset derived properties, or implement cross-field validations that can't be expressed\nthrough the standard UI.\n\n**Function signature**\n\nThe preSave function is invoked with a single 'options' argument containing:\n```javascript\nfunction preSave(options) {\n  // Process options and return the modified resource\n  return options.newResource;\n}\n```\n\n**Available context**\n\nThe 'options' argument provides access to:\n- `options.newResource` - The resource being saved (with pending changes)\n- `options.oldResource` - The previous version of the resource (before changes)\n- `options.sandbox` - Boolean flag indicating whether the script is running in a sandbox environment\n\n\n**Common uses**\n\n- Enforcing complex business rules across multiple fields\n- Automatically deriving field values based on other configuration\n- Performing validation that depends on external systems or data\n- Normalizing or standardizing configuration values\n- Adding computed or derived properties\n- Implementing versioning or change tracking\n- Dynamically looking up data using the Celigo API module to enrich configuration\n\n**Return value**\n\nThe function must return the newResource object (potentially modified) to be saved.\nThrowing an exception will prevent saving and signal an error to the user.\n","properties":{"function":{"type":"string","description":"The name of the function to execute within the referenced script.\n\nThis property specifies which function to invoke from the script\nreferenced by _scriptId. The function will be called just before\nthe resource is saved.\n\nThe function must follow the expected signature and return the resource object.\n"},"_scriptId":{"type":"string","format":"objectId","description":"Reference to a predefined script resource containing the preSave function.\n\nThe referenced script should contain the function specified in the\n'function' property. This script must be accessible within the user's account\nand have appropriate permissions.\n"}}},"Settings":{"type":"object","description":"Configuration settings that can be accessed by hooks, filters, mappings and handlebars templates at runtime.\n\nIt enables customization of the resource's logic, allowing hooks, mappings, filters, and\nhandlebars to access and apply the settings at runtime.\n\n**Usage**\n\nThe settings object can store arbitrary JSON data that you want to save with the resource.\nWhile it's often populated through a form defined in the `settingsForm` field, you can also:\n\n- Directly provide JSON settings without using a form\n- Store configuration values used by hooks and templates\n- Create resource-specific constants and parameters\n- Maintain lookup tables or mapping structures\n- Define conditional logic parameters\n\n**Accessibility**\n\nSettings are available in:\n- All handlebars fields for building dynamic payloads\n- Field mapping expressions\n- JavaScript hooks via the options object\n- Filters and transformations\n\nAt runtime, the settings objects in the step's execution chain are gathered into a\nsingle `settings` context keyed by fixed scope keys. Flow runs populate the full\nchain: `settings.integration.*`, `settings.flowGrouping.*` (the flow group the flow\nbelongs to), `settings.flow.*`, `settings.connection.*`, `settings.iClient.*`, and\nthe running step's own scope — `settings.export.*` on exports/lookups,\n`settings.import.*` on imports (the key matches the step type). Steps executing\ninside a My API or a Tool receive only the step's own scope plus\n`settings.connection.*` / `settings.iClient.*`; the integration, flowGrouping, and\nflow scopes resolve empty there. The scope key is literal — resource display names\nand settingsForm section labels are never part of the path. The segments after the\nscope mirror the stored settings JSON key path exactly: a top-level field is\n`settings.<scope>.<fieldId>`, and nested objects add one segment per JSON key\n(e.g. `settings.flowGrouping.Customer.region` when the group's settings JSON nests\n`region` under `Customer`). References without a scope key\n(e.g. `{{settings.myField}}`) resolve to empty strings.\n\n**Best practices**\n\nFor non-technical users, create a custom form instead of editing the JSON directly.\nThis provides a user-friendly interface for updating settings without requiring JSON knowledge.\n","additionalProperties":true},"ResourceResponse":{"type":"object","description":"Response","properties":{"_id":{"type":"string","format":"objectId","readOnly":true,"description":"Unique identifier for the resource. Format is a 24-character hexadecimal string."},"createdAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was created. Set automatically and cannot be modified."},"lastModified":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the resource was last updated. Changes whenever any property is modified."},"deletedAt":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the resource was soft-deleted. When null or absent, the resource is active."}},"required":["_id"]},"IAResourceResponse":{"type":"object","description":"Integration app response fields for resources that are part of integration apps","properties":{"_integrationId":{"type":"string","format":"objectId","readOnly":true,"description":"Reference to the specific integration instance that contains this resource.\n\nThis field is only populated for resources that are part of an integration app\ninstallation. It contains the unique identifier (_id) of the integration\nresource that was installed in the account.\n\nThe integration instance represents a specific installed instance of an\nintegration app, with its own configuration, settings, and runtime environment.\n\nThis reference enables:\n- Tracing the resource back to its parent integration instance\n- Permission and access control based on integration ownership\n- Lifecycle management (enabling/disabling, updating, or uninstalling)\n"},"_connectorId":{"type":"string","format":"objectId","readOnly":true,"description":"Integration App (connector) that defines this resource. Present only on resources an\nIntegration App owns. The app locks parts of these documents, and a PUT that changes a\nlocked field answers 200 but keeps the stored value, so read the document back to confirm\na change."}}},"AIDescription":{"type":"object","description":"AI-generated descriptions and documentation for the resource.\n\nThis object contains automatically generated content that helps users\nunderstand the purpose, behavior, and configuration of the resource without\nrequiring them to analyze the technical details. The AI-generated content\nis sanitized and safe for display in the UI.\n","properties":{"summary":{"type":["string","null"],"description":"Brief AI-generated summary of the resource's purpose and functionality.\n\nThis concise description provides a quick overview of what the resource does,\nwhat systems it interacts with, and its primary role in the integration.\nThe summary is suitable for display in list views, dashboards, and other\ncontexts where space is limited.\n\nMaximum length: 10KB\n"},"detailed":{"type":["string","null"],"description":"Comprehensive AI-generated description of the resource's functionality.\n\nThis detailed explanation covers the resource's purpose, configuration details,\ndata flow patterns, filtering logic, and other technical aspects. It provides\nin-depth information suitable for documentation, tooltips, or detailed views\nin the administration interface.\n\nThe content may include HTML formatting for improved readability.\n\nMaximum length: 10KB\n"},"generatedOn":{"type":["string","null"],"format":"date-time","description":"Timestamp indicating when the AI description was generated.\n\nThis field helps track the freshness of the AI-generated content and\ndetermine when it might need to be regenerated due to changes in the\nresource's configuration or behavior.\n\nThe timestamp is recorded in ISO 8601 format with UTC timezone (Z suffix).\n"}}},"APIM":{"type":"array","description":"Read-only field that stores information about the integration resources\npublished in the API Management (APIM) platform.\n\nThis field tracks the relationship between integrator.io resources and their\npublished counterparts in the APIM platform, which is\ntightly integrated with the Celigo UI. When resources are \"pushed\" to APIM,\nthis field is populated with the relevant identifiers and statuses.\n","items":{"type":"object","properties":{"apiId":{"type":"string","description":"Identifier for the API where this integrator.io resource is published in the APIM.\n\nThis is an APIM resource identifier (not prefixed with underscore like Celigo IDs)\nthat uniquely identifies the API in the API Management platform.\n"},"flowId":{"type":"string","description":"Identifier for the flow within the API where this integrator.io resource is linked.\n\nWhen an API has multiple integrator.io resources linked, each resource is associated\nwith a specific flow in the API, identified by this field. This is an APIM\nresource identifier.\n"},"status":{"type":"string","description":"Indicates the publishing stage of the integrator.io resource in APIM.\n\nPossible values:\n- 'oaspending': The resource is published but the OpenAPI Specification (OAS) is not\n  yet published. The apiId will be updated with the API ID created in APIM.\n- 'published': The OpenAPI Specification for the integrator.io resource has been\n  successfully uploaded to APIM.\n","enum":["oaspending","published"]}}}},"Export":{"type":"object","required":["_id","name","adaptorType","createdAt","lastModified"],"description":"Export object as returned by the API.","allOf":[{"$ref":"#/components/schemas/ExportBase"},{"$ref":"#/components/schemas/ResourceResponse"},{"$ref":"#/components/schemas/IAResourceResponse"},{"type":"object","properties":{"aiDescription":{"$ref":"#/components/schemas/AIDescription"},"apim":{"$ref":"#/components/schemas/APIM"},"apiIdentifier":{"type":"string","readOnly":true,"description":"API identifier assigned to this export."},"asynchronous":{"type":"boolean","readOnly":true,"description":"Server-managed execution-mode flag set on creation."},"__linkedLookupCacheIds":{"type":"array","readOnly":true,"description":"Lookup caches linked to this export, managed by the platform.","items":{"type":"string","format":"objectId"}},"_sourceId":{"type":"string","format":"objectId","readOnly":true,"description":"Reference to the source resource this export was created from."},"_templateId":{"type":"string","format":"objectId","readOnly":true,"description":"Template this export was created from."},"draft":{"type":"boolean","readOnly":true,"description":"When true, this export is in draft state and has not been confirmed."},"draftExpiresAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when the draft version of this export expires."},"debugUntil":{"type":"string","format":"date-time","readOnly":true,"description":"Turns on step debug capture until this time: while it lies in the future, every\nrequest this export sends is recorded in the step's debug log\n(`GET /v1/{flows|apis|tools}/{containerId}/{_id}/requests`). Read-only on PUT —\nwritten only by `PATCH /v1/exports/{_id}` on `/debugUntil`, at most 3600 seconds\nahead (`422` `update_not_allowed` beyond that); `remove` stops capture."}}}]},"ExportBase":{"type":"object","description":"Writable export fields shared by the request and response schemas.","properties":{"name":{"type":"string","maxLength":100,"description":"Display name for the export, shown in the flow builder, job history, and error logs.\nDescriptive, unique names indicating the data source and purpose make large accounts easier to manage."},"description":{"type":["string","null"],"description":"Free-text summary of what the export retrieves and why. Shown in the UI and available\nto AI agents for context; has no effect on execution.","maxLength":5120},"_connectionId":{"format":"objectId","type":["string","null"],"description":"Connection this export uses to reach the source system. The connection's type must be\ncompatible with the export's `adaptorType` (e.g. an `HTTPExport` needs an `http` connection;\n`NetSuiteExport` and `NetSuiteHTTPExport` both need a `netsuite` connection).\nServer-required unless `type` is `webhook` or `simple` (those receive data instead of\nfetching it) or `adaptorType` is `TopicExport` (Event Streams listeners are delivered\nby the platform itself and must omit it). A `WebhookExport` saved without `type: webhook`\nis refused with 422 \"_connectionId is required unless type is [webhook|simple]\"; a webhook\nexport that does carry a connection saves and updates normally."},"adaptorType":{"type":"string","description":"Selects the adaptor technology that executes this export, which determines the compatible\nconnection types and which adaptor-specific configuration object must also be supplied\n(e.g. set `salesforce` when using `SalesforceExport`).","enum":["HTTPExport","FTPExport","AS2Export","S3Export","NetSuiteExport","NetSuiteHTTPExport","SalesforceExport","JDBCExport","RDBMSExport","MongodbExport","DynamodbExport","WrapperExport","SimpleExport","WebhookExport","FileSystemExport","TopicExport","RESTExport"]},"nsDomainType":{"type":"string","enum":["suitetalk","restlet"],"description":"Selects which NetSuite REST host a `NetSuiteHTTPExport` calls. The server derives the\nfull base URL from the connection's NetSuite account\n(`https://<account>.suitetalk.api.netsuite.com` or `https://<account>.restlets.api.netsuite.com`)\nand signs each request with the connection's token-based credentials, so `http.relativeURI`\ncarries the complete path starting at `/services/rest/...` or `/app/site/hosting/restlet.nl`.\nIgnored on other adaptor types."},"type":{"type":["string","null"],"description":"Operational mode of the export. When omitted, the export retrieves all available records —\nthe standard batch mode, which is also the right choice for parsing structured files\n(CSV/XML/JSON) into records. Most modes take their matching configuration object (e.g. a\n`webhook` object when `type` is `webhook`); an HTTP or SQL `delta` export saves without a\n`delta` object because its request carries `{{lastExportDateTime}}`, though\n`delta.startDate` sets its first window. It still needs `type: delta`: on an export\nwith no `type`, `{{lastExportDateTime}}` and `{{currentExportDateTime}}` render\nempty on every run, so a request that carries them goes out with blank values each time\n(live-verified 2026-10-22). Use `blob` only to transfer raw files without parsing their\ncontents.","enum":["webhook","test","delta","once","tranlinedelta","simple","blob","distributed","stream","all"]},"pageSize":{"type":["integer","null"],"description":"Number of records per page streamed to downstream flow steps when the export is the\nflow's source step. Pages are additionally capped at 5 MB regardless of record count, so\nvery large records may produce smaller pages. The server does not validate the value.\nA lookup (`isLookup: true`) runs to completion and hands every result to the incoming\nrecord as one response, capped at 5 MB in total, so the value does not change how many\nresults a lookup returns; a result set larger than that belongs in a standalone export\nstep. In a test run or preview the value also caps the records fetched (default 20).\nOn a webhook listener the records of each incoming request are paged separately, before\nany hook or downstream step runs.","default":20},"dataURITemplate":{"type":"string","description":"Handlebars template that builds a link back to each record in the source application's UI\n(e.g. `https://my.salesforce.com/lightning/r/Contact/{{record.Id}}/view`). The resolved\nURL is stored with error records in job history so users can jump straight to the record."},"traceKeyTemplate":{"type":["string","null"],"description":"Handlebars template that overrides how each record's unique trace key is generated, used\nto track records through the flow and match retries to prior errors (e.g.\n`{{join \"_\" record.customerId record.orderId}}`). When omitted, the system picks a unique\nfield automatically from the record's top level, by exact field name (`GET\n/v1/flows/{_id}/traceKey` serves the pattern). The rendered value of a template is stamped\non each record as `io_custom_trace_key` and travels downstream with it. If a transform\nreshapes the data first, omit the `record.` prefix. Trace keys are capped at 256 characters;\na longer key is stored truncated from the middle, keeping the beginning and end of the\nvalue. Downstream imports use the trace key as their default concurrency lock key (a blank\nkey means no lock); it never de-duplicates — a record emitted twice with the same key is\nprocessed twice."},"skipRetries":{"type":"boolean","description":"When true, the platform does not retain the source data needed to retry failed records,\nreducing stored data at the cost of being unable to reprocess errors from this export.\nShown as \"Do not store retry data\" in the UI. Defaults to false."},"oneToMany":{"$ref":"#/components/schemas/OneToMany"},"pathToMany":{"$ref":"#/components/schemas/PathToMany"},"isLookup":{"type":"boolean","description":"When true, the export runs as a mid-flow lookup: it executes once per incoming record,\nusing the input record's fields to parameterize the request, and passes the results to\nsubsequent steps. When false, the export runs as a standalone data source. A lookup's\nresults are not paged through the flow: every page the adaptor fetches is merged into one\nresponse for the record, up to 5 MB, so `pageSize` does not limit or extend what a lookup\nreturns. A lookup that must return more than that is better modeled as a standalone\nexport step."},"groupByFields":{"$ref":"#/components/schemas/GroupBy"},"delta":{"$ref":"#/components/schemas/Delta"},"test":{"$ref":"#/components/schemas/Test"},"once":{"$ref":"#/components/schemas/Once"},"webhook":{"$ref":"#/components/schemas/Webhook"},"simple":{"$ref":"#/components/schemas/Simple"},"distributed":{"$ref":"#/components/schemas/Distributed"},"cdc":{"$ref":"#/components/schemas/Cdc"},"topic":{"$ref":"#/components/schemas/Topic-2"},"filesystem":{"$ref":"#/components/schemas/FileSystem-2"},"http":{"$ref":"#/components/schemas/Http-2"},"file":{"$ref":"#/components/schemas/File-2"},"salesforce":{"$ref":"#/components/schemas/Salesforce-3"},"as2":{"$ref":"#/components/schemas/AS2-2"},"dynamodb":{"$ref":"#/components/schemas/DynamoDB-2"},"ftp":{"$ref":"#/components/schemas/FTP-2"},"jdbc":{"$ref":"#/components/schemas/JDBC-2"},"mongodb":{"$ref":"#/components/schemas/MongoDB-2"},"netsuite":{"$ref":"#/components/schemas/NetSuite-3"},"rdbms":{"$ref":"#/components/schemas/RDBMS-2"},"s3":{"$ref":"#/components/schemas/S3-3"},"wrapper":{"$ref":"#/components/schemas/Wrapper-3"},"parsers":{"$ref":"#/components/schemas/Parsers"},"filter":{"description":"Filter applied to each record after the export's transform and before its preSavePage\nhook. Records that match continue through the flow; records that don't are silently\ndropped. Filter expressions reference the transformed record's fields.","allOf":[{"$ref":"#/components/schemas/Filter"}]},"inputFilter":{"description":"Filter applied to incoming records before a lookup export queries the external system.\nOnly matching input records trigger lookup calls; non-matching records pass through the\nstep unenriched, which cuts unnecessary API traffic. Only relevant when `isLookup` is true.","allOf":[{"$ref":"#/components/schemas/Filter"}]},"mappings":{"description":"Field mappings applied to each incoming record before a lookup HTTP request is made,\nreshaping the upstream record into the structure the lookup target API expects. Only\nsupported when `isLookup` is true and `adaptorType` is `HTTPExport` or\n`NetSuiteHTTPExport`; do not set it on source exports or other lookup adaptors.","allOf":[{"$ref":"#/components/schemas/Mappings"}]},"transform":{"description":"Transformation that reshapes records before they leave this step — source exports\ntransform the retrieved records, lookup exports transform the lookup results. Commonly\nused to flatten nested lookup responses (e.g. `results[0].id` → `id`); merging results\nback into source records is handled separately by the flow's response mapping.","allOf":[{"$ref":"#/components/schemas/Transform"}]},"hooks":{"type":"object","description":"Custom JavaScript hooks that run at fixed points in the export lifecycle for\ntransformations, validation, and filtering beyond what configuration alone can express.\nA hook name not listed here (an import's `preMap`, for example) is dropped on save\nwithout an error, so read `hooks` back after saving.","properties":{"preSavePage":{"type":"object","description":"Hook that runs on each page of records after the export's transform and output\nfilter, before the page is sent to downstream steps; commonly used to flatten\nstructures, drop unwanted records, or add computed fields. It receives the page as\n`options.data` (with `errors`, `pageIndex`, `configuration` and `settings`) and returns\n`{data, errors, abort, newErrorsAndRetryData}`. Return that object, not the array: a\nhook that returns `options.data` itself, or a filtered copy, drops the page without an\nerror, and the run completes with 0 records, 0 errors and 0 pages. An exception thrown\nhere fails every record on that page, each with its own retry data, and later pages\nstill run.","properties":{"function":{"type":"string","description":"Function to invoke within the referenced script."},"_scriptId":{"type":"string","format":"objectId","description":"Script containing the hook function named in `function`. Must be a script in this\naccount: any other id fails the save with 422 (\"Only a script owned by the user or\na script shared by the Integration App can be used at hooks.preSavePage._scriptId.\")."},"_stackId":{"type":"string","format":"objectId","description":"Stack that hosts the hook logic, used instead of a script for stack-based deployments."},"configuration":{"type":"object","description":"Static parameters passed to the hook function at runtime, letting one script be\nreused across exports with different settings."}}},"preParse":{"type":"object","description":"Hook that runs on file-based exports after the file is downloaded and before it is\nparsed: it receives the raw file text as `preParseData` together with `fileMeta` and\nreturns both, modified or not, for the configured parser to read. Used to normalize a\nnon-standard file — fix a header row, strip a preamble, convert a format — before\nparsing; an exception thrown here fails the run.","properties":{"function":{"type":"string","description":"Function to invoke within the referenced script."},"_scriptId":{"type":"string","format":"objectId","description":"Script containing the hook function named in `function`."},"_stackId":{"type":"string","format":"objectId","description":"Stack that hosts the hook logic, used instead of a script for stack-based deployments."},"configuration":{"type":"object","description":"Static parameters passed to the hook function at runtime, letting one script be\nreused across exports with different settings."}}}}},"settingsForm":{"$ref":"#/components/schemas/Form"},"settings":{"$ref":"#/components/schemas/Settings"},"mockOutput":{"$ref":"#/components/schemas/MockOutput"},"_ediProfileId":{"type":"string","format":"objectId","description":"EDI profile this export uses to parse incoming X12 or EDIFACT documents into structured\nJSON — it supplies the envelope qualifiers, delimiters, version, and validation rules.\nSet it when the export parses or validates EDI files (a save with a `delimited/x12` file\ndefinition at `version` `\"2\"` and no profile fails with 422 `missing_ediProfile`); omit\nit otherwise. Accepted on the file-reading exports — FTP, AS2, S3 and HTTP file mode\n(`http.type: \"file\"`)."},"_postParseListenerId":{"type":"string","format":"objectId","description":"Webhook export invoked once per file after EDI parsing, on both success and failure\n(errors are included in the payload when parsing fails). Primarily used to send\nfunctional acknowledgements (997/999) to trading partners. Accepted on the file-reading\nexports that parse EDI — FTP, AS2, S3 and HTTP file mode (`http.type: \"file\"`)."},"externalId":{"type":["string","null"],"description":"External identifier for correlating the export with a record in another system."},"_integrationId":{"type":["string","null"],"format":"objectId","description":"Not stored on an export: a value sent on create or on a full update is dropped, and a GET\ndoesn't return the field. An export isn't owned by an integration; to find the flows and\nother resources that use it, call `GET /v1/exports/{_id}/dependencies`."},"_connectorId":{"type":"string","format":"objectId","description":"Connector this export was created from, set when the export is part of an installed\nintegration app. The app's install sets it: a create that sends it is refused with 403\n(\"Cannot create an Integration App resource\")."},"unencrypted":{"type":"object","description":"Custom configuration values stored without encryption and returned in API responses."},"useTechAdaptorForm":{"type":"boolean","description":"When true, the UI presents the full technical adaptor form for this export instead of\nthe simplified assistant form."},"rawData":{"type":"string","description":"Key referencing the raw sample payload captured for preview and testing."},"preSave":{"$ref":"#/components/schemas/PreSave"},"assistant":{"type":"string","description":"Identifier for the connector assistant used to configure this export. For the cloud\nfile providers it also selects the provider-specific location rules —\n`azuredatalakestoragegen2` (container in `file.directory.name`, path in\n`http.relativeURI`) and `googlecloudstorage` (bucket in `file.directory.storageRootId`,\nprefix in `file.directory.id`)."},"assistantMetadata":{"type":["object","string"],"additionalProperties":true,"description":"Metadata associated with the connector assistant configuration."},"sampleData":{"type":["string","object","array","null"],"description":"Sample data payload used for previewing and testing the export."},"sampleHeaders":{"type":"array","description":"Sample HTTP headers used for previewing and testing the export.","items":{"type":"object","properties":{"name":{"type":"string","description":"Header name."},"value":{"type":"string","description":"Header value."}}}},"sampleQueryParams":{"type":"array","description":"Sample query parameters used for previewing and testing the export.","items":{"type":"object","properties":{"name":{"type":"string","description":"Query parameter name."},"value":{"type":"string","description":"Query parameter value."}}}}}},"GroupBy":{"type":"array","description":"Specifies which fields to use for grouping records in the export results. When configured, records with\nthe same values in these fields will be grouped together and treated as a single record by downstream\nsteps in your flow.\n\nFor example:\n- Group sales orders by customer ID to process all orders for each customer together\n- Group journal entries by accounting period to consolidate related transactions\n- Group inventory items by location to process inventory by warehouse\n\nWhen grouping is used, the export's page size determines the maximum number of groups per page, not individual\nrecords. Note that effective grouping typically requires that records with the same group field values appear\ntogether in the export data.\n\nNot a webhook listener feature: on a `webhook` export the platform applies it only within each page, after\nthe request has been paged, and only to adjacent rows — the field is not offered for listeners in the UI.\n","items":{"type":"string"}},"Delta":{"type":"object","description":"Configures incremental exports that retrieve only records created or modified since the\nflow's delta checkpoint; omit it for other export types. Adaptors that compare a record field\nname it in `dateField`, while HTTP exports and SQL queries carry {{lastExportDateTime}} in the\nrequest and save without a delta object. Each run's window runs from {{lastExportDateTime}}\n(the checkpoint) to {{currentExportDateTime}} (the time the run started). A first run is not a\nfull export: with no `startDate`, its {{lastExportDateTime}} is the run's own timestamp, so it\npicks up no earlier changes. The checkpoint moves to a run's window end only when the run\nexports records, so a run that exports nothing leaves it where it was, and after a failed run\nthe next run reuses the last checkpoint. On success, when `POST /v1/flows/{_id}/run` sends\n`export.endDate`, the flow's checkpoint is left at that value, so an end date in the past\nmoves the next run's lower bound back.","properties":{"dateField":{"type":"string","description":"Record timestamp field(s) compared against the last successful run time to identify\nchanged records. Accepts a single field or multiple comma-separated fields, processed\nsequentially — useful when different operations update different timestamp fields.\nIf the flow's own downstream steps update the exported records, add export criteria\nthat exclude already-processed records (or use a creation-time field for process-once\nflows) so each run doesn't re-export what the previous run wrote. Not supported on\nHTTP exports: embed {{lastExportDateTime}} in the relativeURI or body instead; an HTTP\nexport saved with dateField is refused with 422 \"Unsupported dateField in\nexport.delta.dateField\". For Salesforce this\nfield is required and defaults to the standard timestamp fields (LastModifiedDate,\nCreatedDate, SystemModstamp, LastActivityDate, LastViewedDate, LastReferencedDate) plus\nany custom timestamp fields."},"dateFormat":{"type":"string","description":"Moment.js format string applied to both delta variables, {{lastExportDateTime}} and\n{{currentExportDateTime}}, in HTTP requests and SQL queries alike. Leave unset unless the\nsource system requires a non-ISO8601 format; by default both render as ISO 8601 UTC with\nmilliseconds. Date-only formats truncate the time portion, widening the filter window."},"lagOffset":{"type":"integer","description":"Buffer in milliseconds subtracted from the last successful run timestamp, creating an\noverlapping window that catches records still propagating when the previous run executed.\nSet it when records created or modified near the run time are occasionally skipped due to\nreplication or indexing delays. Keep it as low as possible — larger values reprocess\nredundant records. A negative value shifts the window forward (look-ahead) instead of back."},"startDate":{"type":["string","null"],"format":"date-time","description":"Lower-bound cutoff for the first run. Omitting it doesn't export history: the first run\nthen starts at its own timestamp, an empty window. Set it to the earliest change the first\nrun should pick up. Later runs start from the flow's checkpoint, the end of the last run\nthat exported records."}}},"Test":{"type":"object","description":"Configures test exports that cap the number of records retrieved, for safely developing and\nvalidating against small data samples. Required when the export's type is \"test\"; omit for\nother export types. A test export behaves like a standard export in every other way (filters,\npagination, processing) and stores no state between runs.","properties":{"limit":{"type":"integer","default":1,"description":"Caps the total records a test run processes, counted on top-level records before\noneToMany processing; the maximum exists to prevent accidentally processing large\ndatasets during development. The cap applies across pages — processing stops once the\nlimit is reached regardless of pageSize. When transitioning to production, leave this in\nplace and change the export's type field; the limit then no longer applies.","minimum":1,"maximum":100}}},"Once":{"type":"object","description":"Configures flag-based exports that skip records an earlier run already processed. Required\nwhen the export's type is \"once\"; omit for other export types. Each run retrieves records\nwhere the tracking boolean field is false, then sets that field to true after successful\nprocessing so later runs skip them; if a run fails, the flags are left unchanged and the\nrecords are retried automatically on the next run.","properties":{"booleanField":{"type":"string","description":"API field name of the boolean/checkbox in the source system that tracks processed\nrecords: the export selects only records where this field is false, then sets it to true\nin batches after each successfully processed page. The field must be writeable by the\nexport's connection. Ensure no other process updates the same field — use a separate\nflag per export process — or records may be skipped unexpectedly."}}},"Webhook":{"type":"object","description":"Configuration for webhook listeners that receive data through incoming HTTP requests. Required when the export's `type` is `webhook`; omit for other export types. Senders POST to the export's listener URL, `https://api.integrator.io/v1/exports/{_id}/data` (or the same path on the account's regional API host); the platform validates each request using the configured `verify` method, passes the payload to subsequent flow steps, and returns a configurable HTTP response to the caller. A request is answered 204 by default when accepted, 401 when it fails verification, and 403 `unable_to_run_flows` while every flow the export feeds is disabled; a refused request is not queued, so the sender must resend it.","properties":{"provider":{"type":"string","description":"Source application sending the webhook, used to pre-configure security settings and payload parsing for that platform. Use `custom` (the default when omitted) for unlisted providers or when you need full manual control over the security configuration. Provider-specific choices may require credentials (tokens, keys) mandated by that platform.","enum":["github","shopify","travis","travis-org","slack","dropbox","onfleet","helpscout","errorception","box","stripe","aha","jira","pagerduty","postmark","mailchimp","intercom","activecampaign","segment","recurly","shipwire","surveymonkey","parseur","mailparser-io","hubspot","integrator-extension","custom","sapariba","happyreturns","typeform"]},"verify":{"type":"string","description":"Verification method applied to every incoming request before processing; required for all webhook exports. Each method needs companion fields: `hmac` requires `key`, `algorithm`, `encoding`, and `header` (except on connector-backed webhooks, where the connector definition supplies them); `token` requires `token`, with `tokenLocation` defaulting to `body` and selecting which location-specific field applies; `basic` requires `username` and `password`; `secret_url` requires `token`. Prefer `hmac` when the source system supports it — it is the most secure option; `secret_url` relies only on URL obscurity and suits non-sensitive data or testing.","enum":["token","hmac","basic","secret_url"]},"token":{"type":"string","description":"Shared secret used when `verify` is `token` or `secret_url`. For `token` verification, the value found at `path` in each request must exactly match (case- and whitespace-sensitive) or the request is rejected with a 401. For `secret_url`, the token is embedded in the webhook URL to create a hard-to-guess endpoint — generate a random, high-entropy value and treat it as a sensitive credential."},"algorithm":{"type":"string","description":"Hashing algorithm used to validate HMAC signatures when `verify` is `hmac`. Must match the algorithm the webhook sender uses — a mismatch causes every request to be rejected. Use `sha256` unless the provider explicitly requires another value.","enum":["sha1","sha256","sha384","sha512"]},"encoding":{"type":"string","description":"Encoding of the HMAC signature value when `verify` is `hmac`. Must match the encoding the webhook sender uses — a mismatch causes requests to be rejected with a 401 even when the signature is otherwise correct. With `provider: custom`, `hex` is compared as lowercase hex: an uppercase hex signature is rejected.","enum":["hex","base64"]},"key":{"type":"string","description":"Secret used to validate signatures when `verify` is `hmac`. The system computes a signature of the request body using this key and the configured `algorithm`, then compares it with the signature sent in the request header named by `header`. Treat it as a highly sensitive credential — never expose it in repositories or logs."},"header":{"type":"string","description":"Name of the request header that carries the HMAC signature when `verify` is `hmac`. Must match the header name the webhook sender uses (header names are case-insensitive); requests without this header are rejected with a 401. With `provider: custom` the header value must be the bare digest in the configured `encoding`: a value with a `sha256=` prefix is rejected with a 401."},"tokenLocation":{"type":"string","enum":["body","header","queryParam"],"default":"body","description":"Where the verification token is found when `verify` is `token`. Each location uses a different companion field: `body` uses `path`, `header` uses `tokenHeaderName` (and `tokenHeaderScheme`), `queryParam` uses `tokenQueryParamName`."},"path":{"type":"string","description":"JSON path into the request body holding the verification token when `verify` is `token` and `tokenLocation` is `body` (e.g. `meta.token`). The value at this path must exactly match `token` or the request is rejected."},"tokenHeaderName":{"type":"string","description":"Request header carrying the verification token when `verify` is `token` and `tokenLocation` is `header`."},"tokenHeaderScheme":{"type":"string","enum":["bearer","custom","none"],"default":"bearer","description":"Scheme prefixing the token in the header when `tokenLocation` is `header`."},"customTokenScheme":{"type":"string","description":"Custom header scheme prefix used when `tokenHeaderScheme` is `custom`."},"tokenQueryParamName":{"type":"string","description":"Query parameter carrying the verification token when `verify` is `token` and `tokenLocation` is `queryParam`."},"_httpConnectorId":{"type":"string","format":"objectId","description":"HTTP connector backing this listener when the webhook is provided by an assistant/connector (e.g. Slack); set by the platform for connector-backed listeners."},"requestMediaType":{"type":"string","enum":["json","xml","csv","urlencoded","plaintext"],"description":"Overrides how the incoming request body is parsed when the provider sends a non-standard content type. When omitted, the body is parsed by its Content-Type header."},"pathToRecords":{"type":"string","description":"JSON path into an object payload to the array whose elements become the records. When omitted, a top-level JSON array is emitted as one record per element and an object payload is emitted as a single record."},"includeParentData":{"type":"boolean","description":"When true and `pathToRecords` is set, each emitted record also carries the surrounding parent fields from the payload."},"username":{"type":"string","description":"Username half of the credentials when `verify` is `basic`. Incoming requests must include an `Authorization: Basic` header carrying the base64-encoded `username:password` pair. Use only over HTTPS to prevent credential interception."},"password":{"type":"string","description":"Password half of the credentials when `verify` is `basic`, validated together with `username` from the request's `Authorization` header. Use a strong, unique value and treat it as a sensitive credential."},"successStatusCode":{"type":"integer","description":"HTTP status code returned to the caller after successful processing; must be a valid 2xx code. The default 204 returns no response body and causes `successBody` to be ignored — set 200 or 202 when the caller needs a response body or expects a specific code.","default":204},"successBody":{"type":"string","description":"Response body returned to the caller after successful processing; ignored when `successStatusCode` is 204. Content type is set by `successMediaType`, and the value can be static text or structured JSON/XML, including handlebars expressions for dynamic values."},"successMediaType":{"type":"string","description":"Sets the Content-Type header on successful webhook responses. Only takes effect when a `successBody` is returned (a status code other than 204), and must match the actual format of that body.","default":"json","enum":["json","xml","plaintext"]},"successResponseHeaders":{"type":"array","description":"Custom headers added to successful webhook responses — for example CORS headers or correlation IDs. Headers defined here take precedence over automatically set headers such as Content-Type, and values support handlebars expressions for dynamic content.","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the header to set on successful webhook responses; headers defined here take precedence over automatically set headers such as Content-Type."},"value":{"type":"string","description":"Value sent for the header; supports handlebars expressions for dynamic content."}}}},"challengeResponseHeaders":{"type":"array","description":"Custom headers returned for webhook subscription verification (challenge) requests, which providers send before delivering real events. Required header values vary by provider — consult the provider's documentation, since incorrect challenge headers can prevent the subscription from being verified.","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the header to set on challenge (subscription verification) responses."},"value":{"type":"string","description":"Value sent for the header; consult the provider's documentation for required challenge header values."}}}},"challengeSuccessBody":{"type":"string","description":"Response body returned for webhook subscription verification (challenge) requests. Many providers require echoing back a challenge value from the request, which handlebars expressions can access — for example `{{hub.challenge}}` (Facebook/Instagram) or `{\"challenge\":\"{{challenge}}\"}` (Slack). An incorrect challenge response prevents the subscription from being verified."},"challengeSuccessStatusCode":{"type":"integer","description":"HTTP status code returned for webhook subscription verification (challenge) requests. Most providers expect the default 200; change it only when the provider's verification explicitly requires a different code.","default":200},"challengeSuccessMediaType":{"type":"string","description":"Sets the Content-Type header on challenge responses. Must match both the format of `challengeSuccessBody` and the provider's requirements — for example, Slack expects `json` while Facebook/Instagram verification expects `plaintext`.","default":"json","enum":["json","xml","plaintext"]}},"if":{"properties":{"verify":{"const":"hmac"}},"required":["verify"],"not":{"required":["_httpConnectorId"]}},"then":{"required":["key","algorithm","encoding","header"]},"else":{"if":{"properties":{"verify":{"const":"token"}},"required":["verify"]},"then":{"required":["token"],"if":{"properties":{"tokenLocation":{"const":"body"}},"required":["tokenLocation"]},"then":{"required":["path"]},"else":{"if":{"properties":{"tokenLocation":{"const":"header"}},"required":["tokenLocation"]},"then":{"required":["tokenHeaderName"]},"else":{"if":{"properties":{"tokenLocation":{"const":"queryParam"}},"required":["tokenLocation"]},"then":{"required":["tokenQueryParamName"]},"else":{"required":["path"]}}}},"else":{"if":{"properties":{"verify":{"const":"basic"}},"required":["verify"]},"then":{"required":["username","password"]},"else":{"if":{"properties":{"verify":{"const":"secret_url"}},"required":["verify"]},"then":{"required":["token"]}}}}},"Simple":{"type":"object","description":"Configuration for `simple` (data-loader) exports — exports that accept manually uploaded\nfiles through the data-loader UI instead of fetching data from a connection. Relevant only\nwhen the export's `type` is `simple`; the uploaded content is parsed using the same file\nsettings as file-based exports.","properties":{"file":{"$ref":"#/components/schemas/File-2"}}},"File-2":{"type":"object","description":"Controls how files are parsed, filtered, and processed across all file-based exports\n(FTP/SFTP, Amazon S3, simple file uploads, and other file sources). The type field selects\nthe file format and determines which format-specific object (csv, json, xlsx, xml, or\nfileDefinition) must be configured; the output field selects whether files are parsed into\nrecords, transferred as blobs, or listed as metadata only. The filter object selectively\nskips files before processing.","properties":{"encoding":{"type":"string","description":"Character encoding used to read and parse file content. If the encoding is unknown, try\nutf8 first (the default), then win1252 for Western-language files with garbled\ncharacters; consider the geographic origin of the data when selecting.","enum":["utf8","win1252","utf-16le","gb18030","macroman","iso88591","shiftjis"]},"type":{"type":"string","description":"Format of the files being processed; determines which format-specific configuration\nobject (csv, json, xlsx, xml, or fileDefinition) must be populated — other format\nobjects are ignored. Required for all file-based exports except blob exports (export\ntype \"blob\" or output \"blobKeys\").","enum":["csv","json","xlsx","xml","filedefinition"]},"output":{"type":"string","description":"Processing mode for retrieved files: parse contents into records, transfer files as\nunparsed blobs, or return only file metadata. Determines what data is passed to\nsubsequent flow steps.","enum":["records","metadata","blobKeys"]},"skipDelete":{"type":"boolean","default":false,"description":"When true, source files remain on the file server after processing; when false (the\ndefault), files are deleted after successful processing. Files that fail processing are\nnever deleted, regardless of this setting. Enable retention when files must be processed\nby other flows or systems, or kept for compliance."},"compressionFormat":{"type":"string","description":"Compression format of incoming files, which are decompressed before any other processing\n(parsing, filtering). Set this only when the source always delivers compressed files —\nif a file marked as compressed is not actually compressed, processing fails. Leave unset\nwhen files arrive uncompressed or only sometimes compressed.","enum":["gzip","zip"]},"purgeInternalBackup":{"type":"boolean","description":"When true, Celigo keeps no internal backup copies of files processed by this export;\nwhen false (the default), copies are retained for your account's retention period and\nare available for reprocessing or troubleshooting. Applies only to this export and only\nto Celigo's internal copies — source files are governed by skipDelete. Enable for highly\nsensitive data or zero-retention policies; without backups, recovery may require\nre-obtaining files from the source system."},"decrypt":{"type":"string","description":"Decryption applied to incoming files before any other processing; decryption runs before\ndecompression, and a decryption failure fails the file's processing entirely. The\nconnection must already be configured with the private key (and passphrase, if\napplicable) matching the public key used to encrypt the files. Only PGP/GPG encryption\nis currently supported.","enum":["pgp"]},"batchSize":{"type":["integer","null"],"description":"Number of files retrieved per batch; if a batch fails, the whole batch is retried\n(stored as `null` when not configured). Use\nlower values (10-50) for large files to reduce timeout and memory pressure, and higher\nvalues for many small files to improve throughput. Controls file retrieval only — record\npaging is governed by the export's pageSize.","maximum":1000},"sortByFields":{"type":"array","description":"Sorts the records parsed from each file before they are processed, establishing a\ndeterministic processing order (for example, chronological or priority-based). Sorting\nhappens after parsing but before any filtering or grouping, and does not modify the\nsource files. Sorting by the same fields used in groupByFields improves grouping\nperformance.","items":{"type":"object","properties":{"field":{"type":"string","description":"Record field to sort by; use dot notation for nested properties (e.g.\ncustomer.name). Field names are case-sensitive."},"descending":{"type":"boolean","description":"When true, sorts this field in descending order (newest/highest first); when false\nor omitted, sorts ascending. Directions can be mixed across fields in a\nmulti-field sort."}}}},"groupByFields":{"$ref":"#/components/schemas/GroupBy"},"groupEmptyValues":{"type":"boolean","description":"When true, records whose groupByFields values are empty are still grouped together rather\nthan each forming its own group; when false (the default), empty-keyed records are not\ngrouped. Only relevant when groupByFields is set."},"csv":{"type":"object","description":"Parsing settings for delimiter-separated text files (CSV, TSV, pipe-delimited, and\nsimilar). Configure when type is \"csv\".\n\nRequired when type is csv.","properties":{"columnDelimiter":{"type":"string","description":"Character sequence separating fields within each row; comma when omitted. Use \"\\t\"\nfor tab-delimited files; European-locale exports often use semicolons. A wrong\ndelimiter doesn't fail the parse: with a header row, each row becomes one field named\nafter the whole header line (`{\"order_id;sku;qty\": \"1001;TEE-M;2\"}`), so the failure\nshows up in later steps."},"rowDelimiter":{"type":"string","description":"Character sequence marking the end of each record; auto-detected from file content\nwhen omitted. Set explicitly (\"\\n\", \"\\r\\n\", or \"\\r\") only when auto-detection fails,\nsuch as for files with mixed line endings — an incorrect value merges or splits\nrecords."},"hasHeaderRow":{"type":"boolean","description":"When true (the default), the first row is read as field names rather than data, and\nthose names are available in mappings. When false, every row is treated as a data\nrecord and fields are referenced by position."},"trimSpaces":{"type":"boolean","description":"When true, removes leading and trailing whitespace from every field value during\nparsing; when false (the default), whitespace is preserved exactly as in the source.\nHeader row values are always trimmed regardless of this setting, and spaces between\nwords are never affected."},"rowsToSkip":{"type":"integer","description":"Number of rows at the top of the file to ignore before parsing begins — useful for\nreport titles, timestamps, or other metadata above the data. The header row (when\nhasHeaderRow is true) is expected after the skipped rows."},"disableQuoteAndStripEnclosingQuotes":{"type":"boolean","description":"When true, disables CSV quote processing: quotes are treated as literal characters,\nenclosing quotes are stripped, and delimiters inside quoted text split the field.\nWhen false (the default), standard RFC 4180 quoting applies and quoted fields\nprotect embedded delimiters. Enable only for files with non-standard or malformed\nquoting; review this setting first when field counts vary unexpectedly between rows."},"keyColumns":{"type":"array","description":"Field names whose values together identify a record's group when consecutive rows\nshare the same key — used to merge multi-line records (e.g. an order header repeated\nacross line-item rows) into a single record. Leave empty for one record per row; the\ncolumns must exist in the parsed header.","items":{"type":"string"}}}},"json":{"type":"object","description":"Parsing settings for JSON files. Configure when type is \"json\". resourcePath locates the\narray of records when they are nested inside a container object; malformed JSON fails\nthe entire file's processing.\n\nRequired when type is json.","properties":{"resourcePath":{"type":"string","description":"Dot-notation path to the array of records within the JSON structure (e.g.\n\"response.data.customers\"); leave empty when the file's root is already the record\narray. The path must resolve to an array of objects — array indexing and wildcards\nare not supported. A path that resolves to nothing produces zero records without\nraising an error."}}},"xlsx":{"type":"object","description":"Parsing settings for Microsoft Excel (.xlsx) workbooks. Configure when type is \"xlsx\".\nCalculated cell values are extracted rather than formulas; legacy .xls files are not\nsupported — use the modern Open XML format.\n\nRequired when type is xlsx.","properties":{"hasHeaderRow":{"type":"boolean","description":"When true (the default), the first row is read as field names rather than data;\nblank header cells are auto-named and duplicate names are made unique with suffixes.\nWhen false, every row is treated as data and fields receive generic positional names\n(Column1, Column2, ...)."}}},"xml":{"type":"object","description":"Parsing settings for XML documents. Configure when type is \"xml\". resourcePath is an\nXPath expression selecting the elements treated as records; namespaces are handled\nautomatically.\n\nRequired when type is xml.","properties":{"resourcePath":{"type":"string","description":"XPath expression selecting the elements treated as records — each matching element\nbecomes one record, with its child elements as fields. Required for XML parsing;\nthere is no default. Use an absolute path (/Root/Order) when the structure is fixed,\n//Element to match at any depth, or predicates (//Element[@type=\"product\"]) to\nfilter; XPath is case-sensitive, and a non-matching path yields zero records."}}},"includeParentData":{"$ref":"#/components/schemas/IncludeParentData"},"fileDefinition":{"type":"object","description":"Parsing via a predefined file definition resource, for formats the standard parsers\ncannot handle — fixed-width files, EDI documents (X12, EDIFACT), and multi-record-type\nor proprietary formats. Configure when type is \"filedefinition\".\n\nRequired when type is filedefinition.","properties":{"_fileDefinitionId":{"type":"string","format":"objectId","description":"File definition resource containing the parsing rules for this format. Must\nreference an existing, accessible file definition — never guess or fabricate the ID.\nDefinitions are reusable across exports, so changing one affects every export that\nuses it."},"allowPartialSuccess":{"type":"boolean","description":"When true, a file whose records partially fail parsing against the file definition\nstill emits the records that did parse, instead of failing the entire file. Applies to\nfile-definition (fixed-width, EDI) parsing only. For X12 and EDIFACT the unit is the\ntransaction set (ST–SE, UNH–UNT), and it takes effect only with an EDI profile linked:\na failed set is set aside and reported as an error while the file's other sets go on\nas records. Without this flag, one failed set fails the whole file."}}},"filter":{"description":"Selects which files are processed: files matching the filter criteria are exported,\nand non-matching files are skipped entirely before processing begins. Filterable\nfields come from each file's listing entry: the name as `fileName`, the size as\n`filesize`, and the modified time as `lastModifiedTime` on FTP, SFTP and S3 or\n`modifiedTime` on cloud-storage folders (`http.type: \"file\"`)\n(e.g. [\"endswith\", [\"string\", [\"extract\", \"fileName\"]], \".csv\"]). There is no\n`lastmodified` key.","allOf":[{"$ref":"#/components/schemas/Filter"}]},"backupPath":{"type":"string","description":"Path where backup copies of source files are stored before processing."},"includeSubdirectories":{"type":"boolean","default":false,"description":"When true, an Azure Data Lake Storage Gen2 transfer (`assistant: azuredatalakestoragegen2`,\n`http.type: file`) also picks up files in the subdirectories below the directory path in\n`http.relativeURI`; when false or omitted only that directory's own files are transferred."},"directory":{"type":"object","required":["pathMode"],"description":"Structured source location for cloud file-provider sources (Google Drive shared drives,\nBox, Dropbox, Google Cloud Storage, Azure Data Lake Storage Gen2), replacing the flat path\nused by classic FTP/S3 sources. Select the location by folder ID or by a path relative to\na configured storage root via pathMode. For the drive-style providers, directoryId mode\naddresses the folder by id alone — the flat relative-path field is not consulted at\nruntime. Google Cloud Storage puts the bucket in `storageRootId` and the object prefix in\n`id` with `pathMode: directoryId`; Azure Data Lake Storage Gen2 puts the container in\n`name` and the directory path in `http.relativeURI`.","properties":{"pathMode":{"type":"string","enum":["relativePath","directoryId"],"description":"How the directory is addressed — by provider folder ID, or by a path relative to a configured storage root."},"id":{"type":"string","description":"Provider-native folder ID of the source directory (e.g. a Google Drive folder ID); set\nwhen pathMode is directoryId. This is the file provider's own ID, not a Celigo resource ID.\nFor Google Cloud Storage it is the object prefix inside the bucket, ending with `/`."},"name":{"type":"string","description":"Display name of the folder identified by id, kept for readability in the UI; the\nfolder browser fills it when the location is picked visually. For Azure Data Lake\nStorage Gen2 it is the authoritative container name instead — the form saves the\ncontainer here and no id."},"storageRootId":{"type":"string","description":"Provider-native ID of the storage root (e.g. a Google Drive shared-drive ID) the relative\npath resolves against; set when pathMode is relativePath. The provider's own ID, not a Celigo ID.\nFor Google Cloud Storage it is the bucket name."},"storageRootName":{"type":"string","description":"Display name of the storage root identified by storageRootId, retained for reference in the UI."}}},"backupDirectory":{"type":"object","required":["pathMode"],"description":"Structured destination for backup copies of source files on cloud file providers — the\nfile-provider counterpart to backupPath. Addressed the same way as directory.","properties":{"pathMode":{"type":"string","enum":["relativePath","directoryId"],"description":"How the backup location is addressed — by provider folder ID, or by a path relative to a configured storage root."},"id":{"type":"string","description":"Provider-native folder ID of the backup directory; set when pathMode is directoryId."},"name":{"type":"string","description":"Display name of the backup folder identified by id, kept for readability in the UI;\nthe folder browser fills it when the location is picked visually."},"storageRootId":{"type":"string","description":"Provider-native ID of the storage root the backup relative path resolves against; set when pathMode is relativePath."}}}}},"IncludeParentData":{"type":"boolean","description":"When true and a path to records is configured on the export (`pathToRecords` for MongoDB and DynamoDB, `http.response.resourcePath` for HTTP, `file.json`/`file.xml` `resourcePath` for file-based exports), the data outside that path is attached to each extracted record as a `_PARENT` object, keeping the surrounding envelope available to downstream steps. When omitted or false, records are emitted without `_PARENT`, exactly as before. Constraints: on HTTP exports this option cannot be combined with `useXpathParser: true` (rejected at save with `EXPORT_INCLUDE_PARENT_DATA_XPATH_UNSUPPORTED`), and XML responses must resolve to the default converted-JSON parse mode — a run whose XML parser resolves to DOM/XPath mode (for example, because a connection-level field uses an advanced XPath expression) fails at start with the same error rather than silently omitting `_PARENT`. On file-based exports, it requires `file.type` of `json` with `file.json.resourcePath` configured — XML file envelope capture is not yet supported (the streaming XML parser emits no envelope event; XML support arrives with a parsers-package enhancement) — and any other file configuration is rejected at save with `EXPORT_INCLUDE_PARENT_DATA_UNSUPPORTED_FILE_TYPE`."},"Distributed":{"type":"object","description":"Authentication settings for distributed (real-time listener) exports.","properties":{"bearerToken":{"type":"string","description":"Bearer token that a system pushing data into this export sends as\n`Authorization: Bearer <token>`. Optional — most distributed listeners (NetSuite,\nSalesforce real-time) use no token. Write-only and stored encrypted; treat it as a\nsensitive credential.","format":"password"}}},"Cdc":{"type":"object","description":"Change-data-capture configuration for real-time listener exports (the \"Listen for real-time\ndata\" source step). Present only when `type` is `stream`. Set on `MongodbExport` (MongoDB change\nstreams) and `RDBMSExport` (SQL Server CDC and PostgreSQL logical replication); the listener\nstreams change events continuously instead of fetching records on a schedule.\n\nOn save the API normalizes the `properties` grid into the typed fields: a `snapshot.mode`\nentry populates `snapshotMode` and a `capture.mode` entry populates `captureMode`. Every\nstream export must resolve a snapshot mode from one of those two places.","properties":{"captureMode":{"type":"string","enum":["change_streams_update_full","change_streams_update_full_with_pre_image"],"default":"change_streams_update_full","description":"How MongoDB change-stream events are materialized into records. Use\n`change_streams_update_full_with_pre_image` when downstream steps need the document's prior\nstate. MongoDB listeners only — `RDBMSExport` (SQL Server CDC, PostgreSQL) does not set this."},"snapshotMode":{"type":"string","enum":["initial","no_data","when_needed","initial_only"],"default":"no_data","description":"Whether the listener seeds existing rows before streaming changes. Use `initial` to backfill\nexisting data on the first run; `no_data` to stream only new changes."},"slotName":{"type":"string","pattern":"^[a-z_][a-z0-9_]{0,62}$","description":"Name of the PostgreSQL logical replication slot the listener consumes. PostgreSQL listeners\nonly. When omitted, the platform manages the slot name. Must be a valid PostgreSQL\nidentifier (lowercase letters, numbers, or underscores, starting with a letter or\nunderscore)."},"publicationName":{"type":"string","pattern":"^[a-z_][a-z0-9_]{0,62}$","description":"Name of the PostgreSQL publication that defines which tables stream changes. PostgreSQL\nlisteners only. When set, the publication is the sole table filter and `rdbms.tables`\nstays unset. Must be a valid PostgreSQL identifier."},"cursor":{"type":"string","description":"Server-managed stream position marker (where the listener resumes reading change events).\nMaintained by the platform as events are consumed; the UI exposes a reset workflow for it\nunder Cursor Management."},"paths":{"type":"array","items":{"type":"string"},"description":"Dot-notation paths within each raw change event to retain in the emitted record. MongoDB\nlisteners default to `[\"payload\"]` (the whole change document); SQL Server and PostgreSQL\nlisteners commonly select `payload.after`, `payload.op`, and related fields to expose the\nchanged row and operation type."},"intervalTime":{"type":"integer","default":300,"description":"Polling interval, in seconds, at which the listener checks the source for new change events."},"properties":{"type":"array","description":"Connector-engine settings passed through to the underlying CDC connector as name/value\npairs. Mirrors the form's additional-properties grid: the required keys (`capture.mode`\nand `snapshot.mode` for MongoDB, `snapshot.mode` for SQL Server and PostgreSQL) are\nserialized first, followed by optional keys such as `field.exclude.list`,\n`column.include.list`, or `snapshot.collection.filter.overrides.<db>.<collection>`.\nOn save the API also lifts `snapshot.mode` / `capture.mode` entries into the typed\n`snapshotMode` / `captureMode` fields.","items":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","description":"Connector property key (e.g. `capture.mode`, `snapshot.mode`, `field.exclude.list`)."},"value":{"type":"string","description":"Connector property value."}}}}}},"Topic-2":{"type":"object","description":"Subscription settings for an Event Streams listener (`adaptorType: TopicExport`). The\nlistener consumes messages published to the topic and hands them to the flow in batches;\nthe platform delivers them internally, so the export carries no `_connectionId`. Present\nwhen `adaptorType` is `TopicExport`; the server fills `startPosition` and `maxWaitTimeMs`\nwith their defaults when they are omitted.","properties":{"_topicId":{"type":"string","format":"objectId","description":"Topic this listener subscribes to. Create or look it up with `GET /v1/topics`; the\ntopic must belong to the same account. Pointing an existing listener at a different\ntopic starts a new subscription, read from `startPosition`. A clone made in the same\nenvironment — of this export, or of a flow that contains it — keeps `_topicId`, so the\ncopy becomes another listener on the topic once its flow is enabled."},"startPosition":{"type":"string","enum":["latest","earliest"],"default":"latest","description":"Where the listener starts reading when it first subscribes to `_topicId`. Use `earliest`\nto backfill retained history, `latest` (the default) to process only new messages. Read\non create and whenever `_topicId` changes; on other updates the stored value is kept even\nif a different value is sent."},"maxWaitTimeMs":{"type":"integer","minimum":1000,"default":30000,"description":"Longest time, in milliseconds, the listener waits to fill a batch before delivering\nwhatever it has collected. Lower values reduce latency for sparse topics; the default\nof 30 seconds favors fuller batches. The batch record cap is the export's `pageSize`."}}},"FileSystem-2":{"type":"object","description":"Defines which files to read from a local or mounted folder on the host running the\non-premise agent. Required when the _connectionId field references a file-system (on-premise)\nconnection; must not be included for other connection types.","required":["directoryPath"],"properties":{"directoryPath":{"type":"string","description":"Folder on the on-premise agent's host to read files from; the agent's OS account must\nhave read permission on it. Accepts a local OS path or a UNC network share, and supports\nhandlebars templates for dynamic folders."}}},"Http-2":{"type":"object","description":"Configuration for HTTP exports. Required when the export's `_connectionId` references a\nconnection of type `http`, and on `NetSuiteHTTPExport` (NetSuite's REST APIs on a `netsuite`\nconnection, where `nsDomainType` supplies the host).","properties":{"type":{"type":"string","enum":["file","blob","csvstream"],"description":"Set to `file` when the export works with files rather than a record-returning endpoint: raw file downloads (PDFs, images, binary data) and the folder mode of the cloud file providers (Google Drive, Box, Dropbox, Azure Blob, Google Cloud Storage, Celigo Storage), where each file in the folder is parsed by the `file` object's `type` (csv, json, xml, xlsx, filedefinition — including EDI) into records; the `file` object must also be configured. Leave undefined for standard data exports that return structured records (JSON, XML, GraphQL, SOAP responses) — the response is then parsed into records for downstream steps. When set to `file` or `blob`, the export appears as a \"Transfer\" step in Flow Builder instead of a standard \"Export\" step."},"formType":{"type":"string","enum":["http","rest","graph_ql","assistant","assistant_graphql"],"description":"Authoring mode of the HTTP request form. Controls which editor the UI presents\n(standard HTTP, legacy REST, GraphQL, or connector-assistant driven) and how the\nrequest fields are interpreted. When omitted, the standard HTTP form is used."},"method":{"type":"string","description":"HTTP method used to request data from the target API. Consult the target API's\ndocumentation to determine the appropriate method. `HEAD` exists for connector endpoints\nthat return metadata in response headers only (Azure Data Lake Storage Gen2 path\nproperties).","enum":["GET","POST","PUT","PATCH","DELETE","HEAD"]},"followRedirects":{"type":"boolean","default":true,"description":"When explicitly false, 3xx responses are not followed — the redirect\nresponse itself (status code, `Location` header, body) becomes the\nrecord. Omitted or true follows allowed redirects (the default\nbehavior)."},"maxRedirects":{"type":"integer","minimum":1,"maximum":10,"description":"Caps how many consecutive 3xx redirects are followed. Only applies\nwhen `followRedirects` is not false; to not follow at all, set\n`followRedirects: false` rather than `maxRedirects: 0`. Decimal\nvalues are rejected on save."},"relativeURI":{"type":"string","description":"Resource path appended to the connection's `baseURI` to form the complete request URL. Path segments, query parameters, or the entire value can be built with handlebars expressions for endpoints determined at runtime. For lookup exports (`isLookup: true`) with mappings configured, handlebars always render against the original pre-mapped input record, so fields removed or renamed by mappings remain available for URI construction. On `NetSuiteHTTPExport` the base URL is derived from the NetSuite connection and `nsDomainType`, so the path starts at `/services/rest/...` or `/app/site/hosting/restlet.nl`.\n\nLeave it empty to call the connection's `baseURI` exactly; `/` appends a trailing slash, which is a different URL. An export saved with an empty relative URI and no `http.body` comes back from reads without its `http` block, so a PUT built from such a read loses the stored settings: send the complete `http` block with every PUT."},"headers":{"type":"array","description":"Headers specific to this export, merged with (and able to override) headers defined on the connection. Define common headers such as authentication on the connection instead. Values support handlebars expressions; for lookup exports with mappings, values render against the pre-mapped input record.","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the HTTP header to send; a header defined here overrides a same-named header from the connection."},"value":{"type":"string","description":"Value sent for the header. Supports handlebars expressions; for lookup exports with mappings, the value renders against the pre-mapped input record."}}}},"requestMediaType":{"type":"string","description":"Overrides the connection-level request media type for this export. Set only when this endpoint requires a different format than the connection default.","enum":["json","xml","urlencoded","form-data","plaintext"]},"body":{"type":"string","description":"Request body sent with POST, PUT, or PATCH requests, typically carrying query or filter criteria for APIs (such as GraphQL or SOAP) that expect them in the body. The content must match the format set by `requestMediaType` and supports handlebars expressions for dynamic values."},"successMediaType":{"type":"string","description":"Media type used to parse successful response bodies. Set only when the response format differs from the request format.","enum":["json","xml","csv","plaintext"]},"errorMediaType":{"type":"string","description":"Media type used to parse error response bodies. Set only when error responses use a different format than the request.","enum":["json","xml","plaintext"]},"_asyncHelperId":{"type":"string","format":"objectId","description":"AsyncHelper resource that handles polling for long-running operations on APIs that process requests asynchronously (HTTP 202 responses, job tickets, feed or document IDs). Set when the export must submit a request, poll for status, and retrieve results once the external process completes — for example Amazon SP-API feeds or large report generators."},"once":{"type":"object","description":"Callback configuration for once exports, used to mark records as exported in the source system after successful processing.\nCalled only while the export's `type` is `once`: an export with no `type` keeps a saved `once` and never calls it, so every\nrun reads every record again (live-verified 2026-10-22).","properties":{"relativeURI":{"type":"string","description":"Relative path (starting with `/`) called on the source system to mark each record as exported after successful processing. Supports handlebars variables and renders against the pre-mapped record — mappings do not apply to the callback URI."},"method":{"type":"string","description":"HTTP method used for the mark-as-exported callback request.","enum":["GET","PUT","POST","PATCH","DELETE"]},"body":{"type":"string","description":"Request body sent with the mark-as-exported callback. Supports handlebars expressions for dynamic values."}}},"paging":{"type":"object","description":"Controls how the export requests subsequent pages when the API returns multi-page responses. The `method` field determines which companion fields are required and how each next page is requested. For the page, skip, and token methods, reference the matching pagination variable (`{{export.http.paging.page}}`, `{{export.http.paging.skip}}`, or `{{export.http.paging.token}}`) in the relative URI or request body — misconfigured pagination is a common cause of incomplete data retrieval.\n\nWhen this export is the first lookup of a builder-mode API with `pagination.enabled` set, it is that API's volume driver: saving the export (even unchanged) invalidates the API's issued cursors, so a caller mid-walk gets an empty final page, and removing `paging` is accepted on save but makes every call to the API fail with `400 api_pagination_source_not_paged` until paging is restored or the API's flag is turned off.\n\nWhen a next page's response is identical to the previous one, paging stops: the export records an open error with code `page_response_error` (\"Identical response received from next page request. No more page requests will be made.\") and the run completes with the pages already read, so later pages are never exported. A request that doesn't change between pages produces this, as observed with `method: page` and the pagination variable missing from the relative URI and body; reference the variable so each request asks for the next page.","properties":{"method":{"type":"string","description":"Pagination strategy used to request each subsequent page; match it to the mechanism documented by the target API. Determines which companion fields are required: `token` needs `path` and `pathLocation`, `url` needs `path`, `relativeuri` needs `relativeURI`, `body` needs `body`, while `linkheader` typically needs no extra configuration. Using the wrong method results in errors or incomplete data retrieval.","enum":["linkheader","page","skip","token","url","relativeuri","body"]},"page":{"type":"integer","description":"Starting page number for `method: page`. Set to 1 for APIs whose first page is not zero-indexed; when omitted, paging starts at 0. The value is incremented automatically for each subsequent page request."},"skip":{"type":"integer","description":"Starting offset for `method: skip`. Rarely needed — most APIs start at 0, and the value is incremented by the page size automatically for each subsequent request. Set only when the API requires a non-zero starting offset."},"token":{"type":"string","description":"Initial token for `method: token`. Leave empty for normal pagination — the first request is sent without a token, and subsequent tokens are extracted from each response via `path`. Set only to resume from a known token or for APIs that require a token on the first request."},"path":{"type":"string","description":"Location of the pagination value in each response — the continuation token for `method: token`, or the complete next-page URL for `method: url`. When `pathLocation` is `body`, use a dot-notation JSON path (e.g. `meta.nextToken`); when `header`, use the exact case-sensitive header name. Not used by other pagination methods."},"pathLocation":{"type":"string","description":"Where the export looks for the pagination token referenced by `path`. Required for `method: token`; not used by other pagination methods.","enum":["body","header"]},"pathAfterFirstRequest":{"type":"string","description":"Alternative token location used for responses after the first page, in the same format as `path`. Set only when the API moves the token to a different location in subsequent responses — setting it unnecessarily can cause pagination to fail."},"relativeURI":{"type":"string","description":"Overrides the main relative URI for second and later page requests; leave empty when the main relative URI works for all pages. Build it with handlebars using the `previous_page` context — `previous_page.full_response` (the prior response body), `previous_page.last_record` (the last record of the prior page), and `previous_page.headers` (the prior response headers)."},"body":{"type":"string","description":"Overrides the main request body for second and later page requests, typically for GraphQL or SOAP APIs that paginate through the body; leave empty when the main body works for all pages. Build it with handlebars using the `previous_page` context — `previous_page.full_response`, `previous_page.last_record`, and `previous_page.headers`."},"mergeBodyParamsToPagingBody":{"type":"boolean","description":"Only applies when `paging.method` is `body`. When true, the body\nparameters produced by the export's mappings at runtime are merged\ninto the evaluated `paging.body` for page 2+ requests, so paging\nrequests keep the same mapped parameters as the first page.\nOmitted/false keeps the original behavior — page 2+ requests send\nonly the evaluated `paging.body`."},"linkHeaderRelation":{"type":"string","description":"Link header relation followed for `method: linkheader` when the API uses a value other than the default `next`. Case-sensitive and must exactly match the `rel` value in the Link header, without the `rel=` prefix."},"resourcePath":{"type":"string","description":"Overrides the path to records for second and later page responses. Set only when follow-up pages place records at a different location than the first response; leave empty when all pages share the same structure."},"lastPageStatusCode":{"type":"integer","description":"HTTP status code that signals the last page, replacing the default behavior of treating 404 as the end of pagination. When this status is received (any code but `200` paired with `lastPagePath`), paging stops and the response is not treated as an error. Set it when the API uses a non-404 code (such as 204 or 400) to indicate no more pages, or to `200` together with `lastPagePath` when the last page is an ordinary 200 response: then the response is parsed, its records are kept, and paging stops only when `lastPagePath` matches `lastPageValues`."},"lastPagePath":{"type":"string","description":"JSON path to a response-body field that signals the end of pagination, such as a \"has more\" flag or a cursor that empties on the last page. Must be used with `lastPageValues`, which lists the values at this path that stop paging. If the path does not exist in a response, the condition is not considered met. With `method: token` the path is read only on a response whose status code equals `lastPageStatusCode`, so pair it with `lastPageStatusCode: 200` for an ordinary last page; with `method: body` it is read on every response."},"lastPageValues":{"type":"array","description":"Values at `lastPagePath` that stop pagination; a match on any entry ends paging. All entries are compared as exact, case-sensitive strings, even for boolean or numeric fields — use `\"true\"`, `\"false\"`, `\"null\"` for JSON null, or `\"\"` for an empty string. Required when `lastPagePath` is set.","items":{"type":"string"}},"maxPagePath":{"type":"string","description":"JSON path to the total page count in the response, used to stop paging once the last page is reached. Only applies to the `page` and `skip` methods. Point it at the total number of pages, not the current page number."},"maxCountPath":{"type":"string","description":"JSON path to the total record count in the response, used to stop paging once all records have been retrieved. Only applies to the `page` and `skip` methods; when both are set, `maxPagePath` takes precedence. Point it at the total number of records, not the count in the current page."}},"if":{"properties":{"method":{"const":"body"}},"required":["method"]},"then":{"required":["body"]},"else":{"if":{"properties":{"method":{"const":"token"}},"required":["method"]},"then":{"required":["path","pathLocation"]},"else":{"if":{"properties":{"method":{"const":"relativeuri"}},"required":["method"]},"then":{"required":["relativeURI"]},"else":{"if":{"properties":{"method":{"const":"url"}},"required":["method"]},"then":{"required":["path"]}}}}},"response":{"type":"object","description":"Controls how records are extracted from the API response and how success or failure is detected at the response level. When the API wraps records in an envelope object, set `resourcePath` to the path of the records array — without it, the entire response body is treated as a single record. Leave this object undefined when the API returns a bare JSON array.","properties":{"resourcePath":{"type":"string","description":"Dot-separated path to the array of records inside the response body (e.g. `data.customers` for `{\"data\": {\"customers\": [...]}}`); without it, a wrapped response is treated as a single record. Values starting with `$` are evaluated as JSONPath instead of dot notation — including unions (`$['orders','invoices'][*]`) and filter expressions (`$.items[?(@.qty>3)]`); all other values keep the existing dot-notation behavior unchanged. Leave undefined when the API returns a bare JSON array. This extracts records from the API response — not to be confused with `oneToMany`/`pathToMany`, which unwrap arrays from input records, or `paging.resourcePath`, which applies only to subsequent page responses."},"includeParentData":{"$ref":"#/components/schemas/IncludeParentData"},"resourceIdPath":{"type":"string","description":"Path to the unique identifier within each record, used primarily when processing results of asynchronous import responses. When omitted, the system looks for standard `id` or `_id` fields automatically."},"successPath":{"type":"string","description":"Path to a response field that signals whether the call succeeded, for APIs that return HTTP 200 even on failure. With `successValues`, the value at this path must match one of them; without `successValues`, the path only has to resolve — a response in which it is absent fails with `response_failure` (\"Unable to extract paths from response body using path: <path>, Response: …\")."},"successValues":{"type":"array","items":{"type":"string"},"description":"Values at `successPath` that mark the response as successful; any other value is treated as an error. All comparisons are string-based — use `\"true\"` or `\"false\"` for boolean fields."},"errorPath":{"type":"string","description":"Path to the error message in the response body. The value at this path is included in error logs and error records when the API returns an error; without it, the message is the whole response body."},"failPath":{"type":"string","description":"Path to a response field that signals failure even when the HTTP status code is 200 — the inverse of `successPath`. With `failValues`, the response fails when the value there matches one of them; without, when the path holds a truthy value. A path that resolves to an array matches only when the array has exactly one element."},"failValues":{"type":"array","items":{"type":"string"},"description":"Values at `failPath` that mark the response as failed, even when the HTTP status code is 200."},"allowArrayforSuccessPath":{"type":"boolean","description":"When true, treats the value at `successPath` as an array and counts the response as successful if any element matches `successValues`, rather than requiring a single scalar match. Set it for APIs that return per-record status arrays in a batch response."},"twoDArray":{"type":"object","description":"Parsing options for endpoints that return tabular data as a two-dimensional array (rows of cells) rather than an array of objects, such as spreadsheet-style or report APIs.","properties":{"hasHeader":{"type":"boolean","description":"When true, the first row is treated as column headers and used to name the fields of each generated record."},"doNotNormalize":{"type":"boolean","description":"When true, the rows are passed through as raw arrays instead of being normalized into keyed records."}}},"blobFormat":{"type":"string","description":"Controls how the binary response body is decoded for blob exports. Only relevant when `http.type` is `file` or the export type is `blob`.","enum":["utf8","ucs2","utf-16le","ascii","binary","base64","hex"]}}},"_httpConnectorVersionId":{"type":"string","format":"objectId","description":"Pins the export to a version of the published HTTP connector its connection is bound to\n(`connection.http._httpConnectorId`), so the UI renders that connector's endpoint form instead\nof a hand-authored request. Linked on save together with `_httpConnectorResourceId` and\n`_httpConnectorEndpointId` — see that field for the rule."},"_httpConnectorResourceId":{"type":"string","format":"objectId","description":"The connector resource (a group of related endpoints, such as Orders) that this export's\nendpoint belongs to. Linked on save together with `_httpConnectorEndpointId` — see that field\nfor the rule."},"sendAuthForFileDownloads":{"type":"boolean","description":"When true, includes authentication headers when downloading files."},"_httpConnectorEndpointId":{"type":"string","format":"objectId","description":"The connector endpoint this export calls, shown in the UI as the selected endpoint. On save the\nplatform derives it (with the version and resource ids) from `relativeURI` and `method` when\nthey match an endpoint of the connector the connection is bound to — path parameters, literal\nids and query strings still match — and replaces ids that belong to another connector. An id for\nthe connection's own connector is kept as sent even when the URI names a different endpoint;\nwhen nothing matches, or the connection has no connector, whatever was sent is kept and nothing\nis derived."}}},"Salesforce-3":{"type":"object","description":"Configuration for Salesforce exports. Required when the export's `_connectionId` references a Salesforce connection; omit for other connection types. The `type` field selects the extraction mode: `soql` runs batch queries and requires the `soql` object, while `distributed` listens for real-time events and requires the `distributed` object. File retrieval (when the export's `type` is `blob`) requires `sObjectType` and `id` instead.","properties":{"type":{"type":"string","description":"Selects the extraction mode and determines which configuration object is required. Use `soql` for scheduled or on-demand batch queries and lookups — it requires the `soql` object and works with both the `rest` and `bulk` APIs and the standard, delta, test, and once export types. Use `distributed` for real-time event handling — it requires the `distributed` object, always appears as a Listener in Flow Builder, ignores the `api` field, and supports only the standard export type.","enum":["soql","distributed"]},"sObjectType":{"type":"string","description":"API name of the Salesforce object the export operates on; the object must exist in the connected org and be accessible to the integration user. Required for distributed exports and for blob exports — blob exports accept only the file storage objects (`Attachment`, `ContentVersion`, `Document`) paired with the `id` field. Optional for SOQL exports, where the object can be inferred from the query."},"id":{"type":"string","description":"Salesforce record ID of the file to retrieve; required for blob exports and not used for `soql` or `distributed` exports. Accepts a static ID or a handlebars expression such as `{{record.Attachment_Id__c}}` to resolve the file at runtime — dynamic IDs require the export to run as a lookup (`isLookup: true`). The ID must belong to the object named in `sObjectType`."},"includeDeletedRecords":{"type":"boolean","description":"When true, SOQL exports use Salesforce's `queryAll()` instead of `query()`, including Recycle Bin records deleted within the past 15 days; each record's `IsDeleted` field identifies deletions. Useful for synchronizing deletes to target systems or maintaining a complete audit trail. Ignored for distributed and blob exports.","default":false},"api":{"type":"string","description":"Salesforce API used to run SOQL queries; ignored for distributed and blob exports. Use `rest` (the default when omitted) for smaller datasets (under 10,000 records) and for lookup exports — the Bulk API is not compatible with `isLookup: true`. Use `bulk` for large data volumes and higher throughput, optionally tuned through the `bulk` object.","enum":["rest","bulk"]},"bulk":{"type":"object","description":"Settings for Salesforce Bulk API 2.0 jobs. Only applies when `api` is `bulk` on a SOQL export; ignored otherwise.","properties":{"maxRecords":{"type":"integer","description":"Caps how many records a single Bulk API job retrieves, which helps prevent timeouts with complex queries or large records. When omitted, Salesforce's default applies. Lower values suit complex or custom objects; higher values improve throughput for simple records.","minimum":10000},"purgeJobAfterExport":{"type":"boolean","default":true,"description":"When true, deletes the Bulk API job in Salesforce after all data is retrieved, keeping the Bulk Data Load Jobs list clean — at the cost of making job details unavailable for later troubleshooting. Has no effect on the data retrieved or the success of the export."}}},"soql":{"type":"object","description":"SOQL query configuration; required when `type` is `soql` and not used otherwise. The query controls which objects, fields, and filter conditions the export retrieves, with either the REST or Bulk API.\n\nRequired when type is soql.","properties":{"query":{"type":"string","description":"SOQL statement passed directly to Salesforce that defines which objects, fields, and records the export retrieves. Supports handlebars — for delta exports, filter with `WHERE LastModifiedDate > {{lastExportDateTime}}` — and relationship queries that fetch parent and child records together. Select only the fields you need and use `ORDER BY` for consistent results across pages.","maxLength":200000}}},"distributed":{"type":"object","description":"Real-time listener configuration; required when `type` is `distributed` and not used otherwise. Records reach the flow only when an Apex trigger on `sObjectType` in the connected org calls the Celigo package (`integrator_da__.RealTimeExporter.processExport()`) — no scheduling or manual execution is involved. Integration Apps create their triggers at installation; for a custom flow the customer deploys the trigger in Salesforce, starting from the code the export page shows under Required trigger.","properties":{"referencedFields":{"type":["array","null"],"description":"Fields from related objects to include in the exported record, written in dot notation (e.g. `Account.Name`, `Owner.Email`, `Custom_Lookup__r.Field__c`). Works with lookup and master-detail relationships, up to 10 unique referenced relationships per export. Referenced fields are retrieved via separate API calls, so include only fields the integration actually needs.","items":{"type":["string","null"]}},"disabled":{"type":"boolean","description":"When true, the listener stops processing events while its configuration and Salesforce triggers stay in place. Events that occur while disabled are ignored, not queued — they are not processed retroactively when re-enabled, so consider a delta export to catch up after extended pauses."},"qualifier":{"type":["string","null"],"description":"SOQL WHERE-clause condition that filters which record events the listener processes; when omitted (stored as `null`), every event for the object is processed. The Celigo Salesforce package evaluates it inside Salesforce as `SELECT Id FROM <sObjectType> WHERE (<qualifier>) AND Id IN :ids` before anything is sent, so only fields of the monitored object and SOQL comparison syntax apply — Salesforce formula functions are not part of this grammar. A deleted record is never returned by that query, so a listener with any qualifier receives no delete events; leave it unset when removals must trigger the flow."},"batchSize":{"type":["integer","null"],"description":"Controls how many event records are grouped into each real-time processing batch. Stored as `null` when not configured. Smaller batches lower latency for time-sensitive operations; larger batches improve throughput for high-volume objects. Does not limit how many records are processed in total — only how they are grouped.","minimum":4,"maximum":200},"skipExportFieldId":{"type":["string","null"],"description":"API name of a Salesforce checkbox field used to prevent infinite loops in bidirectional syncs: when the integration updates a record, this field is set so the resulting event is ignored, then cleared automatically. Required for bidirectional sync scenarios. The field must be a checkbox dedicated to integration use and updateable by the integration user."},"relatedLists":{"type":["array","null"],"description":"Child record sets to include with the parent record when it changes, one entry per related list. When omitted (stored as `null`), only the primary record is processed. Each related list adds Salesforce API calls and increases payload size.","items":{"type":"object","description":"Defines how to retrieve one type of child record related to the primary object. Configure multiple entries to retrieve different types of children.","properties":{"referencedFields":{"type":"array","description":"API names of the child object fields to retrieve; only the listed fields are included, and an empty array retrieves only the `Id` field. Include only fields the integration needs — each field adds data volume and processing time.","items":{"type":["string","null"]}},"parentField":{"type":"string","description":"API name of the lookup or master-detail field on the child object that references the parent (e.g. `AccountId`, `Parent_Object__c`) — the field's API name, not the relationship name. Used to build the query that fetches children for each parent record."},"sObjectType":{"type":"string","description":"Case-sensitive API name of the child object to retrieve (e.g. `Contact`, `Custom_Child__c`) — use the API name, not the label. The object must have a relationship field to the parent and be accessible to the connected user."},"filter":{"type":"string","description":"SOQL condition that limits which child records are included; when omitted, all related children are retrieved. Provide only the condition expression without the `WHERE` keyword — it is combined automatically with the parent relationship filter."},"orderBy":{"type":"string","description":"Sort order for the retrieved child records, as field names with optional `ASC`/`DESC` directions (e.g. `CreatedDate DESC`). Provide only the fields and directions without the `ORDER BY` keywords. When omitted, Salesforce determines the order."}},"required":["parentField","referencedFields","sObjectType"]}}}}},"if":{"properties":{"type":{"const":"distributed"}},"required":["type"]},"then":{"required":["distributed","sObjectType"]},"else":{"if":{"required":["distributed"]},"then":{},"else":{"if":{"required":["id"]},"then":{"required":["sObjectType"]},"else":{"if":{"not":{"propertyNames":{"enum":["metadata"]}}},"then":{"required":["soql","type"]}}}}},"AS2-2":{"type":"object","description":"Configures AS2 exports and listeners. Optional — the transport settings live on the AS2\nconnection, and an AS2 export is valid with only `file` (parse settings); set this object\nto link a Trading Partner Connector or to transfer raw files as blobs. An AS2 listener\nacts as the flow's source, receiving trading-partner transmissions in near real-time and\nhandling decryption, signature verification, and MDN generation; AS2 (Applicability\nStatement 2) transmits EDI and other data securely over HTTP/S using S/MIME encryption\nand digital signatures.","properties":{"_tpConnectorId":{"type":"string","format":"objectId","description":"Trading Partner Connector that supplies the partner-specific EDI configuration —\ncommunication protocol, document schemas, mappings, validation rules, and endpoint\ndetails. Set this to link the export to all settings required for AS2 communication\nwith that partner."},"blob":{"type":"boolean","description":"When true, retrieves raw files without parsing them into structured records (rendered as\na \"Transfer\" step in the flow UI). Use only when the file contents are not needed in\nsubsequent steps — for binary files or when parsing is handled downstream. Only\navailable on AS2 and VAN exports."}},"required":[]},"DynamoDB-2":{"type":"object","description":"Defines how records are queried from DynamoDB tables. Required when the _connectionId field\nreferences a DynamoDB connection; must not be included for other connection types. Basic\nexports need region, method, tableName, keyConditionExpression, expressionAttributeNames,\nand expressionAttributeValues; once exports (export type \"once\") additionally need\nonceExportPartitionKey, plus onceExportSortKey for composite-key tables.","required":["region","tableName","keyConditionExpression","expressionAttributeNames","expressionAttributeValues"],"properties":{"region":{"type":"string","enum":["us-east-1","us-east-2","us-west-1","us-west-2","af-south-1","ap-east-1","ap-south-1","ap-northeast-1","ap-northeast-2","ap-northeast-3","ap-southeast-1","ap-southeast-2","ca-central-1","eu-central-1","eu-west-1","eu-west-2","eu-west-3","eu-south-1","eu-north-1","me-south-1","sa-east-1"],"description":"AWS region hosting the DynamoDB table. Must match the region where the table is\ndeployed so the integration can reach it.","default":"us-east-1"},"method":{"type":"string","enum":["query"],"description":"DynamoDB operation used to retrieve items. Only \"query\" is currently supported."},"tableName":{"type":"string","description":"Name of the DynamoDB table to query. Must exactly match an existing table (case-sensitive)."},"keyConditionExpression":{"type":"string","description":"Key condition determining which items the query retrieves. Must include a condition on\nthe partition key and may add sort-key conditions (equality, BETWEEN, begins_with).\nReference attribute names with \"#\" placeholders defined in expressionAttributeNames and\nvalues with \":\" placeholders defined in expressionAttributeValues."},"filterExpression":{"type":"string","description":"Filters query results on non-key attributes after the key condition is applied; omit to\nreturn all items matching the key condition. Uses the same \"#\" and \":\" placeholders\ndefined in expressionAttributeNames and expressionAttributeValues."},"projectionExpression":{"type":"array","items":{"type":"string"},"description":"Attributes to return from each item, reducing data transfer; omit to return all\nattributes. Each array element is one field, referenced via \"#\" placeholders defined in\nexpressionAttributeNames."},"expressionAttributeNames":{"type":"string","description":"JSON string mapping \"#\" placeholders to actual attribute names, e.g.\n{\"#pk\": \"customerId\"}. Placeholders defined here are used in keyConditionExpression,\nfilterExpression, and projectionExpression."},"expressionAttributeValues":{"type":"string","description":"JSON string mapping \":\" placeholders to comparison values, e.g. {\":status\": \"ACTIVE\"}.\nValues can be static or dynamic handlebars expressions such as {{lastExportDateTime}},\nand are referenced from keyConditionExpression and filterExpression."},"onceExportPartitionKey":{"type":"string","description":"Partition key attribute that uniquely identifies items when the export's type is \"once\".\nCeligo uses it to mark items as processed after a successful export, preventing the same\nitems from being exported again on subsequent runs."},"onceExportSortKey":{"type":"string","description":"Sort key attribute used together with onceExportPartitionKey to identify processed items\nwhen the table has a composite primary key. Omit for tables keyed by a partition key\nalone."},"pathToRecords":{"$ref":"#/components/schemas/PathToRecords"},"includeParentData":{"$ref":"#/components/schemas/IncludeParentData"}}},"PathToRecords":{"type":"string","maxLength":250,"description":"Path to the array of records inside each document or item fetched from the source; each array element is emitted as an individual record, which keeps records within the 5 MB page-size limit even when the source document exceeds it. Accepts dot notation (e.g. `data.orders`) or JSONPath for values starting with `$`, including unions (`$['orders','invoices'][*]`) and filter expressions (`$.items[?(@.qty>3)]`); a path that resolves to nothing emits zero records for that document or item. When omitted, each document or item is emitted as one record, exactly as before — HTTP and file-based exports configure this same capability through their existing `http.response.resourcePath` and `file.json`/`file.xml` `resourcePath` fields."},"FTP-2":{"type":"object","description":"Defines which files to retrieve from an FTP, FTPS, or SFTP server. Required when the\n_connectionId field references an FTP/SFTP connection; must not be included for other\nconnection types. directoryPath selects the folder, fileNameStartsWith/fileNameEndsWith\nfilter files by name, and backupDirectoryPath controls where files are moved after\nretrieval.","required":["directoryPath"],"properties":{"_tpConnectorId":{"type":"string","format":"objectId","description":"Trading Partner Connector that supplies partner-specific B2B settings for this export.\nWhen set, the export inherits the connector's pre-configured settings; omit to use only\nthe FTP connection details."},"directoryPath":{"type":"string","description":"Directory on the server to retrieve files from, either absolute or relative to the login\ndirectory; the FTP user must have read permission on it. Use forward slashes regardless\nof server OS — paths are case-sensitive on UNIX/Linux servers. Supports handlebars\ntemplates, e.g. archive/{{date 'YYYY-MM-DD'}}."},"fileNameStartsWith":{"type":"string","description":"Only retrieves files whose names start with this value (case-sensitive on most servers);\naccepts static text or handlebars templates. When combined with fileNameEndsWith, files\nmust match both."},"fileNameEndsWith":{"type":"string","description":"Only retrieves files whose names end with this value, commonly a file extension\n(case-sensitive on most servers). When combined with fileNameStartsWith, files must\nmatch both."},"backupDirectoryPath":{"type":"string","description":"Directory on the same server where files are moved after successful export, giving you\nan independent backup; if omitted, files are simply deleted from the source directory\nafter successful export unless `file.skipDelete` is true (Celigo also keeps its own copy\nof processed files for a set period). Supports static paths or handlebars templates."}}},"JDBC-2":{"type":"object","description":"Configuration object for JDBC (Java Database Connectivity) data integration exports.\n\nThis object is REQUIRED when the _connectionId field references a JDBC database connection\nand must not be included for other connection types. It defines how data is extracted\nfrom relational databases using SQL queries.\n\n**Jdbc export capabilities**\n- Execute custom SQL SELECT statements\n- Support for joins, aggregations, and functions\n- Flexible data retrieval from any accessible tables or views\n- Compatible with all major database systems\n**Critical:** WHAT BELONGS IN THIS OBJECT\n- `query` - **ALWAYS REQUIRED** - The SQL SELECT statement\n- `once` - **REQUIRED** when the export's Object Type is `\"once\"` (set _include_once: true)\n- **DO NOT** put `delta` inside this object - delta is handled via the query\n\n**Delta exports (type: \"delta\")**\nFor delta/incremental exports, do NOT populate a `delta` object inside `jdbc`.\nInstead, use `{{lastExportDateTime}}` or `{{currentExportDateTime}}` directly in the query:\n```json\n{\n  \"type\": \"delta\",\n  \"jdbc\": {\n    \"query\": \"SELECT * FROM customers WHERE updatedAt > {{lastExportDateTime}}\"\n  }\n}\n```\n\n**Once exports (type: \"once\")**\nFor once exports (mark records as processed), populate `jdbc.once.query`:\n```json\n{\n  \"type\": \"once\",\n  \"jdbc\": {\n    \"query\": \"SELECT * FROM customers WHERE exported = false\",\n    \"once\": {\n      \"query\": \"UPDATE customers SET exported = true WHERE id = {{record.id}}\"\n    }\n  }\n}\n```\n\n**Standard exports (type: null or not specified)**\nJust provide the query:\n```json\n{\n  \"jdbc\": {\n    \"query\": \"SELECT * FROM customers WHERE status = 'ACTIVE'\"\n  }\n}\n```","required":["query"],"properties":{"formType":{"type":["string","null"],"enum":["sql","simple",null],"description":"Selects how the export's query is authored. Use `sql` for a hand-written SQL statement in\n`query`; use `simple` to build the query from the `simple` object's table/columns/filter."},"query":{"type":"string","description":"SQL SELECT statement executed to retrieve data, from simple table selections to joins and\naggregations. For delta exports, include {{lastExportDateTime}}/{{currentExportDateTime}}\nin the WHERE clause rather than configuring a separate delta object."},"once":{"type":"object","description":"**CRITICAL: REQUIRED when the export's Object Type is \"once\".**\n\nIf Object Type is \"once\", you MUST set _include_once to true (or include this object).\n\nThis object has ONLY ONE property: \"query\" (a SQL UPDATE string).\nDO NOT create any other properties like \"update\", \"table\", \"set\", \"where\", etc.\n\nCORRECT format:\n```json\n{\"query\": \"UPDATE customers SET exported=true WHERE id={{record.id}}\"}\n```\n\nWRONG format (DO NOT DO THIS):\n```json\n{\"update\": {\"table\": \"customers\", \"set\": {...}}}\n```\n","properties":{"query":{"type":"string","description":"**REQUIRED** - A SQL UPDATE statement string to mark records as processed.\n\nThis is a plain SQL UPDATE query string, NOT a structured object.\n\nThe query runs AFTER each record is successfully exported, setting a flag\nto indicate the record has been processed.\n\nFormat: \"UPDATE <table> SET <column>=<value> WHERE <id_column>={{record.<id_field>}}\"\n\nExample: \"UPDATE customers SET exported=true WHERE id={{record.id}}\"\n\nThe {{record.id}} placeholder is replaced with the actual record ID from each exported row.\n"}}},"simple":{"type":"object","description":"Visual query-builder configuration used when `formType` is `simple`; the platform builds\nthe SELECT from these instead of raw SQL. Only relevant when `formType` is `simple`.","properties":{"table":{"type":"string","description":"Table or view to select rows from."},"columns":{"type":"array","items":{"type":"string"},"description":"Columns to return; an empty list selects all columns."},"filter":{"description":"Filter applied as the WHERE clause for the generated query, stored as the\nplatform's structured filter-rules object.","allOf":[{"$ref":"#/components/schemas/Filter"}]}}}}},"MongoDB-2":{"type":"object","description":"Defines how documents are retrieved from MongoDB collections. Required, with `method` and\n`collection`, when the _connectionId field references a MongoDB connection and the export\nfetches documents; must not be included for other connection types. A change-stream\nlistener (`type: stream`) is configured through `cdc` instead and may omit this object, or\nset only `collection` to scope the listener. Supports find queries with optional filter\ncriteria and field projections.","properties":{"method":{"type":"string","enum":["find"],"description":"MongoDB operation used to retrieve documents. Only \"find\" is currently supported,\nequivalent to db.collection.find(filter, projection)."},"collection":{"type":"string","description":"Name of the MongoDB collection to query. Case-sensitive and must reference an existing\ncollection in the connected database. Required on find exports; on a change-stream\nlistener (`type: stream`) it is optional and may list several collections separated by\ncommas, matching the listener form's \"Collections\" field."},"filter":{"type":"string","description":"MongoDB query document, as a JSON string, that selects which documents to export; omit\nto return every document in the collection. Supports standard MongoDB query operators\nand handlebars variables for dynamic values — for example\n{\"lastModified\": {\"$gt\": \"{{lastRun}}\"}} for incremental processing."},"projection":{"type":"string","description":"MongoDB projection document, as a JSON string, that limits which fields are returned;\nomit to return all fields. Use 1 to include fields or 0 to exclude them — the two modes\ncannot be mixed except for _id, which is included by default unless explicitly excluded.\nProjection affects only the shape of returned documents, not which documents match."},"pipeline":{"type":"string","description":"MongoDB aggregation pipeline, as a JSON-array string of stages, used instead of a plain\nfind when documents need to be transformed, joined, or grouped server-side (e.g. `$match`,\n`$lookup`, `$group`). When set, it takes the place of `filter`/`projection`. Supports\nhandlebars variables for incremental processing."},"readPreference":{"type":"string","enum":["doNotOverride","primary","primaryPreferred","secondary","secondaryPreferred","nearest"],"default":"doNotOverride","description":"Overrides which replica-set member this export reads from, trading consistency for load\ndistribution. Leave at `doNotOverride` to inherit the connection's setting."},"pathToRecords":{"$ref":"#/components/schemas/PathToRecords"},"includeParentData":{"$ref":"#/components/schemas/IncludeParentData"}}},"NetSuite-3":{"type":"object","description":"NetSuite-specific export configuration. Required when `_connectionId` references a NetSuite\nconnection; omit for all other connection types. Supports saved-search, RESTlet, and\ndistributed (SuiteApp) exports, plus two file cabinet modes: blob exports transfer files\nas-is (set the export's top-level `type` to `blob` and configure `netsuite.blob`), while\nfile exports parse file contents into records (leave the export's top-level `type` unset\nand configure `netsuite.file`).","properties":{"type":{"type":"string","enum":["search","basicSearch","metadata","selectoption","restlet","getList","getServerTime","distributed","file"],"description":"Controls how data is retrieved from NetSuite and which sibling object must be configured:\n`search` pairs with `netsuite.searches`, `restlet` with `netsuite.restlet`, `distributed`\nwith `netsuite.distributed`, and `file` with `netsuite.file.folderInternalId`. For blob\nexports (raw file transfer without parsing), leave this field unset, set the export's\ntop-level `type` to `blob`, and configure `netsuite.internalId` instead. For lookups\n(`isLookup: true`), prefer `restlet`, which supports `suiteapp2.0` saved searches with\ndynamic inputs; `search` is limited for dynamic lookups."},"searches":{"type":"array","description":"Saved search configurations that query NetSuite for records. Each entry references a\nsaved search by internal ID, names the record type being searched, and can add filter\ncriteria.","items":{"type":"object","properties":{"savedSearchId":{"type":"string","description":"Internal ID of the NetSuite saved search to run."},"recordType":{"type":"string","description":"NetSuite record type being searched, as its exact lowercase script ID\n(e.g. \"customer\", \"salesorder\") — not the display name."},"criteria":{"type":"array","description":"Additional filter criteria applied on top of the saved search.","items":{"type":"object","properties":{"field":{"type":"string","description":"Script-id of the search column to filter on.  REQUIRED.\nMust come from the record type's SEARCH COLUMNS list\n(NOT the body-fields list).  Becomes the `name`\nargument of NetSuite's `search.createFilter()`."},"operator":{"type":"string","description":"Comparison operator.  REQUIRED.  Supported values\ndepend on the column's data type (e.g. `is`,\n`isnot`, `after`, `before`, `anyof`,\n`noneof`, `contains`, `startswith`,\n`greaterthan`, `lessthan`).  Must be a string\nNetSuite recognises -- mismatched operators cause\nruntime rejection."},"join":{"type":"string","description":"Optional join name when filtering through a related\nrecord (e.g. `customer` from a sales order).  Use\nonly when the column lives on a related record and\nNetSuite supports the join from the base record type."},"searchValue":{"description":"Value (or array of values for `anyof`-style\noperators) to compare against the column.  Required\nfor most operators; omit for the few unary operators\n(e.g. `isempty`, `isnotempty`)."}},"required":["field","operator"]}}}}},"metadata":{"type":"object","description":"Supplemental metadata associated with the NetSuite export."},"selectoption":{"type":"object","description":"Configuration for `selectoption` exports, which retrieve the available select options\nfor a NetSuite field."},"customFieldMetadata":{"type":"object","description":"Metadata describing the custom fields defined in the NetSuite account."},"skipGrouping":{"type":"boolean","description":"When true, each saved-search result row becomes its own record. When false or omitted,\nconsecutive result rows that share the record's internal id are grouped into one record\nshaped as an array of rows — the *Group rows* option in the UI."},"statsOnly":{"type":"boolean","description":"When true, returns only summary statistics about matching records instead of the\ndetailed records themselves."},"internalId":{"type":"string","description":"Internal ID of the file in the NetSuite file cabinet to export. Required for blob\nexports (the export's top-level `type` is `blob`); the file is transferred as-is\nwithout parsing. For parsed file exports, use `netsuite.file.folderInternalId` instead."},"blob":{"type":"object","properties":{"purgeFileAfterExport":{"type":"boolean","description":"When true, permanently deletes the file from the NetSuite file cabinet after a\nsuccessful export. When false or omitted, the file is left in place."}},"description":"Configuration for blob exports, which transfer files from the NetSuite file cabinet\nas-is without parsing them into records. To export a blob, set the export's top-level\n`type` to `blob`, set `netsuite.internalId` to the file's internal ID, and leave\n`netsuite.type` unset. Use `netsuite.file` instead when file contents should be parsed\ninto records."},"restlet":{"type":"object","properties":{"recordType":{"type":"string","description":"NetSuite record type the RESTlet operates on, as its script ID. Custom record\ntypes use the \"customrecord_\" prefix."},"batchSize":{"type":"number","description":"Number of records the RESTlet returns per request (default 1000). This, not the\nexport's `pageSize`, sizes each NetSuite call; the export keeps requesting until the\nsearch is exhausted. Larger batches reduce round-trips but raise NetSuite governance\nand memory use per call; tune to the record's size. The RESTlet caps a call at 1000,\nso a larger value acts as 1000. In a test run or preview the request is capped at the\nexport's `pageSize` (default 20) instead."},"searchId":{"type":["string","null"],"description":"Internal ID of the NetSuite saved search the RESTlet executes. Results follow the\nsaved search's current definition, so editing the search in NetSuite changes the\nexport output without changing this ID."},"useSS2Restlets":{"type":"boolean","description":"When true, calls SuiteScript 2.0 RESTlets. When false or omitted, legacy\nSuiteScript 1.0 RESTlets are used.\nSet at step creation together with restletVersion — the same\ncreation-time-only constraint applies (see restletVersion)."},"restletVersion":{"type":"string","enum":["suitebundle","suiteapp1.0","suiteapp2.0"],"description":"RESTlet version the export invokes. Defaults to `suiteapp2.0` when\n`useSS2Restlets` is true, `suitebundle` otherwise.\nThe version is fixed when the step is created — the Advanced selector is\ndisabled on existing steps, and migrating an existing export to a\ndifferent version means recreating or cloning it."},"criteria":{"type":"array","description":"Filter conditions added on top of the saved search (the UI's \"Additional search\ncriteria\"). Each condition pairs a search column with an operator and one or two\ncomparison values, and the conditions are combined with AND — every one must match.","items":{"type":"object","properties":{"field":{"type":"string","description":"Script-id of the search column to filter on. This is the primary\nfield the criterion is evaluated against (e.g. \"trandate\", \"status\")."},"join":{"type":"string","description":"Join relationship that applies this filter to a related record's fields instead\nof the base record. Must match a join name NetSuite defines for the record type."},"operator":{"type":"string","enum":["after","allof","any","anyof","before","between","contains","doesnotcontain","doesnotstartwith","equalto","greaterthan","greaterthanorequalto","haskeywords","is","isempty","isnot","isnotempty","lessthan","lessthanorequalto","noneof","notafter","notallof","notbefore","notbetween","notequalto","notgreaterthan","notlessthan","notlessthanorequalto","noton","notonorafter","notonorbefore","notwithin","on","onorafter","onorbefore","startswith","within"],"description":"Comparison operator applied between the column and the search value, using\nNetSuite's lowercase search operator names. Which operators a column accepts\ndepends on its data type."},"searchValue":{"description":"Value the column is compared against — a string, a number, or an array of\nvalues for `anyof`-style operators. On lookup steps it may be a Handlebars\nexpression rendered from the incoming record (for example `{{poNumber}}`)."},"searchValue2":{"description":"Second comparison value for the two-value operators `between` and `notbetween`\n(the upper bound). Omit for every other operator."},"formula":{"type":"string","description":"NetSuite formula expression used as the filter condition, for criteria that\nstandard field-operator-value comparisons can't express."}}}},"columns":{"type":"array","description":"Search columns the RESTlet returns for each matching record. With a saved search,\nthe RESTlet returns the search's own result columns followed by any listed here: a\nlist adds columns and never removes one of the search's, so remove a column in the\nsaved search itself. An ad-hoc search (no `searchId`) must list its columns here.","items":{"type":"object","properties":{"_id":{"type":"string","readOnly":true,"description":"Subdocument id the server assigns to the column entry on save. Not a NetSuite\nfield id — never set it when authoring."},"name":{"type":"string","description":"Column field name as referenced in the search."},"join":{"type":"string","description":"Join relationship that sources this column from a related record instead of\nthe base record. Supports dot notation for nested joins (e.g. \"employee.manager\")."},"summary":{"type":"string","enum":["group","sum","count","avg","min","max"],"description":"Summary type applied to this column, turning the search into a summary search."},"formula":{"type":"string","description":"NetSuite formula expression that computes this column's value at runtime, such\nas a CASE statement or date formatting, instead of reading a stored field."},"label":{"type":"string","description":"Display name for the column; it becomes the property name of the column's value\nin each exported record."},"sort":{"type":"boolean","description":"When true, query results are sorted by this column's values."}}}},"markExportedBatchSize":{"type":"integer","description":"Number of records updated per RESTlet call when marking records as exported in\nNetSuite (live: an integer count, e.g. 100).","maximum":100,"minimum":1},"hooks":{"type":["object","null"],"properties":{"batchSize":{"type":"number","description":"Legacy location of the RESTlet page size; `restlet.batchSize` is the current\nfield and takes precedence when both are set."},"preSend":{"type":"object","properties":{"fileInternalId":{"type":"string","description":"Internal ID of the JavaScript file in the NetSuite File Cabinet that defines\nthe hook function. Must be readable by the role behind the export's connection."},"function":{"type":"string","description":"Name of the global function in that file that NetSuite invokes as the pre\nsend hook."},"configuration":{"type":"object","description":"Free-form JSON stored with the hook reference for the script's own use. Custom\nsettings in scope for the export are passed to the function separately as\n`options.settings`."}},"description":"SuiteScript hook that runs inside NetSuite after a page of records is collected\nand before it is sent to the Celigo platform."}},"description":"SuiteScript hooks for the export. Only `preSend` exists on exports; imports carry\nthe pre map, post map, and post submit hooks."},"cLocked":{"type":"object","description":"Lock state that prevents modification of the configuration when set."}},"description":"Configuration for `restlet` exports. Identifies the RESTlet script that retrieves data\nfrom NetSuite, along with the saved search, criteria, and columns it executes with."},"distributed":{"type":"object","properties":{"recordType":{"type":"string","description":"NetSuite record type the listener is deployed on, as its lowercase script ID\n(`customer`, `salesorder`, `customrecord_<scriptid>`) — never the display name or a\ncustom record's numeric internal ID. Types the platform's record-type catalog marks\n`doesNotSupportRTExport` cannot host a listener and are not offered in the UI."},"executionContext":{"type":"array","description":"NetSuite execution contexts that trigger this distributed export. A record change\nfires the export only when it occurs in one of the listed contexts.","default":["userinterface","webstore"],"items":{"type":"string","enum":["userinterface","webservices","csvimport","offlineclient","portlet","scheduled","suitelet","custommassupdate","workflow","webstore","userevent","mapreduce","restlet","webapplication","restwebservices"]}},"disabled":{"type":"boolean","description":"When true, the listener's NetSuite deployment is inactive and record saves no longer\ntrigger the export. Maintained by the platform as the flow is disabled and re-enabled;\nre-enabling the flow re-registers the deployment. A listener is created disabled, and\na flow created already enabled leaves it that way until a flow update sets\n`disabled: false`, which activates it within seconds."},"executionType":{"type":"array","description":"Record operations that trigger this distributed export. A record event fires the\nexport only when its operation matches one of the listed types.","default":["create","edit","xedit"],"items":{"type":"string","enum":["create","edit","delete","xedit","copy","view","cancel","approve","reject","pack","ship","markcomplete","reassign","editforecast","dropship","specialorder","orderitems","paybills","print","email"]}},"qualifier":{"type":["array","string","null"],"description":"Field-specific qualification criteria the SuiteApp evaluates against the record before\nexporting it; saves that do not match are discarded. Stored as a filter-expression array\nof `[field, operator, value]` rules joined by lowercase `\"and\"` / `\"or\"`, as a legacy\nexpression string, or null when no criteria are set."},"skipExportFieldId":{"type":"string","description":"Script ID of a checkbox body field on the record type. A save with that box checked is\nskipped by the listener, which then clears the box itself — used to keep a mass update\nfrom fanning out and to stop a write-back import from re-triggering the listener."},"hooks":{"type":"object","description":"SuiteScript hook that runs inside NetSuite before the record leaves it.","properties":{"preSend":{"type":"object","description":"Pre-send hook: a SuiteScript function in the NetSuite file cabinet invoked before\nrecords leave NetSuite, with the full SuiteScript API available — for example to run\ndynamic searches against the record being exported.","properties":{"fileInternalId":{"type":"string","description":"Internal ID of the file cabinet file that holds the hook's SuiteScript."},"function":{"type":"string","description":"Name of the function in that file to invoke for each export."},"configuration":{"type":"object","description":"Settings passed to the hook function."}}}}},"sublists":{"type":["array","null"],"description":"IDs of the NetSuite sublists to include with each exported record (for example `item`\nfor transaction line items). Records are exported with body fields only when no\nsublists are selected. Legacy documents may store null (equivalent to no sublists).","items":{"type":"string"}},"referencedFields":{"type":"object","description":"Field references recorded on the listener by the platform. Not exposed in the listener\nform; leave as stored."},"relatedLists":{"type":"object","description":"Related record lists recorded on the listener by the platform. Not exposed in the\nlistener form; leave as stored."},"forceReload":{"type":"boolean","description":"When true, the listener reloads the record from the database after the save before\nexporting it, so fields that are populated only once the save completes — such as\ntransaction line IDs on new lines — are included. The reload slows every save of that\nrecord type."},"ioEnvironment":{"type":"string","description":"integrator.io environment the listener's NetSuite deployment reports to. Written by the\nplatform when the listener registers with NetSuite; live-observed values include\n`production` and `staging`."},"ioDomain":{"type":"string","description":"integrator.io domain the listener's NetSuite deployment calls back to — the account's\nown regional domain, stamped by the platform on every save and echoed on create and\nupdate responses; not returned by GET."},"lastSyncedDate":{"type":"string","format":"date-time","description":"When the listener's deployment last synchronized with NetSuite. Written by the platform;\nnot set until the first successful synchronization."},"settings":{"type":"object","description":"Additional key-value settings for the distributed export. Not exposed in the listener\nform."},"useSS2Framework":{"type":"boolean","description":"Derived by the platform from `frameworkVersion` — true when it is `suiteapp2.0`, absent\notherwise — and echoed on create and update responses; not returned by GET. A value\nsent on create or update is replaced by the derived one."},"frameworkVersion":{"type":"string","enum":["suitebundle","suiteapp1.0","suiteapp2.0"],"description":"SuiteApp framework version used by the distributed export.\nFixed when the step is created — the Advanced selector is disabled on\nexisting steps, and migrating an existing listener to a different\nversion means recreating or cloning it."}},"description":"Configuration for `distributed` exports, which use the NetSuite SuiteApp to fire\nreal-time, event-driven exports when records change. Define the record type to listen\nto and the execution contexts and operations that trigger it."},"getList":{"type":"array","description":"Configuration for `getList` exports — a list of record references, each retrieving one\nNetSuite record by its identifier. The export returns the fetched records in list order.","items":{"type":"object","properties":{"type":{"type":"string","description":"Standard NetSuite record type to retrieve (e.g. \"customer\", \"salesOrder\").\nUsed when `typeId` is absent. Case-sensitive."},"typeId":{"type":"string","description":"Script ID of a custom record type (\"customrecord_...\") or custom transaction\ntype (\"customtransaction_...\"). Takes precedence over `type` when present.\nCase-sensitive."},"internalId":{"type":"string","description":"Internal ID of the NetSuite record to retrieve. Assigned by NetSuite at record\ncreation and unique within the record type. Takes precedence over `externalId`."},"externalId":{"type":"string","description":"Identifier assigned by an external system, used to reference and synchronize the\nrecord across systems. Distinct from the NetSuite-assigned internal ID."}}}},"searchPreferences":{"type":"object","properties":{"bodyFieldsOnly":{"type":"boolean","description":"When true, search results include only the record's body fields and exclude fields\nfrom joined or related records. Reduces payload size when related data isn't needed."},"pageSize":{"type":"number","description":"Number of search results NetSuite returns per page."},"returnSearchColumns":{"type":"boolean","description":"When true, search results include the column data defined in the search. When\nfalse, column data is omitted and results contain only minimal record information."}},"description":"Preferences that control how NetSuite executes searches and shapes the results it returns."},"file":{"type":"object","description":"Configuration for file exports, which retrieve files from the NetSuite file cabinet\nand parse their contents (CSV, XML, JSON) into records. Leave the export's top-level\n`type` unset when using this object; for raw transfers without parsing, use\n`netsuite.blob` with the export's `type` set to `blob` instead.","properties":{"folderInternalId":{"type":"string","description":"Internal ID of the NetSuite file cabinet folder to export files from. Accepts a\nhandlebars expression (e.g. `{{record.folderId}}`) when the folder must be\nselected dynamically from record data. The ID is stable across folder renames and\nmoves, but may differ between non-production and production accounts."},"backupFolderInternalId":{"type":"string","description":"Internal ID of the NetSuite file cabinet folder where backup files are stored.\nIDs are account-specific and do not transfer between non-production and production\nenvironments."},"fileNameStartsWith":{"type":"string","description":"Optional prefix filter. Only files whose names start with this string are\nexported from the configured folder."},"fileNameEndsWith":{"type":"string","description":"Optional suffix filter. Only files whose names end with this string (e.g. a\n\".csv\" extension) are exported from the configured folder."}}}},"required":[]},"RDBMS-2":{"type":"object","description":"Configuration object for Relational Database Management System (RDBMS) data integration exports.\n\nThis object defines how data is read from a relational database and must not be included\nfor other connection types. For query-type exports (standard, delta, once) it is REQUIRED\nwhen the _connectionId field references an RDBMS database connection and holds the SQL\nquery. For real-time stream listeners (`type: \"stream\"`) it holds the CDC watch list\n(`tables`) instead — and a PostgreSQL listener scoped by a publication\n(`cdc.publicationName`) may omit this object entirely.\n\n**Rdbms export capabilities**\n- Execute custom SQL SELECT statements\n- Support for joins, aggregations, and functions\n- Flexible data retrieval from any accessible tables or views\n- Compatible with all major database systems\n\n**Critical:** WHAT BELONGS IN THIS OBJECT\n- `query` - The SQL SELECT statement - required for every query-type export (standard, delta, once)\n- `once` - **REQUIRED** when the export's Object Type is `\"once\"` (set _include_once: true)\n- `tables` - the CDC listener watch list - real-time stream listeners (`type: \"stream\"`) only\n- **DO NOT** put `delta` inside this object - delta is handled via the query\n- **DO NOT** author `query` on a stream listener - real-time CDC exports have no SQL query\n\n**Delta exports (type: \"delta\")**\nFor delta/incremental exports, do NOT populate a `delta` object inside `rdbms`.\nInstead, use `{{lastExportDateTime}}` or `{{currentExportDateTime}}` directly in the query, and\nset the export's top-level `delta.startDate`: without it the first run's window is empty.\n```json\n{\n  \"type\": \"delta\",\n  \"delta\": { \"startDate\": \"2026-01-01T00:00:00.000Z\" },\n  \"rdbms\": {\n    \"query\": \"SELECT * FROM customers WHERE updatedAt > {{lastExportDateTime}}\"\n  }\n}\n```\n\n**Once exports (type: \"once\")**\nFor once exports (mark records as processed), populate `rdbms.once.query`:\n```json\n{\n  \"type\": \"once\",\n  \"rdbms\": {\n    \"query\": \"SELECT * FROM customers WHERE exported = false\",\n    \"once\": {\n      \"query\": \"UPDATE customers SET exported = true WHERE id = {{record.id}}\"\n    }\n  }\n}\n```\n\n**Standard exports (type: null or not specified)**\nJust provide the query:\n```json\n{\n  \"rdbms\": {\n    \"query\": \"SELECT * FROM customers WHERE status = 'ACTIVE'\"\n  }\n}\n```\n\n**Real-time CDC listener exports (type: \"stream\")**\nSQL Server and PostgreSQL connections support change-data-capture listeners that stream row\nchanges continuously (the `cdc` object holds the listener settings). A stream export has NO\n`query` — it watches `tables`:\n```json\n{\n  \"type\": \"stream\",\n  \"rdbms\": { \"tables\": \"public.orders,public.customers\" },\n  \"cdc\": { \"properties\": [{ \"name\": \"snapshot.mode\", \"value\": \"no_data\" }] }\n}\n```\nA PostgreSQL listener can instead scope tables through a publication\n(`cdc.publicationName`), in which case `tables` stays unset and the `rdbms` object may be\nomitted entirely.","properties":{"tables":{"type":"string","description":"Comma-separated list of tables the CDC listener watches for changes. Used only by real-time\nlistener exports (`type` is `stream`); standard SQL exports use `query` instead. SQL Server\nlisteners use fully-qualified three-part names (`database.schema.table`); PostgreSQL\nlisteners use two-part names (`schema.table`)."},"query":{"type":"string","description":"SQL SELECT statement executed to retrieve data, from simple table selections to joins,\naggregations, and parameterized queries with Handlebars expressions. Query-type exports\nonly — real-time stream listeners (`type` is `stream`) have no SQL query. For delta exports,\nreference {{lastExportDateTime}}/{{currentExportDateTime}} directly in the query rather\nthan configuring a separate delta object.\n\nDouble braces render a string as a quoted SQL literal with its apostrophes escaped, and\ntriple braces insert a value raw. Observed in Snowflake queries:\n\n- String helper output (`dateFormat`, `jsonSerialize`) and both delta variables are\n  quoted the same way; a number or boolean renders bare, whether it is a JSON value or a\n  numeric helper's result (`{{add 2 3}}` renders `5`).\n- In a run, {{lastExportDateTime}} and {{currentExportDateTime}} arrive as quoted ISO 8601\n  UTC literals with milliseconds (`'2026-01-15T00:00:00.000Z'`), or in `delta.dateFormat`\n  when it is set: add no quotes of your own, and don't parse them with a format mask\n  written for another format.\n- A preview of an unsaved export document renders both as empty, unquoted text, so a\n  delta query fails to compile there unless the document carries both in `postData`.\n- The quoting leaves backslashes, which Snowflake reads as escape sequences, so a\n  double-brace value holding a backslash arrives changed. Pass such a value in a\n  dollar-quoted constant with triple braces (`$${{{record.field}}}$$`), which Snowflake\n  reads as is, but only when it can't contain `$$`: a `$$` ends the constant, and the rest\n  is read as SQL."},"once":{"type":"object","description":"**CRITICAL: REQUIRED when the export's Object Type is \"once\".**\n\nIf Object Type is \"once\", you MUST set _include_once to true (or include this object).\n\nThis object has ONLY ONE property: \"query\" (a SQL UPDATE string).\nDO NOT create any other properties like \"update\", \"table\", \"set\", \"where\", etc.\n\nCORRECT format:\n```json\n{\"query\": \"UPDATE customers SET exported=true WHERE id={{record.id}}\"}\n```\n\nWRONG format (DO NOT DO THIS):\n```json\n{\"update\": {\"table\": \"customers\", \"set\": {...}}}\n```\n","properties":{"query":{"type":"string","description":"**REQUIRED** - A SQL UPDATE statement string to mark records as processed.\n\nThis is a plain SQL UPDATE query string, NOT a structured object.\n\nThe query runs AFTER each record is successfully exported, setting a flag\nto indicate the record has been processed.\n\nFormat: \"UPDATE <table> SET <column>=<value> WHERE <id_column>={{record.<id_field>}}\"\n\nExample: \"UPDATE customers SET exported=true WHERE id={{record.id}}\"\n\nThe {{record.id}} placeholder is replaced with the actual record ID from each exported row.\n"}}}}},"S3-3":{"type":"object","description":"Defines which files to retrieve from an Amazon S3 bucket. Required when the _connectionId\nfield references an AWS S3 connection; must not be included for other connection types.\nregion and bucket locate the source, keyStartsWith/keyEndsWith filter objects by key, and\nbackupBucket with keyPrefix controls where files are moved after retrieval.","required":["region","bucket"],"properties":{"region":{"type":"string","default":"us-east-1","description":"AWS region where the bucket is located. Case-insensitive; the value is normalized to\nlowercase."},"bucket":{"type":"string","description":"S3 bucket to retrieve files from. The connection's AWS credentials must have\ns3:ListBucket and s3:GetObject permissions on it."},"keyStartsWith":{"type":"string","description":"Only retrieves objects whose keys start with this value (case-sensitive), effectively\nselecting a folder in S3's flat key structure. When combined with keyEndsWith, objects\nmust match both."},"keyEndsWith":{"type":"string","description":"Only retrieves objects whose keys end with this value (case-sensitive), commonly a file\nextension. When combined with keyStartsWith, objects must match both."},"backupBucket":{"type":"string","description":"Bucket in the same region where files are moved after successful export, giving you an\nindependent backup; if omitted, files are simply deleted from the source bucket after\nsuccessful export unless `file.skipDelete` is true (Celigo also keeps its own copy of\nprocessed files for a set period). The connection's AWS credentials must have\ns3:PutObject permission on this bucket."},"keyPrefix":{"type":"string","description":"Prefix prepended to each file's name when it is moved to the backup bucket; accepts\nstatic text or handlebars templates. The original directory structure is not preserved —\nonly the filename is appended to this prefix. Ignored unless backupBucket is set."}}},"Wrapper-3":{"type":"object","description":"Configuration for Wrapper exports, which delegate data retrieval to custom connector code\n(typically a stack-hosted function) rather than a built-in adaptor. Required when the\n_connectionId field references a wrapper connection.","required":["function"],"properties":{"function":{"type":"string","description":"Name of the function the wrapper invokes to retrieve records. Must match a callable\nfunction in the wrapper's execution context; names are case-sensitive."},"configuration":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"array","maxItems":0}],"description":"Free-form settings passed to the wrapper function at runtime (connector-specific keys such\nas method, apiVersion, headers, relativePath, or body). Structure is defined by the wrapper\ncode, not by this schema. May be stored as an empty array when no settings exist."}}},"Parsers":{"type":"array","description":"Configuration for parsing XML payloads (i.e. files, HTTP responses, etc.). Use this field when you need to process XML data\nand transform it into a JSON records.\n\n**Implementation notes**\n\n- This is where you configure how to parse XML data in your resource\n- Although defined as an array, you typically only need a single parser configuration\n- Currently only XML parsing is supported\n- Only configure this field when working with XML data that needs structured parsing\n","items":{"type":"object","properties":{"version":{"type":"string","description":"Version identifier for the parser configuration format. Currently only version \"1\" is supported.\n\nAlways set this field to \"1\" as it's the only supported version at this time.\n","enum":["1"]},"type":{"type":"string","description":"Defines the type of parser to use. Currently only \"xml\" is supported.\n\nWhile the system is designed to potentially support multiple parser types in the future,\nat this time only XML parsing is implemented, so this field must be set to \"xml\".\n","enum":["xml"]},"name":{"type":"string","description":"Optional identifier for the parser configuration. This field is primarily for documentation\npurposes and is not functionally used by the system.\n\nThis field can be omitted in most cases as it's not required for parser functionality.\n"},"rules":{"type":"object","description":"Configuration rules that determine how XML data is parsed and converted to JSON.\nThese settings control the structure and format of the resulting JSON records.\n\n**Parsing options**\n\nThere are two main parsing strategies available:\n- **Automatic parsing**: Simple but produces more complex output\n- **Custom parsing**: More control over the resulting JSON structure\n","properties":{"V0_json":{"type":"boolean","description":"Controls the XML parsing strategy.\n\n- When set to **true** (Automatic): XML data is automatically converted to JSON without\n  additional configuration. This is simpler to set up but typically produces more complex\n  and deeply nested JSON that may be harder to work with.\n\n- When set to **false** (Custom): Gives you more control over how the XML is converted to JSON.\n  This requires additional configuration (like listNodes) but produces cleaner, more\n  predictable JSON output.\n\nMost implementations use the Custom approach (false) for better control over the output format.\n"},"listNodes":{"type":"array","description":"Paths of elements that always parse as arrays. An element that can repeat parses as an\nobject when it occurs once and as an array when it occurs more than once, so list every\nrepeating element here to give each record the same shape regardless of item count.\nEach entry is the element's full path from the record element selected by the resource\npath (from the document root when there is no resource path), with an optional leading\nslash. Only relevant when V0_json is set to false (Custom parsing).\n","items":{"type":"string"}},"includeNodes":{"type":"array","description":"Limits which XML nodes are included in the output JSON.\n\nFor large XML documents, you can use this field to extract only the nodes you need,\nreducing the size and complexity of the resulting JSON. Only nodes specified here\n(and their children) will be included in the output.\n\nEach entry should be a simplified XPath expression pointing to nodes to include.\n\nOnly relevant when V0_json is set to false (Custom parsing).\n","items":{"type":"string"}},"excludeNodes":{"type":"array","description":"Specifies which XML nodes should be excluded from the output JSON.\n\nSometimes it's easier to specify which nodes to exclude rather than which to include.\nUse this field to identify nodes that should be omitted from the output JSON.\n\nEach entry should be a simplified XPath expression pointing to nodes to exclude.\n\nOnly relevant when V0_json is set to false (Custom parsing).\n","items":{"type":"string"}},"stripNewLineChars":{"type":"boolean","description":"Controls whether newline characters are removed from text values.\n\nWhen set to true, all newline characters (\\n, \\r, etc.) will be removed from\ntext content in the XML before conversion to JSON.\n","default":false},"trimSpaces":{"type":"boolean","description":"Controls whether leading and trailing whitespace is trimmed from text values.\n\nWhen set to true, all values will have leading and trailing whitespace removed\nbefore conversion to JSON.\n","default":false},"attributePrefix":{"type":"string","description":"Specifies a character sequence to prepend to XML attribute names when converted to JSON properties.\n\nIn XML, both elements and attributes can exist at the same level, but in JSON this distinction is lost.\nTo maintain the distinction between element data and attribute data in the resulting JSON, this prefix\nis added to attribute names during conversion.\n\nFor example, with attributePrefix set to \"Att-\" and an XML element like:\n```xml\n<product id=\"123\">Laptop</product>\n```\n\nThe resulting JSON would be:\n```json\n{\n  \"product\": \"Laptop\",\n  \"Att-id\": \"123\"\n}\n```\n\nThis helps maintain the distinction between element content and attribute values in the\nconverted JSON, making it easier to reference specific data in downstream processing steps.\n"},"textNodeName":{"type":"string","description":"Specifies the property name to use for element text content when an element has both\ntext content and child elements or attributes.\n\nWhen an XML element contains both text content and other nested elements or attributes,\nthis field determines what property name will hold the text content in the resulting JSON.\n\nFor example, with textNodeName set to \"value\" and an XML element like:\n```xml\n<item id=\"123\">\n  Laptop\n  <category>Electronics</category>\n</item>\n```\n\nThe resulting JSON would be:\n```json\n{\n  \"item\": {\n    \"value\": \"Laptop\",\n    \"category\": \"Electronics\",\n    \"id\": \"123\"\n  }\n}\n```\n\nThis allows for unambiguous parsing of complex XML structures that mix text content with\nchild elements. Choose a name that's unlikely to conflict with actual element names in your XML.\n"}}}}}},"MockOutput":{"type":["object","null"],"description":"Sample data that simulates the output from an export for testing and configuration purposes.\n\nMock output allows you to configure and test flows without executing the actual export or\nwaiting for real-time data to arrive. This is particularly useful for:\n- Initial flow configuration and testing\n- Mapping development without requiring live data\n- Generating metadata for downstream flow steps\n- Creating realistic test scenarios\n- Documenting expected data structures\n\n**Structure**\n\nThe mock output must follow the integrator.io canonical format, which consists of a\n`page_of_records` array containing record objects. Each record object has a `record`\nproperty that contains the actual data fields.\n\n```json\n{\n  \"page_of_records\": [\n    {\n      \"record\": {\n        \"field1\": \"value1\",\n        \"field2\": \"value2\",\n        ...\n      }\n    },\n    ...\n  ]\n}\n```\n\n**Usage**\n\nIn a flow test run, and for an upstream lookup during a page-processor preview\n(`previewPageProcessor`), integrator.io substitutes this mock output for the export's live\nresponse instead of calling the source. A saved mock is not read anywhere else: a target\nlookup in that preview runs its live query, invoking the export\n(`POST /v1/exports/{_id}/invoke`) reads the source, and real runs ignore it. This allows you\nto:\n- Test mappings with representative data\n- Configure downstream flow steps without waiting for real data\n- Simulate various data scenarios\n\nWhere it is substituted, the records stand in for the RAW parsed response: the export's own\n`transform` and output `filter` still run on top of them, so downstream steps — and a\nlookup's results mapping — see the transformed, filtered records, not the shape stored here.\n\nIn a flow test run of an HTTP export with a saved mock (live probe, 2026-10-21), the export\nstill listed `request` and `response` stages: `request` held the request the export composes,\nand `response` held the mock under the source's URL, as did the test run's request log, which\nrecorded it as a 200. Read those stages as the substituted mock, not as a live call.\n\n`POST /v1/exports/preview` reads the `mockOutput` in its request body, not the saved one. For\nan export the preview supports (an HTTP export, for example), a body that carries it (as an\nexport read back with GET does) returns that snapshot as a lone `parse` stage without calling\nthe source; remove `mockOutput` from the body to preview live records.\n\n**Limitations**\n\n- Maximum of 10 records\n- Maximum size of 1 MB\n- Must follow the canonical format shown above\n\nMock output can be populated automatically from preview data (which is the parsed response,\nbefore `transform` / `filter`) or entered manually.\n","properties":{"page_of_records":{"type":"array","description":"Array of record objects in the integrator.io canonical format.\n\nEach item in this array represents one record that would be processed\nby the flow during execution.\n","items":{"type":"object","properties":{"record":{"type":"object","description":"Container for the actual record data fields.\n\nThe structure of this object will vary depending on the specific\nexport configuration and the source system's data structure.\n","additionalProperties":true}}}}}},"Tool":{"type":"object","required":["_id","name","_integrationId","createdAt","lastModified"],"description":"Tool object as returned by the API.","allOf":[{"$ref":"#/components/schemas/ToolBase"},{"$ref":"#/components/schemas/ResourceResponse"},{"type":"object","properties":{"_sourceId":{"type":"string","format":"objectId","readOnly":true,"description":"Origin resource ID when this tool was created by cloning or installing a template."},"draftExpiresAt":{"type":"string","format":"date-time","readOnly":true,"description":"Timestamp when a draft tool auto-deletes. Server-computed when `draft` is set at\ncreation."}}}]},"ToolBase":{"type":"object","description":"Writable tool fields shared by the request and response schemas.","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable name for the tool.\n\nDisplayed in the UI and used to identify the tool's purpose.\n"},"description":{"type":"string","maxLength":5120,"description":"Optional detailed description of what the tool does.\n\nUse this to document the tool's purpose, expected inputs/outputs,\nand any special considerations.\n"},"_integrationId":{"type":"string","format":"objectId","description":"Reference to the integration this tool belongs to.\n\nEvery tool must be associated with an integration. The integration\ndetermines the scope and access controls for the tool.\n"},"input":{"$ref":"#/components/schemas/Input"},"output":{"$ref":"#/components/schemas/Output"},"routers":{"type":"array","description":"Optional routers for conditional processing logic.\n\nRouters allow you to direct input data to different processing branches\nbased on filter criteria or script logic. Tools only support\n\"first_matching_branch\" routing strategy.\n\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal value to exit the tool.\n\nA tool saves without `routers`, but it can't run: `POST /v1/tools/{_id}/invoke`\nand a test run answer 400 `tool_empty_router` (\"Tool has no steps configured.\nAdd at least one step before executing the tool.\"). A full PUT that omits\n`routers` removes the steps the same way. Put linear steps in one pass-through\nrouter with a single branch.\n","items":{"$ref":"#/components/schemas/Router"}},"aiDescription":{"$ref":"#/components/schemas/AIDescription"},"draft":{"type":"boolean","description":"When true, this tool is a draft that auto-deletes when its expiry passes\n(`draftExpiresAt` in the response). Set at creation; an update can clear the\nflag but never set it."}}},"Input":{"type":"object","description":"Configuration for the tool's input processing.\n\nDefines the expected input structure, optional transformations to apply\nbefore routing, and mock data for testing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the input configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the expected input data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the expected input data structure.\n\nUsed for validation, documentation, and AI-assisted tooling.\nMust be a valid JSON Schema document. Leave `$schema` out or name draft-07: a\n`$schema` naming draft 2020-12 fails the save with 422 \"Invalid input.schema -\nno schema with key or ref ...\". An `$id` saves.\n\nEach invoke's input is validated against it. A missing `required` property\nrefuses the call with 422 `tool_input_schema_validation_failed` (\"Tool input\nvalidation failed: must have required property '<name>'\"), and a failed\n`pattern` with 422 \"Tool input validation failed: <field>: must match pattern\n...\". A property made required later therefore breaks callers that don't send\nit.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; invocation input validates the same either way.\nUnset opens the visual editor, and an edit there rebuilds `schema` from the field list,\nkeeping only `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"transform":{"$ref":"#/components/schemas/Transform"},"traceKeyTemplate":{"type":"string","maxLength":1024,"description":"Handlebars template evaluated against the invocation input (`record`) to produce\nthe `traceKey` stored on each run-history entry, so an invocation can be found again\nwith the `traceKeyPrefix` filter on `GET /v1/tools/{_id}/invocations`. Omit it and\nentries carry an empty `traceKey`."},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Input stage until this time: while it lies in\nthe future, every record entering the tool is written to\n`GET /v1/tools/{_id}/tool_input/requests` with its input-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/input/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Sample input saved with the tool. `POST /v1/tools/{_id}/test/run` doesn't read it:\nsend it as that call's `input`, or the run starts from an empty record. Maximum\nsize: 1MB.\n","additionalProperties":true}}},"Output":{"type":"object","description":"Configuration for the tool's output processing.\n\nDefines how the tool's results are mapped, transformed, and enriched\nbefore being returned. Supports field mappings, lookups for data\nenrichment, and custom script hooks for pre/post-mapping processing.\n","properties":{"name":{"type":"string","maxLength":200,"description":"Display name for the output configuration.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of the output data and its purpose.\n"},"schema":{"type":"object","description":"JSON Schema describing the output data structure.\n\nUsed for documentation and validation of the tool's output.\nMust be a valid JSON Schema document.\n\nThe mapped output is validated against it. While the stored schema holds\n`additionalProperties: false`, `POST /v1/tools/{_id}/invoke` answers 422 \"Tool\noutput validation failed: must NOT have additional properties\" when the output\nmappings write a field the schema doesn't list.\n","additionalProperties":true},"schemaMode":{"type":"string","enum":["manual","json"],"description":"Which editor the UI opens `schema` in; the output validates the same either way. Unset\nopens the visual editor, and an edit there rebuilds `schema` from the field list, keeping\nonly `type`, `properties`, `required`, `items`, `description`, `enum`, `pattern`,\n`minLength`/`maxLength`, `minimum`/`maximum`, `exclusiveMinimum`/`exclusiveMaximum`,\n`multipleOf`, `minItems`/`maxItems` and a `\"null\"` type — `format`, `const`, `default`,\n`additionalProperties`, `$ref` and `allOf`/`anyOf`/`oneOf`/`if` are dropped without a\nprompt, and `integer` becomes `number`."},"mappings":{"description":"Field mappings to transform data into the output format.\n\nMaps data from processing results to the output structure.\nUses Celigo's standard mapping format with extract/generate field paths —\na flat array of mapping entries (each entry may recurse via its own\nnested ``mappings`` for object/array structures).\n","allOf":[{"$ref":"#/components/schemas/Mappings"}]},"lookups":{"type":"array","description":"Lookup tables for data enrichment during output processing.\n\nStatic key-value mappings used to translate values (e.g., status codes,\ncategory names) during output generation.\n","items":{"type":"object","properties":{"name":{"type":"string","description":"Name of the lookup, used to reference it from mappings.\n"},"map":{"type":"object","description":"Key-value mapping object. Keys are the input values and\nvalues are the corresponding output values.\n","additionalProperties":true},"default":{"type":"string","description":"Default value returned when the input key is not found in the map.\n"},"allowFailures":{"type":"boolean","description":"Whether to continue processing if the lookup fails to find a match\nand no default is provided.\n"}}}},"hooks":{"type":"object","description":"Custom script hooks for pre- and post-mapping processing.\n\nAllows running custom JavaScript functions before and after\noutput mappings are applied.\n","properties":{"preMap":{"type":"object","description":"Script to run before applying output mappings.\n\nCan modify the data before it is mapped to the output structure.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}},"postMap":{"type":"object","description":"Script to run after applying output mappings.\n\nCan modify the final output data after mappings are applied.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute within the script"}}}}},"debugUntil":{"type":"string","format":"date-time","description":"Turns on debug capture for the Tool Output stage until this time: while it lies in\nthe future, every post-mapped record is written to\n`GET /v1/tools/{_id}/tool_output/requests` with its output-schema outcome, in every\nexecution context (standalone invoke, AI agent or MCP caller, flow or API). At\nmost 3600 seconds ahead — a later value is rejected with `422` `update_not_allowed`\nand nothing is saved. Written by PUT or by `PATCH /v1/tools/{_id}` on\n`/output/debugUntil`; `remove` (or a past time) stops capture. Capture is not\nretroactive and does not change record retention."},"mockInput":{"type":"object","description":"Mock data for testing the tool's output processing.\n\nProvides sample data that would arrive from the routing/processing\nstage, used to test mapping and lookup logic. Maximum size: 1MB.\n","additionalProperties":true}}},"Router":{"type":"object","description":"Configuration for conditional routing within a tool.\n\nRouters evaluate input data and direct it to different processing branches\nbased on criteria. This enables complex business logic and conditional\nprocessing within the tool.\n\nUnlike flows, tools only support \"first_matching_branch\" routing strategy.\nBranches can chain to other routers or use the special \"outputRouter\"\nterminal sink to exit the tool and return results.\n\nA router comes in two shapes, told apart by whether `routeRecordsTo` and\n`routeRecordsUsing` are set: a **branching router** sets both and has two or\nmore branches, each taking the inputs its `inputFilter` (or the router's\n`script`) matches — the node you see in the Tool builder; a **pass-through\nrouter** sets neither and has exactly one branch that every input goes\nstraight through — not drawn, it holds the steps before the first branching\nrouter and merges branches back into shared steps (branches rejoin only by\npointing `nextRouterId` at the same router).\n","properties":{"id":{"type":"string","maxLength":36,"pattern":"^[A-Za-z0-9_-]+$","description":"Unique identifier for this router within the tool.\n\nUsed to reference this router from other routers' branch `nextRouterId`.\nUse at most 36 letters, digits, underscores and hyphens: a longer id fails\nthe save with 422 (\"Router id is missing or contains more than 36 characters\nor invalid characters. Please correct the router id.\"), as on flow routers.\n"},"name":{"type":"string","maxLength":300,"description":"Human-readable name for the router.\n"},"routeRecordsTo":{"type":"string","enum":["first_matching_branch"],"description":"Routing strategy. Tools only support \"first_matching_branch\",\nwhich routes to the first branch whose criteria match the input.\nSet together with `routeRecordsUsing` on a branching router; leave\nboth unset on a one-branch pass-through router.\n"},"routeRecordsUsing":{"type":"string","enum":["input_filters","script"],"description":"How a branching router decides which branch an input matches. Set together\nwith `routeRecordsTo`; leave both unset on a one-branch pass-through router.\n"},"script":{"type":"object","description":"Script configuration when routeRecordsUsing is \"script\".\n\nThe function should return the name of the branch to route to.\n","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name that returns the branch name"}}},"branches":{"type":"array","description":"List of branches defining different processing paths.\n\nEach branch has optional filter criteria and a set of processing steps.\nRecords are evaluated against branch criteria in order; the first\nmatching branch is selected.\n","items":{"type":"object","properties":{"name":{"type":"string","maxLength":300,"description":"Name of this branch.\n"},"description":{"type":"string","maxLength":10240,"description":"Description of when and why this branch is selected.\n"},"branchId":{"type":"string","description":"Stable identifier for this branch within the tool, generated by\nthe builder. Used to reference the branch independently of its\nposition in the branches array (e.g., from step requests).\n"},"inputFilter":{"type":"object","description":"Filter criteria to determine if this branch should be selected.\n\nUses Celigo's expression-based filter format. Send `version` and `rules`\ndirectly on `inputFilter` — not inside the `{type: \"expression\", expression: {...}}`\nwrapper that export and import filters use. A wrapped filter is saved without an\nerror, but the branch is stored with no `inputFilter` and takes every input.\n","properties":{"version":{"type":"string","enum":["1"],"description":"Filter version"},"rules":{"type":"array","description":"Filter rules in Celigo expression-based filter format.\n\nArray-based DSL where the first element is an operator (e.g., \"equals\", \"and\", \"or\"),\nfollowed by operands which can be nested expressions.\n","items":{}}}},"nextRouterId":{"type":"string","description":"Identifier of the next router to chain to after this branch completes.\n\nUse \"outputRouter\" as a special terminal value to exit the tool\nand return the processing results.\n\nEvery branch needs one. A branch without it is saved, but every invoke is\nthen refused with 422 and every test run with 400 `tool_dangling_branch`\n(\"One or more branches are not merged back. Review the setup to ensure all\nbranches converge.\"), whichever branch the input takes. An id that names no\nrouter fails the save with 422 \"Invalid nextRouterId is defined on the branch.\nPlease correct the branch configuration.\"\n"},"pageProcessors":{"type":"array","description":"Processing steps to execute in this branch.\n\nEach processor references an export (lookup) or import resource\nfor data retrieval or submission. An export is a step at most once in a\ntool: using it again, on another branch or in the same one, fails the save\nwith 422 \"The export <id> is already included as a pageProcessor in your\nflow, and cannot be added again.\"\n","items":{"type":"object","properties":{"type":{"type":"string","enum":["export","import"],"description":"Type of processor.\n\n- **export**: Retrieves data from an external system (lookup)\n- **import**: Sends data to an external system\n"},"_exportId":{"type":"string","format":"objectId","description":"Export resource reference (when type is \"export\")"},"_importId":{"type":"string","format":"objectId","description":"Import resource reference (when type is \"import\")"},"proceedOnFailure":{"type":"boolean","description":"When true, the later steps and the output stage still run after this\nprocessor fails; when false, they're skipped. Either way a direct\n`POST /v1/tools/{_id}/invoke` answers 422 with the failed step's error,\nwithout the output."},"setupInProgress":{"type":"boolean","description":"When true, the processor's configuration is still being\nset up in the UI and the step is not yet runnable.\n"},"responseMapping":{"type":"object","description":"Merges fields from this processor's response back onto the\nin-flight record so later processors and the tool's output\ncan read them. Extracts do NOT read the raw application\nresponse — they evaluate against the platform's canonical\nper-record envelope: for lookups (`type: \"export\"`) that is\n`{\"statusCode\", \"data\": [<result records>], \"errors\"}`\nwhere `data` holds the records AFTER the export's own\n`transform` and output `filter`, so paths must start from\n`data` and name transformed fields by their transformed\nname (e.g. `data[0].name`) — a lookup result field\nwritten without the `data` prefix resolves to nothing;\nfor imports it is `{\"id\", \"statusCode\", \"ignored\",\n\"_json\", ...<body fields flattened>}`, so use `id`,\n`_json.<path>`, or the body field itself at the root\n(`orderNumber` and `_json.orderNumber` read the same\nvalue). An extract may be a Handlebars expression over\nthe same envelope (`{{data.0.name}}`,\n`{{#if data.length}}found{{/if}}`; dot indexes only; a\nstring result); there is no `record` object in this\ncontext, so `{{record.…}}` renders nothing.\n","properties":{"fields":{"type":"array","description":"Simple field-level mappings","items":{"type":"object","properties":{"extract":{"type":"string","description":"Path within the canonical response envelope to\ncopy the value from (`data[0].x` / `data.0.x`\nfor lookups; `id`, `_json.<path>` or the\nflattened body field `<path>` for imports), or\na Handlebars expression over that envelope (dot\nindexes inside `{{ }}`, never a `record.` root).\n"},"generate":{"type":"string","description":"Field path on the in-flight record where the\nextracted value is stored (dot notation for\nnesting).\n"}}}},"lists":{"type":"array","description":"List-level mappings for array data","items":{"type":"object","properties":{"generate":{"type":"string","description":"Target list path"},"fields":{"type":"array","description":"Field-level mappings applied to each item in the list.","items":{"type":"object","properties":{"extract":{"type":"string","description":"Source field path"},"generate":{"type":"string","description":"Target field path"}}}}}}}}},"hooks":{"type":"object","description":"Custom scripts for processing","properties":{"postResponseMap":{"type":"object","description":"Script to run after response mapping","properties":{"_scriptId":{"type":"string","format":"objectId","description":"Reference to the script resource"},"function":{"type":"string","description":"Function name to execute"}}}}}}}}}}}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/descendants":{"get":{"summary":"List resources a tool depends on, grouped by type","operationId":"listToolDescendants","tags":["Tools"],"description":"Returns the full dependency tree of a tool as three arrays: the\n`imports`, `exports`, and nested `tools` it references directly or\ntransitively. Each entry is the complete resource document, so the\ncaller doesn't need to fan out individual GETs.\n\nPair with `GET /v1/tools/{_id}/connections` to enumerate the full\nresource and connection footprint in two calls.","parameters":[{"name":"_id","in":"path","required":true,"description":"Tool id.","schema":{"type":"string","format":"objectId"}}],"responses":{"200":{"description":"Full descendant resource docs grouped by type. Each array may be\nempty when the tool doesn't reference that resource kind.","content":{"application/json":{"schema":{"type":"object","properties":{"imports":{"type":"array","description":"Full Import documents the tool depends on.","items":{"$ref":"#/components/schemas/Import"}},"exports":{"type":"array","description":"Full Export documents the tool depends on.","items":{"$ref":"#/components/schemas/Export"}},"tools":{"type":"array","description":"Full inner Tool documents nested beneath this tool.","items":{"$ref":"#/components/schemas/Tool"}}}}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"}}}}}}
````

## Clone a tool

> Creates a copy of a tool in the target integration, together with the export and\
> import steps it references, and returns a manifest of the resources the clone\
> created. The clone records its lineage in \`\_sourceId\`, which places it in the source\
> tool's clone family.\
> \
> The target integration is never inferred from the source tool — pass the source\
> tool's own integration id to clone in place. When any step of the tool references a\
> connection, \`connectionMap\` is required as well: every connection the tool uses must\
> appear as a key, mapped to itself to reuse it or to another connection of the account\
> to repoint the cloned steps. A tool with no connection-bearing steps clones without it.\
> \
> Use \`GET /v1/tools/{\_id}/clone/preview\` first to see what the clone would create —\
> each \`Connection\` object in the preview is a key \`connectionMap\` must carry.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/clone":{"post":{"operationId":"cloneTool","tags":["Tools"],"summary":"Clone a tool","description":"Creates a copy of a tool in the target integration, together with the export and\nimport steps it references, and returns a manifest of the resources the clone\ncreated. The clone records its lineage in `_sourceId`, which places it in the source\ntool's clone family.\n\nThe target integration is never inferred from the source tool — pass the source\ntool's own integration id to clone in place. When any step of the tool references a\nconnection, `connectionMap` is required as well: every connection the tool uses must\nappear as a key, mapped to itself to reuse it or to another connection of the account\nto repoint the cloned steps. A tool with no connection-bearing steps clones without it.\n\nUse `GET /v1/tools/{_id}/clone/preview` first to see what the clone would create —\neach `Connection` object in the preview is a key `connectionMap` must carry.","parameters":[{"name":"_id","in":"path","required":true,"description":"The id of the tool to clone.","schema":{"type":"string","format":"objectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["_integrationId"],"properties":{"_integrationId":{"type":"string","format":"objectId","description":"Integration the cloned tool is created in."},"name":{"type":"string","description":"Name for the cloned tool. Defaults to `Clone - <source tool name>` when omitted."},"connectionMap":{"type":"object","description":"Connection ids the tool's steps reference, each mapped to the connection the\ncloned steps use instead. Required when any step references a connection —\nomitted or empty, the clone fails with 400 `invalid_connectionMap`. Map an id\nto itself to keep the connection; every key must be a connection the tool\nreferences and every value a connection in the account.","additionalProperties":{"type":"string","format":"objectId"}},"_stackId":{"type":"string","format":"objectId","description":"Stack that the cloned steps' stack references point at. Required when the clone\npreview reports `stackRequired: true` (400 `stack_not_found` without it)."}}}}}},"responses":{"201":{"description":"The clone was created. Returns a manifest of every resource the clone created —\nthe new tool plus the export and import steps it copied.","content":{"application/json":{"schema":{"type":"array","description":"Manifest of resources created by the clone.","items":{"type":"object","properties":{"model":{"type":"string","description":"Model name of the created resource (e.g. `Tool`, `Export`, `Import`)."},"_id":{"type":"string","format":"objectId","description":"Unique id of the created resource."}}}}}}},"400":{"description":"The body omits `_integrationId` (`required_field_missing`), or the tool's steps\nreference a connection and `connectionMap` is missing or empty\n(`invalid_connectionMap`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"description":"`connectionMap` does not match the tool: a value is not a connection in the account,\nor a key is not a connection the tool's steps reference (the unmapped connection is\nreported alongside). Nothing is created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Preview cloning a tool

> Returns a preview of the resources that would be created by cloning this tool. No\
> resources are created.\
> \
> Call this before \`POST /v1/tools/{\_id}/clone\` to inspect what the clone would create.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ClonePreviewResponse":{"type":"object","description":"Preview of the resources that would be created by a clone operation.\nEach object in the `objects` array represents a resource that will be\ncloned, including the target resource and all transitive dependencies\n(connections, scripts, exports, imports, etc.).\n","properties":{"objects":{"type":"array","description":"List of resources that would be created by the clone. Always includes\nthe target resource and may include transitive dependencies such as\nconnections, scripts, exports, imports, async helpers, and lookup caches.\n","items":{"type":"object","properties":{"model":{"type":"string","description":"The model type of the resource. Observed values include\nAsyncHelper, Connection, Export, Flow, Import, Integration,\nLookupCache, Script, and Tool.\n"},"doc":{"type":"object","description":"The full resource document that would be created by the clone.","additionalProperties":true}}}},"stackRequired":{"type":"boolean","description":"`true` when the resource or one of its dependencies references a stack (a hook or\nwrapper connection that runs on a stack). Send `_stackId` in the clone request: without\nit the clone fails with 400 `stack_not_found`."},"_stackId":{"type":["string","null"],"description":"The first stack the resource or a dependency references, or null. Pass it, or another\nstack in the target environment, as `_stackId` in the clone request; the clone points\nevery stack reference at that one stack."}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/clone/preview":{"get":{"operationId":"previewCloneTool","tags":["Tools"],"summary":"Preview cloning a tool","description":"Returns a preview of the resources that would be created by cloning this tool. No\nresources are created.\n\nCall this before `POST /v1/tools/{_id}/clone` to inspect what the clone would create.","parameters":[{"name":"_id","in":"path","required":true,"description":"Tool id to preview cloning.","schema":{"type":"string","format":"objectId"}}],"responses":{"200":{"description":"Clone preview retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClonePreviewResponse"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"}}}}}}
```

## List dependencies of a tool

> Returns the set of resources that depend on the specified resource.\
> The response is an object whose keys are dependent-resource types\
> (e.g. \`flows\`, \`imports\`) and whose values are arrays of dependency\
> entries.\
> \
> Returns \`{}\` for both zero-dependency and nonexistent IDs.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"DependencyResponse":{"type":"object","description":"Map of dependent-resource types to arrays of dependency entries.\nKeys are plural resource type strings (e.g. `flows`, `imports`,\n`connections`). An empty object `{}` means no dependents.\n","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/DependencyEntry"}}},"DependencyEntry":{"type":"object","description":"A single resource that depends on the queried resource.","properties":{"id":{"type":"string","description":"Unique identifier of the dependent resource."},"name":{"type":"string","description":"Display name of the dependent resource."},"type":{"type":"string","description":"Sub-type of the dependent resource when its collection distinguishes one, such as\n`stream` for a listener export. Only present for resource types that carry a `type`."},"paths":{"type":"array","description":"Dot-notation paths within the dependent resource that reference\nthe target resource. `[*]` denotes array elements.","items":{"type":"string"}},"accessLevel":{"type":"string","description":"The caller's access level on the dependent resource."},"dependencyIds":{"type":"object","description":"Map of resource types to arrays of IDs that this dependent\nresource references on the target. Keys are singular or plural\nresource type strings; values are arrays of ID strings.","additionalProperties":{"type":"array","items":{"type":"string"}}}},"required":["id","name","paths","accessLevel","dependencyIds"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}}}},"paths":{"/v1/tools/{_id}/dependencies":{"get":{"operationId":"listToolDependencies","tags":["Tools"],"summary":"List dependencies of a tool","description":"Returns the set of resources that depend on the specified resource.\nThe response is an object whose keys are dependent-resource types\n(e.g. `flows`, `imports`) and whose values are arrays of dependency\nentries.\n\nReturns `{}` for both zero-dependency and nonexistent IDs.","parameters":[{"name":"_id","in":"path","required":true,"description":"Resource ID.","schema":{"type":"string","format":"objectId"}}],"responses":{"200":{"description":"Dependency map. Keys are resource-type strings; values are arrays\nof dependency entries. Returns `{}` when no dependents exist.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DependencyResponse"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"}}}}}}
```

## Get a downloadable template for a tool

> Packages the tool as an installable template and returns a signed S3 URL\
> where the template \`.zip\` can be downloaded. The URL is pre-signed and\
> short-lived (approximately 15 minutes), so fetch the file promptly; call\
> the endpoint again for a fresh URL.\
> \
> The \`.zip\` contains the tool definition plus every resource it references\
> — nested tools, exports, imports, connections, and scripts — grouped into\
> one folder per resource type, with an \`integration.json\` manifest at the\
> root. Requires the \`create:tool:template\` permission.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}}},"paths":{"/v1/tools/{_id}/template":{"get":{"operationId":"getToolTemplate","tags":["Tools"],"summary":"Get a downloadable template for a tool","description":"Packages the tool as an installable template and returns a signed S3 URL\nwhere the template `.zip` can be downloaded. The URL is pre-signed and\nshort-lived (approximately 15 minutes), so fetch the file promptly; call\nthe endpoint again for a fresh URL.\n\nThe `.zip` contains the tool definition plus every resource it references\n— nested tools, exports, imports, connections, and scripts — grouped into\none folder per resource type, with an `integration.json` manifest at the\nroot. Requires the `create:tool:template` permission.","parameters":[{"name":"_id","in":"path","required":true,"description":"Tool id.","schema":{"type":"string","format":"objectId"}}],"responses":{"200":{"description":"Signed download URL for the tool template zip.","content":{"application/json":{"schema":{"type":"object","properties":{"signedURL":{"type":"string","format":"uri","description":"Pre-signed, short-lived S3 URL to download the template `.zip`."},"key":{"type":"string","description":"S3 object key for the generated template `.zip`, named `<toolId>.zip`."}}}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"}}}}}}
```

## Invoke a Tool synchronously

> Executes a Tool synchronously and returns the mapped output (or errors).\
> \
> Optional \`x-log-mode\` enables enterprise invocation logging. When logging is\
> active for the run, the response includes \`invocationId\` — use that value as\
> \`{executionId}\` with \`GET /v1/tools/{\_id}/invocations/{executionId}\`.\
> Sandbox Tools return \`403\` when logging is attempted.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/invoke":{"post":{"operationId":"invokeTool","tags":["Tools"],"summary":"Invoke a Tool synchronously","description":"Executes a Tool synchronously and returns the mapped output (or errors).\n\nOptional `x-log-mode` enables enterprise invocation logging. When logging is\nactive for the run, the response includes `invocationId` — use that value as\n`{executionId}` with `GET /v1/tools/{_id}/invocations/{executionId}`.\nSandbox Tools return `403` when logging is attempted.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}},{"name":"x-log-mode","in":"header","required":false,"description":"Enables enterprise Tool logging for this invoke. Omit to skip logging.\nEvery direct API call is a standalone invoke: only `debug` enables logging —\nthe other valid values are accepted but silently ignored (the invoke runs\nwithout logging; no error is returned). Values outside the enum return\n`400`. The `basic` and `detailed` levels (`standard` is an alias for\n`basic`) apply only to Agent, MCP, and Guardrail invocations, where Celigo\nservices forward the invoker's effective log level; external callers\ncannot select them.","schema":{"type":"string","enum":["basic","standard","detailed","debug"]}},{"name":"x-integration-id","in":"header","required":false,"description":"Invoker integration id for Agent or Guardrail invokes.\nIgnored for MCP and standalone invokes.","schema":{"type":"string","format":"objectId"}},{"name":"x-by-user-id","in":"header","required":false,"description":"End-user id that triggered an Agent or Guardrail invoke.\nIgnored for MCP and standalone invokes.","schema":{"type":"string","format":"objectId"}},{"name":"x-invoker-id","in":"header","required":false,"description":"Direct caller resource id for Agent or Guardrail invokes.\nIgnored for MCP and standalone invokes.","schema":{"type":"string","format":"objectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Tool invoke body. Both `input` and `overrides` are required — a missing\nbody or a missing key returns `400`. Pass `{}` for either when there is\nnothing to send.","required":["input","overrides"],"properties":{"input":{"type":"object","additionalProperties":true,"description":"Tool input data conforming to the Tool's input schema when defined."},"overrides":{"type":"object","description":"Runtime overrides for connections, imports, exports, and routers. Every\nconnection the Tool's steps reference must be bound in\n`overrides.connections` — a step whose connection is not overridden fails\nthe run with 422 `connection_not_overridden` naming the connection id.","additionalProperties":true,"properties":{"connections":{"type":"array","description":"Connection bindings, one per connection the Tool's steps reference. Name the\nconnection to run with in `_id`: a binding with only `_abstractId`, or with\nthe connection in `_connectionId` instead, is refused with 422 \"Invalid\noverride at overrides.connections[0]. Connection override must include at\nleast one of: _id, _borrowConcurrencyFromConnectionId,\nrequiresEndUserCredentials.\"","items":{"type":"object","required":["_abstractId"],"properties":{"_abstractId":{"type":"string","format":"objectId","description":"Connection id the Tool's export or import step was authored against."},"_id":{"type":"string","format":"objectId","description":"Connection to run the step with; may equal `_abstractId` to keep the authored connection."}}}}}}},"additionalProperties":true}}}},"responses":{"200":{"description":"Tool executed successfully.","content":{"application/json":{"schema":{"type":"object","required":["output","errors"],"properties":{"output":{"type":"object","additionalProperties":true},"errors":{"type":"array","description":"Empty on success. Non-empty execution errors are returned as `422` instead.","items":{"type":"object","additionalProperties":true}},"invocationId":{"type":"string","format":"objectId","description":"Present when enterprise Tool logging is active for the run."}},"additionalProperties":true}}}},"400":{"description":"Malformed request — a body missing `input` or `overrides`, an `x-log-mode` value\noutside the enum, or a Tool whose branches have no steps yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"description":"A step failed, or the Tool was refused before any step ran: a connection binding is\nmissing (`connection_not_overridden`) or has no `_id`, its branches don't all merge\nback (`tool_dangling_branch`), or the input fails `input.schema` (a missing `required`\nproperty answers `tool_input_schema_validation_failed`). The output stage answers 422 too when the stored\n`output.schema` holds `additionalProperties: false` and the output mappings write a field\nit doesn't list (\"Tool output validation failed: must NOT have additional properties\").\nA failed step answers 422 even when it has\n`proceedOnFailure: true`: the later steps still run, but the response carries the error,\nnot the output. Either way the response includes `invocationId` when logging is active,\nso the run can be opened in the run history.","content":{"application/json":{"schema":{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","description":"Execution errors, each with a `code`, `message`, `source`, and epoch-millisecond `time`.","items":{"type":"object","additionalProperties":true}},"invocationId":{"type":"string","format":"objectId","description":"Present when enterprise Tool logging is active for the run."}},"additionalProperties":true}}}}}}}}}
```

## List Tool invocation run history

> Returns the run history for a Tool — one entry per invocation that ran with\
> enterprise logging enabled (\`x-log-mode\` on \`POST /v1/tools/{\_id}/invoke\`, or the\
> log level forwarded by the calling Agent, MCP server, or Guardrail). Invocations\
> that ran without logging never appear here.\
> \
> Newest first by default. When \`time\_gte\`, \`time\_lte\`, \`next\`, and \`prev\` are all\
> omitted, only the last 5 minutes are returned — pass an explicit window for a\
> broader history. Use an entry's \`executionId\` with\
> \`GET /v1/tools/{\_id}/invocations/{executionId}\` to drill into its steps.\
> \
> Entries are written asynchronously: a run appears here a few seconds after\
> \`POST /v1/tools/{\_id}/invoke\` returns, so re-read before concluding that a run\
> was not logged.\
> \
> Sandbox Tools return \`403\`. Custom roles only see invocations whose\
> \`\_invokerIntegrationId\` is in their accessible integrations.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ToolRunHistoryResponse":{"type":"object","description":"Paginated Tool invocation run history.","required":["invocations","_pagination"],"properties":{"invocations":{"type":"array","description":"Invocation summaries, newest first by default.","items":{"$ref":"#/components/schemas/ToolInvocationSummary"}},"_pagination":{"type":"object","description":"Cursor pagination URLs for the list.","required":["nextPageUrl","prevPageUrl"],"properties":{"nextPageUrl":{"type":["string","null"],"description":"URL to fetch the next page; null when there is no next page."},"prevPageUrl":{"type":["string","null"],"description":"URL to fetch the previous page; null when there is no previous page."}}}}},"ToolInvocationSummary":{"type":"object","description":"One logged Tool invocation in the run history.","required":["executionId","_toolId","_invokerIntegrationId","_triggeredByUserId","invokerType","_invokerId","status","traceKey","remoteIP","time","timeTaken","logMode"],"properties":{"executionId":{"type":"string","format":"objectId","description":"Identifier of this invocation. Same value returned as `invocationId` from\n`POST /v1/tools/{_id}/invoke` when logging is enabled; pass it to\n`GET /v1/tools/{_id}/invocations/{executionId}` to open the trace."},"_toolId":{"type":"string","format":"objectId","description":"Tool that was invoked."},"_invokerIntegrationId":{"type":"string","description":"Integration the invocation ran under — the caller's integration for Agent, MCP,\nand Guardrail invokes, the Tool's own integration for a standalone invoke. Empty\nstring when unset."},"_triggeredByUserId":{"type":"string","description":"User whose credentials triggered the invoke. Empty string when unset."},"invokerType":{"type":"string","enum":["agent","mcp","standalone","guardrail"],"description":"Kind of caller that invoked the Tool. Filter the list on it with the `invokerType` query parameter."},"_invokerId":{"type":"string","description":"Import id of the Agent, MCP, or Guardrail caller. Empty string for a standalone invoke."},"status":{"type":"string","enum":["success","error","ignore"],"description":"Outcome of the invocation."},"traceKey":{"type":"string","description":"Value the Tool's `input.traceKeyTemplate` produced for this invocation. Empty\nstring when the Tool has no template. Match on it with the `traceKeyPrefix` filter."},"remoteIP":{"type":"string","description":"Client IP address the invoke came from."},"time":{"type":"string","format":"date-time","description":"Timestamp when the invocation started."},"timeTaken":{"type":"integer","description":"Wall-clock duration of the invocation in milliseconds, from request to response.\nIt also counts time spent outside the steps, so it exceeds the sum of the step\n`timeTaken` values on the trace."},"logMode":{"type":"string","enum":["basic","detailed","debug"],"description":"Logging level that was active for this invocation; only `detailed` and `debug` produce step records."}},"additionalProperties":true},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/invocations":{"get":{"operationId":"listToolInvocations","tags":["Tools"],"summary":"List Tool invocation run history","description":"Returns the run history for a Tool — one entry per invocation that ran with\nenterprise logging enabled (`x-log-mode` on `POST /v1/tools/{_id}/invoke`, or the\nlog level forwarded by the calling Agent, MCP server, or Guardrail). Invocations\nthat ran without logging never appear here.\n\nNewest first by default. When `time_gte`, `time_lte`, `next`, and `prev` are all\nomitted, only the last 5 minutes are returned — pass an explicit window for a\nbroader history. Use an entry's `executionId` with\n`GET /v1/tools/{_id}/invocations/{executionId}` to drill into its steps.\n\nEntries are written asynchronously: a run appears here a few seconds after\n`POST /v1/tools/{_id}/invoke` returns, so re-read before concluding that a run\nwas not logged.\n\nSandbox Tools return `403`. Custom roles only see invocations whose\n`_invokerIntegrationId` is in their accessible integrations.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}},{"name":"pageSize","in":"query","description":"Number of entries per page. Larger values are clamped to 100; `0` or a non-numeric\nvalue returns `400` `invalid_url`.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"sortOrder","in":"query","description":"Sort direction by invocation time.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"next","in":"query","description":"Cursor timestamp for the next page. Copy it from `_pagination.nextPageUrl`\ntogether with `cursorExecutionId`; mutually exclusive with `prev`.","schema":{"type":"string","format":"date-time"}},{"name":"prev","in":"query","description":"Cursor timestamp for the previous page. Copy it from `_pagination.prevPageUrl`\ntogether with `cursorExecutionId`; mutually exclusive with `next`.","schema":{"type":"string","format":"date-time"}},{"name":"cursorExecutionId","in":"query","description":"Tie-breaker execution id, required when paging with `next`/`prev`.","schema":{"type":"string","format":"objectId"}},{"name":"time_gte","in":"query","description":"Include invocations at or after this timestamp.","schema":{"type":"string","format":"date-time"}},{"name":"time_lte","in":"query","description":"Include invocations at or before this timestamp.","schema":{"type":"string","format":"date-time"}},{"name":"traceKeyPrefix","in":"query","description":"Filter on `traceKey` (the value the Tool's `input.traceKeyTemplate` produced).\nA prefix longer than 2 characters matches with starts-with; 1–2 characters\nmatch exactly.","schema":{"type":"string","minLength":1,"maxLength":500}},{"name":"invokerType","in":"query","description":"Filter by invoker type — a single value or a comma-separated list of\n`ToolInvocationSummary.invokerType` values.","schema":{"type":"string"}},{"name":"_invokerId","in":"query","description":"Filter by the direct caller resource id (the Agent, MCP server, or Guardrail import).","schema":{"type":"string","format":"objectId"}},{"name":"_invokerIntegrationId","in":"query","description":"Narrow results to one invoker integration. Custom roles may only pass an\nintegration they can access; otherwise the list is empty.","schema":{"type":"string","format":"objectId"}},{"name":"status","in":"query","description":"Filter by invocation status — a single value or a comma-separated list of\n`ToolInvocationSummary.status` values. Any other token returns `400`\n`invalid_status`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated run history. `invocations[]` is empty when nothing matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolRunHistoryResponse"}}}},"400":{"description":"Invalid query parameter: a `status` token outside the enum (`invalid_status`), a\n`sortOrder` other than `asc`/`desc` (`invalid_sort_order`), a `time_gte`/`time_lte`\nthat is not a timestamp (`invalid_field`), or a `pageSize` below 1 or non-numeric\n(`invalid_url`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"description":"Unknown or inaccessible Tool `_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete Tool invocation logs in a date range

> Queues asynchronous deletion of the stored invocation logs for the Tool within\
> \`\[time\_gte, time\_lte]\`. Returns \`202 Accepted\` immediately; the entries disappear\
> from \`GET /v1/tools/{\_id}/invocations\` once the background cleanup runs.\
> Requires log-deletion access. Sandbox Tools return \`403\`.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/invocations":{"delete":{"operationId":"deleteToolInvocationLogs","tags":["Tools"],"summary":"Delete Tool invocation logs in a date range","description":"Queues asynchronous deletion of the stored invocation logs for the Tool within\n`[time_gte, time_lte]`. Returns `202 Accepted` immediately; the entries disappear\nfrom `GET /v1/tools/{_id}/invocations` once the background cleanup runs.\nRequires log-deletion access. Sandbox Tools return `403`.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}},{"name":"time_gte","in":"query","required":true,"description":"Start of the deletion window. Must be earlier than `time_lte`; omitting either bound\nreturns `400` `invalid_query_params`.","schema":{"type":"string","format":"date-time"}},{"name":"time_lte","in":"query","required":true,"description":"End of the deletion window. Neither bound may be in the future.","schema":{"type":"string","format":"date-time"}}],"responses":{"202":{"description":"Deletion request accepted for asynchronous processing. No body."},"400":{"description":"Missing or invalid window parameters — a bound is omitted, a bound is in the future,\nor `time_gte` is not before `time_lte`. Nothing is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"$ref":"#/components/responses/404-not-found"}}}}}}
```

## Get trace metadata for a Tool invocation

> Returns the invocation header plus one step record per stage the run passed\
> through: the Tool's input bubble (\`\<toolId>\_input\`), each export or import step,\
> and the output bubble (\`\<toolId>\_output\`). A run that failed before any step\
> executed returns an empty \`steps\` array. Use a step's \`\_expOrImpId\`, \`groupId\`, and\
> \`recordId\` with the log-data endpoint to read the payload it processed.\
> \
> When the invocation is missing or not visible under the caller's integration ACL,\
> the endpoint returns \`204 No Content\`. The trace is written asynchronously: for a few\
> seconds after \`POST /v1/tools/{\_id}/invoke\` returns, a valid \`invocationId\` also answers\
> \`204\`, and the header can then appear before its step records, with an empty \`steps\`\
> array. Re-read before treating a run as unknown or as failed before its first step.\
> \`404\` applies only to an unknown or inaccessible Tool \`\_id\`. A malformed \`executionId\`\
> returns \`422\`. Sandbox Tools return \`403\`.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ToolTraceMetadataResponse":{"type":"object","description":"Invocation header plus every step record for the run. A subset of the list summary —\n`_invokerIntegrationId`, `_triggeredByUserId`, and `remoteIP` are only on the list\nendpoint.","required":["executionId","_toolId","status","time","timeTaken","traceKey","invokerType","_invokerId","logMode","steps"],"properties":{"executionId":{"type":"string","format":"objectId","description":"Identifier of this invocation. Same value returned as `invocationId` from\n`POST /v1/tools/{_id}/invoke` when logging is enabled."},"_toolId":{"type":"string","format":"objectId","description":"Tool that was invoked."},"status":{"type":"string","enum":["success","error","ignore"],"description":"Outcome of the invocation."},"time":{"type":"string","format":"date-time","description":"Timestamp when the invocation started."},"timeTaken":{"type":"integer","description":"Wall-clock duration of the invocation in milliseconds, from request to response.\nIt also counts time spent outside the steps, so it exceeds the sum of the step\n`timeTaken` values — use the step sum when apportioning time across steps."},"traceKey":{"type":"string","description":"Value the Tool's `input.traceKeyTemplate` produced for this invocation. Empty string when the Tool has no template."},"invokerType":{"type":"string","enum":["agent","mcp","standalone","guardrail"],"description":"Kind of caller that invoked the Tool."},"_invokerId":{"type":"string","description":"Import id of the Agent, MCP, or Guardrail caller. Empty string for a standalone invoke."},"logMode":{"type":"string","enum":["basic","detailed","debug"],"description":"Logging level that was active for this invocation."},"steps":{"type":"array","description":"Step records in execution order — input bubble, branch steps, output bubble.\nEmpty when the run failed before its first step. Step records are written after\nthe header, so a run read seconds after it finished can also return an empty array.","items":{"$ref":"#/components/schemas/ToolInvocationStep"}}}},"ToolInvocationStep":{"type":"object","description":"One step record within a Tool invocation trace.","required":["status","timeTaken","groupId","recordId","time"],"properties":{"_expOrImpId":{"type":"string","description":"Identifier of the stage that produced this record: the export or import id for a\nstep in a branch, or the Tool's input/output bubble id (`<toolId>_input` /\n`<toolId>_output`). Absent for records not tied to a stage."},"status":{"type":"string","enum":["success","error","ignore"],"description":"Outcome of the step for this record."},"stage":{"type":"string","description":"Processing stage that recorded the outcome. Present when the platform recorded one; omitted otherwise."},"timeTaken":{"type":"integer","description":"Step processing time in milliseconds."},"groupId":{"type":"string","description":"Group the record belongs to within the step. Pass it to the log-data endpoint together with `recordId`."},"outputGroupId":{"type":"string","description":"Nested Tool output-bubble group id on ToolImport steps. Use as `groupId` when\nquerying `responseMapping` or `postResponseMapHook` log data for that ToolImport.\nAbsent for non-ToolImport steps."},"recordId":{"type":"string","description":"Identifier of this record within the run."},"parentRecordId":{"type":"string","description":"Record this one was derived from. Present on child records returned by the children endpoint."},"time":{"type":"string","format":"date-time","description":"Timestamp when the step ran."}},"additionalProperties":true},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/invocations/{executionId}":{"get":{"operationId":"getToolInvocationTrace","tags":["Tools"],"summary":"Get trace metadata for a Tool invocation","description":"Returns the invocation header plus one step record per stage the run passed\nthrough: the Tool's input bubble (`<toolId>_input`), each export or import step,\nand the output bubble (`<toolId>_output`). A run that failed before any step\nexecuted returns an empty `steps` array. Use a step's `_expOrImpId`, `groupId`, and\n`recordId` with the log-data endpoint to read the payload it processed.\n\nWhen the invocation is missing or not visible under the caller's integration ACL,\nthe endpoint returns `204 No Content`. The trace is written asynchronously: for a few\nseconds after `POST /v1/tools/{_id}/invoke` returns, a valid `invocationId` also answers\n`204`, and the header can then appear before its step records, with an empty `steps`\narray. Re-read before treating a run as unknown or as failed before its first step.\n`404` applies only to an unknown or inaccessible Tool `_id`. A malformed `executionId`\nreturns `422`. Sandbox Tools return `403`.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}},{"name":"executionId","in":"path","required":true,"description":"The execution id (24-character lowercase hex ObjectId) from the run-history list\nor from `invocationId` on the invoke response.","schema":{"type":"string","format":"objectId"}}],"responses":{"200":{"description":"Trace metadata for the invocation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolTraceMetadataResponse"}}}},"204":{"description":"Invocation not found, not visible under the caller's integration ACL, or not yet\nwritten — a run answers `204` for a few seconds after the invoke returns."},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"description":"Malformed `executionId`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## List child records under a Tool trace step

> Returns the child records a step produced from one parent record — the rows a lookup\
> step returned, or the records a one-to-many expansion generated. \`parentRecordId\` is\
> the \`recordId\` of the step entry on the trace-metadata response. Results are\
> cursor-paginated (\`next\`/\`prev\`); the input and output bubbles have no children.\
> \
> When the invocation is missing or not visible under the caller's integration ACL,\
> the endpoint returns \`204 No Content\`. \`404\` applies only to an unknown or inaccessible\
> Tool \`\_id\`. A malformed \`executionId\` returns \`422\`. Sandbox Tools return \`403\`.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ToolChildrenResponse":{"type":"object","description":"Paginated child records under a parent record in a Tool invocation trace.","required":["children","nextPageUrl","prevPageUrl"],"properties":{"children":{"type":"array","description":"Child step records; each carries the `parentRecordId` it was derived from.","items":{"$ref":"#/components/schemas/ToolInvocationStep"}},"nextPageUrl":{"type":["string","null"],"description":"URL to fetch the next page; null when there is no next page."},"prevPageUrl":{"type":["string","null"],"description":"URL to fetch the previous page; null when there is no previous page."}}},"ToolInvocationStep":{"type":"object","description":"One step record within a Tool invocation trace.","required":["status","timeTaken","groupId","recordId","time"],"properties":{"_expOrImpId":{"type":"string","description":"Identifier of the stage that produced this record: the export or import id for a\nstep in a branch, or the Tool's input/output bubble id (`<toolId>_input` /\n`<toolId>_output`). Absent for records not tied to a stage."},"status":{"type":"string","enum":["success","error","ignore"],"description":"Outcome of the step for this record."},"stage":{"type":"string","description":"Processing stage that recorded the outcome. Present when the platform recorded one; omitted otherwise."},"timeTaken":{"type":"integer","description":"Step processing time in milliseconds."},"groupId":{"type":"string","description":"Group the record belongs to within the step. Pass it to the log-data endpoint together with `recordId`."},"outputGroupId":{"type":"string","description":"Nested Tool output-bubble group id on ToolImport steps. Use as `groupId` when\nquerying `responseMapping` or `postResponseMapHook` log data for that ToolImport.\nAbsent for non-ToolImport steps."},"recordId":{"type":"string","description":"Identifier of this record within the run."},"parentRecordId":{"type":"string","description":"Record this one was derived from. Present on child records returned by the children endpoint."},"time":{"type":"string","format":"date-time","description":"Timestamp when the step ran."}},"additionalProperties":true},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"400-bad-request":{"description":"Bad request. The server could not understand the request because of malformed syntax or invalid parameters.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422-unprocessable-entity":{"description":"Unprocessable entity. The request was well-formed but was unable to be followed due to semantic errors.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/invocations/{executionId}/{_expOrImpId}/logs/{parentRecordId}/children":{"get":{"operationId":"listToolInvocationChildren","tags":["Tools"],"summary":"List child records under a Tool trace step","description":"Returns the child records a step produced from one parent record — the rows a lookup\nstep returned, or the records a one-to-many expansion generated. `parentRecordId` is\nthe `recordId` of the step entry on the trace-metadata response. Results are\ncursor-paginated (`next`/`prev`); the input and output bubbles have no children.\n\nWhen the invocation is missing or not visible under the caller's integration ACL,\nthe endpoint returns `204 No Content`. `404` applies only to an unknown or inaccessible\nTool `_id`. A malformed `executionId` returns `422`. Sandbox Tools return `403`.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}},{"name":"executionId","in":"path","required":true,"description":"The execution id (24-character lowercase hex ObjectId).","schema":{"type":"string","format":"objectId"}},{"name":"_expOrImpId","in":"path","required":true,"description":"Step identifier from the trace-metadata response: an export or import id, or the\nTool's input/output bubble id (`<toolId>_input` / `<toolId>_output`).","schema":{"type":"string"}},{"name":"parentRecordId","in":"path","required":true,"description":"The parent record id whose children are returned.","schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter children by processing outcome (single value or comma-separated list).","schema":{"type":"string"}},{"name":"pageSize","in":"query","description":"Number of entries per page. Larger values are clamped to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"sortOrder","in":"query","description":"Sort direction by step time.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"next","in":"query","description":"Cursor timestamp for the next page (from `nextPageUrl`).","schema":{"type":"string","format":"date-time"}},{"name":"prev","in":"query","description":"Cursor timestamp for the previous page (from `prevPageUrl`).","schema":{"type":"string","format":"date-time"}},{"name":"cursorRecordId","in":"query","description":"Tie-breaker record id, required when paging with `next`/`prev`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated child step records. `children[]` is empty when the parent produced none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolChildrenResponse"}}}},"204":{"description":"Invocation not found or not visible under the caller's integration ACL."},"400":{"$ref":"#/components/responses/400-bad-request"},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"$ref":"#/components/responses/422-unprocessable-entity"}}}}}}
```

## Query decoded log data for a Tool trace step

> Returns the payload a step processed during the invocation — what went in and what\
> came out — selected by \`\_expOrImpId\`, \`stage\`, \`groupId\`, and \`recordId\` from the\
> trace-metadata response. The input and output bubbles store their payload under the\
> \`schemaValidation\` stage; a lookup step stores the response it merged under\
> \`responseMapping\`; a step that makes an outbound API call, such as an HTTP lookup,\
> stores each request it sent under \`apiCall\`, one entry per attempt, so a retried\
> request shows each failed attempt before the one that succeeded. Large payloads are\
> not inlined — the entry carries an \`externalReference\` whose \`s3Key\` you pass to the\
> signed-URL endpoint.\
> \
> A selector that matches nothing returns \`200\` with an empty \`logs\` array — a stage\
> the step never ran for that record, a \`groupId\` from another step, and a step that\
> recorded no request all look the same. Payloads are written after the trace's step\
> records, so for a run that just finished an empty array can also mean the payload has\
> not landed yet; re-query before concluding nothing was stored.\
> \
> For nested Tools, set \`stage\` to \`tool\` with the nested ToolImport id to fetch the\
> composed input/output payload.\
> \
> When the invocation is missing or not visible under the caller's integration ACL,\
> the endpoint returns \`204 No Content\`. \`404\` applies only to an unknown or inaccessible\
> Tool \`\_id\`. A malformed \`executionId\` returns \`422\`, and a body missing any of the four\
> selector keys returns \`400\`. Sandbox Tools return \`403\`.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ToolLogDataRequest":{"type":"object","description":"Selector for one stored payload within a Tool invocation. Copy `_expOrImpId`,\n`groupId`, and `recordId` from a step on the trace-metadata response and name the\n`stage` whose payload you want.","required":["_expOrImpId","stage","groupId","recordId"],"properties":{"_expOrImpId":{"type":"string","description":"Stage identifier from the trace: an export or import id, the Tool's input/output\nbubble id (`<toolId>_input` / `<toolId>_output`), or a nested ToolImport id when\n`stage` is `tool`."},"stage":{"type":"string","description":"Processing stage whose payload to fetch. The input and output bubbles store their\npayload under `schemaValidation`; a lookup step stores the merged response under\n`responseMapping`; a step that makes an outbound API call, such as an HTTP lookup,\nstores each request it sent under `apiCall`; use `tool` for a nested ToolImport's\ncomposed payload. A stage the step never ran for this record returns an empty\n`logs` array, the same as a record that stored nothing."},"groupId":{"type":"string","description":"Group identifier of the step record."},"recordId":{"type":"string","description":"Identifier of the record to fetch."}}},"ToolLogDataResponse":{"type":"object","description":"Decoded log payload for a step, plus any error metadata collected while resolving it.","required":["logs","errors"],"properties":{"logs":{"type":"array","description":"Payload entries matching the selector — empty when nothing was stored for it.\nSmall payloads are inlined under `log`; a large payload is replaced by an\n`externalReference[]` whose `source.s3Key` you pass to\n`GET /v1/tools/{_id}/invocations/{executionId}/logs/signedURL` to download it.\nA bubble or mapping stage returns one entry per record; the `apiCall` stage\nreturns one entry per request the step sent, in order, so a retried request shows\neach failed attempt before the one that succeeded.","items":{"type":"object","properties":{"log":{"type":"object","description":"Inlined payload for the selected record and stage. Record-level stages such\nas `schemaValidation` and `responseMapping` carry the timestamp, selector echo\nand `input` / `output` / `errors`; the `apiCall` stage carries the request sent\nand the response received instead.","properties":{"timestamp":{"type":"string","format":"date-time","description":"Timestamp when the stage processed the record."},"groupId":{"type":"string","description":"Group the record belongs to, echoing the selector."},"recordId":{"type":"string","description":"Record identifier, echoing the selector."},"stage":{"type":"string","description":"Stage that produced the payload, echoing the selector."},"toolStage":{"type":["string","null"],"description":"Which Tool bubble captured the payload — `input` or `output`. Null for\nexport and import steps."},"input":{"type":"object","additionalProperties":true,"description":"Data the stage received. For the input and output bubbles this is\n`{record}`; for a lookup step's `responseMapping` stage it is the lookup\nresponse envelope (`statusCode`, `data`, `errors`)."},"output":{"type":"object","additionalProperties":true,"description":"Data the stage produced, as `{record}`."},"errors":{"type":"array","description":"Errors the stage raised for this record; empty on success.","items":{"type":"object","additionalProperties":true}},"apiCallTraceKeyId":{"type":"string","description":"Identifier of one request attempt. Present when `stage` is `apiCall`."},"apiCallType":{"type":"string","description":"Kind of step that sent the request, such as a lookup export. Present when `stage` is `apiCall`."},"status":{"type":"string","enum":["success","failed"],"description":"Outcome of this request attempt. Present when `stage` is `apiCall`."},"request":{"type":"object","description":"The request the step sent on this attempt — `method`, `uri` and `headers`.\nCredential headers are masked. Present when `stage` is `apiCall`.","properties":{"method":{"type":"string","description":"HTTP method of the attempt."},"uri":{"type":"string","description":"Full URL the attempt called."},"headers":{"type":"object","additionalProperties":true,"description":"Request headers as sent, with credential values masked."}},"additionalProperties":true},"response":{"type":"object","description":"The response this attempt received — `statusCode`, `headers`, and the raw\n`body` as a string when the endpoint sent one. Present when `stage` is\n`apiCall`.","properties":{"statusCode":{"type":"integer","description":"HTTP status the endpoint returned."},"headers":{"type":"object","additionalProperties":true,"description":"Response headers as received."},"body":{"type":"string","description":"Raw response body; JSON arrives as a serialized string."}},"additionalProperties":true}},"if":{"properties":{"stage":{"const":"apiCall"}},"required":["stage"]},"then":{"required":["status","request","response"]},"additionalProperties":true},"externalReference":{"type":"array","description":"References to externally stored payload content, present instead of `log` for large payloads.","items":{"type":"object","properties":{"source":{"type":"object","description":"Location of the externally stored content.","properties":{"s3Key":{"type":"string","description":"Relative key to pass to the signed-URL endpoint."}},"additionalProperties":true}},"additionalProperties":true}}},"additionalProperties":true}},"errors":{"type":"array","description":"Error metadata collected while resolving the payload; empty when the lookup succeeded.","items":{"type":"object","additionalProperties":true}}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422-unprocessable-entity":{"description":"Unprocessable entity. The request was well-formed but was unable to be followed due to semantic errors.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/invocations/{executionId}/logs/data/query":{"post":{"operationId":"queryToolInvocationLogData","tags":["Tools"],"summary":"Query decoded log data for a Tool trace step","description":"Returns the payload a step processed during the invocation — what went in and what\ncame out — selected by `_expOrImpId`, `stage`, `groupId`, and `recordId` from the\ntrace-metadata response. The input and output bubbles store their payload under the\n`schemaValidation` stage; a lookup step stores the response it merged under\n`responseMapping`; a step that makes an outbound API call, such as an HTTP lookup,\nstores each request it sent under `apiCall`, one entry per attempt, so a retried\nrequest shows each failed attempt before the one that succeeded. Large payloads are\nnot inlined — the entry carries an `externalReference` whose `s3Key` you pass to the\nsigned-URL endpoint.\n\nA selector that matches nothing returns `200` with an empty `logs` array — a stage\nthe step never ran for that record, a `groupId` from another step, and a step that\nrecorded no request all look the same. Payloads are written after the trace's step\nrecords, so for a run that just finished an empty array can also mean the payload has\nnot landed yet; re-query before concluding nothing was stored.\n\nFor nested Tools, set `stage` to `tool` with the nested ToolImport id to fetch the\ncomposed input/output payload.\n\nWhen the invocation is missing or not visible under the caller's integration ACL,\nthe endpoint returns `204 No Content`. `404` applies only to an unknown or inaccessible\nTool `_id`. A malformed `executionId` returns `422`, and a body missing any of the four\nselector keys returns `400`. Sandbox Tools return `403`.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}},{"name":"executionId","in":"path","required":true,"description":"The execution id (24-character lowercase hex ObjectId).","schema":{"type":"string","format":"objectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolLogDataRequest"}}}},"responses":{"200":{"description":"Decoded log payload and any error metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolLogDataResponse"}}}},"204":{"description":"Invocation not found or not visible under the caller's integration ACL."},"400":{"description":"Request body is missing one or more selector keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"$ref":"#/components/responses/422-unprocessable-entity"}}}}}}
```

## Get a signed URL for a stored Tool log payload

> Returns a short-lived signed URL to download a stored log payload that the log-data\
> endpoint returned as an external reference (\`externalReference\[].source.s3Key\`).\
> The key is resolved inside the invocation's own storage prefix, so a key from another\
> invocation cannot be read through this route. The URL is minted without checking that\
> the object exists — downloading a key that was never stored fails at the storage layer.\
> \
> When the invocation is missing or not visible under the caller's integration ACL,\
> the endpoint returns \`204 No Content\`. Sandbox Tools return \`403\`.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ToolSignedUrlResponse":{"type":"object","description":"A short-lived signed URL for downloading a stored Tool log payload object.","required":["signedURL"],"properties":{"signedURL":{"type":"string","description":"Time-limited signed URL to download the referenced log object."}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422-unprocessable-entity":{"description":"Unprocessable entity. The request was well-formed but was unable to be followed due to semantic errors.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/invocations/{executionId}/logs/signedURL":{"get":{"operationId":"getToolInvocationLogSignedUrl","tags":["Tools"],"summary":"Get a signed URL for a stored Tool log payload","description":"Returns a short-lived signed URL to download a stored log payload that the log-data\nendpoint returned as an external reference (`externalReference[].source.s3Key`).\nThe key is resolved inside the invocation's own storage prefix, so a key from another\ninvocation cannot be read through this route. The URL is minted without checking that\nthe object exists — downloading a key that was never stored fails at the storage layer.\n\nWhen the invocation is missing or not visible under the caller's integration ACL,\nthe endpoint returns `204 No Content`. Sandbox Tools return `403`.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}},{"name":"executionId","in":"path","required":true,"description":"The execution id (24-character lowercase hex ObjectId).","schema":{"type":"string","format":"objectId"}},{"name":"s3Key","in":"query","required":true,"description":"Relative key of the stored log object, copied from `externalReference[].source.s3Key`\non the log-data response. Omitting it returns `400` `missing_required_parameter`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Signed URL for the requested object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolSignedUrlResponse"}}}},"204":{"description":"Invocation not found or not visible under the caller's integration ACL."},"400":{"description":"Missing `s3Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"$ref":"#/components/responses/422-unprocessable-entity"}}}}}}
```

## List captured request logs for a Tool

> Lists the request/response envelopes the platform has stored for this Tool as a whole\
> — the Tool-level counterpart of \`GET /v1/apis/{\_id}/logs\`. Entries are file-backed with\
> server-controlled retention; an empty \`requests\[]\` means nothing is currently stored.\
> Standalone invokes through \`POST /v1/tools/{\_id}/invoke\` do not write here — use\
> \`GET /v1/tools/{\_id}/invocations\` for run history.\
> \
> List entries are summaries (\`key\`, \`time\`, \`method\`, \`statusCode\`); read the payload\
> with \`GET /v1/tools/{\_id}/logs/{key}\`. Without a time window the newest entries are\
> returned; \`time\_lte\` and \`time\_gt\` are epoch milliseconds and \`time\_lte\` requires\
> \`time\_gt\`.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ToolLogsResponse":{"type":"object","description":"Envelope returned by `GET /v1/tools/{_id}/logs`. `requests[]` is empty when no\ncaptured request is currently stored for the Tool — nothing has been captured, or the\nretention window has elapsed.","required":["requests"],"properties":{"requests":{"type":"array","description":"Captured request summaries, newest first.","items":{"$ref":"#/components/schemas/ToolLogEntry"}},"nextPageURL":{"type":"string","description":"Relative URL of the next page, carrying the narrowed time window and a\ncontinuation token. Present only while more of the storage window remains to be\nsearched — it can appear alongside an empty `requests[]`. Follow it as returned;\ndo not construct it."}}},"ToolLogEntry":{"type":"object","description":"Summary row in the `GET /v1/tools/{_id}/logs` listing — enough to identify a captured\nrequest (when it ran, how it ended) and pick it for the detail fetch. Payloads are only\non `GET /v1/tools/{_id}/logs/{key}`.","required":["key","time","method","statusCode"],"properties":{"key":{"type":"string","description":"Opaque log key. Pass it to `GET /v1/tools/{_id}/logs/{key}` to read the full\nenvelope, or in the `keys` body of `DELETE /v1/tools/{_id}/logs` to remove it.\nStructure is `<seq>-<id>-<status>-<method>`; use it exactly as returned."},"time":{"type":"integer","format":"int64","description":"Epoch milliseconds when the request was handled."},"method":{"type":"string","description":"HTTP method of the captured request."},"statusCode":{"type":"string","description":"Status code the invocation ended with, as a string (`\"200\"`). The detail endpoint\nexposes the same value as an integer under `response.statusCode`."}}},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/logs":{"get":{"operationId":"listToolLogs","tags":["Tools"],"summary":"List captured request logs for a Tool","description":"Lists the request/response envelopes the platform has stored for this Tool as a whole\n— the Tool-level counterpart of `GET /v1/apis/{_id}/logs`. Entries are file-backed with\nserver-controlled retention; an empty `requests[]` means nothing is currently stored.\nStandalone invokes through `POST /v1/tools/{_id}/invoke` do not write here — use\n`GET /v1/tools/{_id}/invocations` for run history.\n\nList entries are summaries (`key`, `time`, `method`, `statusCode`); read the payload\nwith `GET /v1/tools/{_id}/logs/{key}`. Without a time window the newest entries are\nreturned; `time_lte` and `time_gt` are epoch milliseconds and `time_lte` requires\n`time_gt`.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}},{"name":"time_gt","in":"query","required":false,"description":"Lower bound of the time window (exclusive), as epoch milliseconds.","schema":{"type":"integer","format":"int64"}},{"name":"time_lte","in":"query","required":false,"description":"Upper bound of the time window (inclusive), as epoch milliseconds. Requires\n`time_gt`; an ISO timestamp or a missing `time_gt` returns `422`.","schema":{"type":"integer","format":"int64"}},{"name":"method","in":"query","required":false,"description":"Only return entries with these HTTP methods. Repeat the parameter for several values.","style":"form","explode":true,"schema":{"type":"array","items":{"type":"string"}}},{"name":"statusCode","in":"query","required":false,"description":"Only return entries with these status codes. Repeat the parameter for several values.","style":"form","explode":true,"schema":{"type":"array","items":{"type":"string"}}},{"name":"nextPageToken","in":"query","required":false,"description":"Continuation token. Follow the `nextPageURL` of the previous page instead of constructing it.","schema":{"type":"string"}}],"responses":{"200":{"description":"Log envelope. `requests[]` is empty when nothing is stored for the Tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolLogsResponse"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"description":"Invalid time window — `time_lte` without `time_gt`, or a value that is not epoch milliseconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete captured request logs for a Tool

> Deletes stored request/response envelopes by the \`key\` values listed by\
> \`GET /v1/tools/{\_id}/logs\`, between 1 and 1000 per call. The response separates the\
> keys that were removed from the keys that failed, with the storage failure reason. A\
> key that no longer exists is reported as deleted.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/logs":{"delete":{"operationId":"deleteToolLogs","tags":["Tools"],"summary":"Delete captured request logs for a Tool","description":"Deletes stored request/response envelopes by the `key` values listed by\n`GET /v1/tools/{_id}/logs`, between 1 and 1000 per call. The response separates the\nkeys that were removed from the keys that failed, with the storage failure reason. A\nkey that no longer exists is reported as deleted.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Keys of the captured requests to delete.","required":["keys"],"properties":{"keys":{"type":"array","description":"Keys from the `GET /v1/tools/{_id}/logs` listing. An empty or missing array returns `400`.","minItems":1,"maxItems":1000,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Deletion outcome.","content":{"application/json":{"schema":{"type":"object","description":"Deletion result envelope.","required":["deleted","errors"],"properties":{"deleted":{"type":"array","description":"Keys that were removed.","items":{"type":"string"}},"errors":{"type":"array","description":"Keys that could not be removed, with the failure reason.","items":{"type":"object","required":["key","error"],"properties":{"key":{"type":"string","description":"Key that failed to delete."},"error":{"type":"string","description":"Storage error code for the failure."}}}}}}}}},"400":{"description":"Missing or empty `keys`, or more than 1000 keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"$ref":"#/components/responses/404-not-found"}}}}}}
```

## Get one captured request log by key

> Returns the full stored request/response envelope for one captured Tool request,\
> looked up by a \`key\` from \`GET /v1/tools/{\_id}/logs\`. The key must match exactly; a key\
> that has expired from retention or was never stored returns \`404\` \`file\_not\_found\`.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"ToolLogDetail":{"type":"object","description":"Full decoded request/response envelope for one captured Tool request, returned by\n`GET /v1/tools/{_id}/logs/{key}`. The stored envelope is returned as-is with the\n`key`, `id`, and `time` decoded from the log key added to it.","required":["key","id","time"],"properties":{"key":{"type":"string","description":"Echoes the `key` path parameter."},"id":{"type":"string","description":"Storage id of this log record — the second segment of `key`."},"time":{"type":"integer","format":"int64","description":"Epoch milliseconds when the request was handled, decoded from `key`."},"request":{"type":"object","description":"The inbound request the Tool received, as the runtime stored it.","additionalProperties":true},"response":{"type":"object","description":"The response the Tool produced, as the runtime stored it.","additionalProperties":true}},"additionalProperties":true},"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/logs/{key}":{"get":{"operationId":"getToolLogByKey","tags":["Tools"],"summary":"Get one captured request log by key","description":"Returns the full stored request/response envelope for one captured Tool request,\nlooked up by a `key` from `GET /v1/tools/{_id}/logs`. The key must match exactly; a key\nthat has expired from retention or was never stored returns `404` `file_not_found`.","parameters":[{"name":"_id","in":"path","required":true,"description":"The Tool id.","schema":{"type":"string","format":"objectId"}},{"name":"key","in":"path","required":true,"description":"Log key from the `GET /v1/tools/{_id}/logs` listing, used exactly as returned.","schema":{"type":"string"}}],"responses":{"200":{"description":"The stored envelope with `key`, `id`, and `time` added.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolLogDetail"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"description":"Unknown Tool, or no stored log for this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Run a tool in test mode

> Synchronously executes a tool in test mode and returns the run metadata\
> together with the resulting flow job and its child jobs. Use this to\
> validate a tool's routing and step configuration before referencing it\
> from a flow, API, agent, or MCP server.\
> \
> The request body is optional. When supplied, wrap the test input in an\
> \`input\` key (\`{"input": {...}}\`) matching the tool's input contract. The\
> run does not read the tool's saved \`input.mockInput\` — without a wrapped\
> \`input\` the tool executes against an empty input record. The \`metadata\`\
> object in the response maps each step id to the ordered list of stage\
> names that ran for that step; use those ids with\
> \`GET /v1/tools/{\_id}/test/run/{runId}/{\_stepId}\` to inspect stage-by-stage\
> results. The run id for follow-up calls is the \`flowJob.\_id\` value.\
> \
> A test run is not free of outside calls: a lookup export with no saved \`mockOutput\` calls\
> its source live, and the run then saves that response on the export as its \`mockOutput\`\
> (the export's \`lastModified\` changes). Every later test run that uses the export, from any\
> Tool or API and whatever its input, answers that lookup from the saved \`mockOutput\` instead\
> of calling the source: the lookup's \`request\` stage shows the request rendered from the new\
> input, and its \`response\` stage holds the saved records. Remove \`mockOutput\` from the export\
> to call the source again. \`POST /v1/tools/{\_id}/invoke\` neither reads nor writes it. Imports\
> never send; each returns its saved \`mockResponse\`, or a placeholder response when none is\
> saved (see \`mockResponse\` on imports). A test run is keyed by the request's \`triggeredAt\`:\
> one sent with a \`triggeredAt\` that an earlier test run of the tool already used is refused\
> with 400 \`test\_run\_not\_allowed\` ("Cannot run the test tool when it is already in the running\
> state."), even after that run has finished. Runs with different \`triggeredAt\` values run at\
> the same time, each returning its own result.\
> \
> Test runs are a separate, short-lived history from normal runs — capture\
> any follow-up step or log details soon after the run completes.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422-unprocessable-entity":{"description":"Unprocessable entity. The request was well-formed but was unable to be followed due to semantic errors.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/test/run":{"post":{"summary":"Run a tool in test mode","description":"Synchronously executes a tool in test mode and returns the run metadata\ntogether with the resulting flow job and its child jobs. Use this to\nvalidate a tool's routing and step configuration before referencing it\nfrom a flow, API, agent, or MCP server.\n\nThe request body is optional. When supplied, wrap the test input in an\n`input` key (`{\"input\": {...}}`) matching the tool's input contract. The\nrun does not read the tool's saved `input.mockInput` — without a wrapped\n`input` the tool executes against an empty input record. The `metadata`\nobject in the response maps each step id to the ordered list of stage\nnames that ran for that step; use those ids with\n`GET /v1/tools/{_id}/test/run/{runId}/{_stepId}` to inspect stage-by-stage\nresults. The run id for follow-up calls is the `flowJob._id` value.\n\nA test run is not free of outside calls: a lookup export with no saved `mockOutput` calls\nits source live, and the run then saves that response on the export as its `mockOutput`\n(the export's `lastModified` changes). Every later test run that uses the export, from any\nTool or API and whatever its input, answers that lookup from the saved `mockOutput` instead\nof calling the source: the lookup's `request` stage shows the request rendered from the new\ninput, and its `response` stage holds the saved records. Remove `mockOutput` from the export\nto call the source again. `POST /v1/tools/{_id}/invoke` neither reads nor writes it. Imports\nnever send; each returns its saved `mockResponse`, or a placeholder response when none is\nsaved (see `mockResponse` on imports). A test run is keyed by the request's `triggeredAt`:\none sent with a `triggeredAt` that an earlier test run of the tool already used is refused\nwith 400 `test_run_not_allowed` (\"Cannot run the test tool when it is already in the running\nstate.\"), even after that run has finished. Runs with different `triggeredAt` values run at\nthe same time, each returning its own result.\n\nTest runs are a separate, short-lived history from normal runs — capture\nany follow-up step or log details soon after the run completes.","operationId":"testRunTool","tags":["Tools"],"parameters":[{"name":"_id","in":"path","required":true,"description":"The unique identifier of the tool to test.","schema":{"type":"string","format":"objectId"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"Optional test input for the run. Send an empty object or omit\nthe body entirely to run with no input.","properties":{"input":{"type":"object","description":"The test input record the tool runs against. Shape depends\non the tool's input contract. The run does not fall back to\nthe tool's saved `input.mockInput` — when this key is\nabsent, the input step receives an empty record.","additionalProperties":true},"triggeredAt":{"type":"string","format":"date-time","description":"Identifies the test run; send the current time. A value an earlier\ntest run of the tool already used is refused with 400\n`test_run_not_allowed`."}},"additionalProperties":true}}}},"responses":{"200":{"description":"The tool ran. Returns the per-step stage metadata, the flow job that\nexecuted the tool, and the child jobs for each step.","content":{"application/json":{"schema":{"type":"object","description":"Test-run result envelope.","properties":{"metadata":{"type":"object","description":"Maps each step id to the ordered list of stage names that\nran for that step (e.g. `request`, `parse`, `router`,\n`input`). Empty array means the step ran no stages.","additionalProperties":{"type":"array","items":{"type":"string"}}},"flowJob":{"type":"object","description":"The flow job record produced by the test run.","additionalProperties":true},"childJobs":{"type":"array","description":"Child job records, one per executed step.","items":{"type":"object","additionalProperties":true}}},"required":["metadata","flowJob","childJobs"]}}}},"400":{"description":"The run was refused before any step ran. A Tool with no steps fails with\n`tool_empty_router`, and one whose branches don't all merge back with\n`tool_dangling_branch`; re-running the same document fails the same way. A\n`triggeredAt` an earlier test run already used fails with `test_run_not_allowed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"},"422":{"$ref":"#/components/responses/422-unprocessable-entity"}}}}}}
```

## Get a tool test-run step result

> Returns the stage-by-stage result of a single step from a prior tool test\
> run. Each entry in \`stages\[]\` describes one stage (e.g. \`request\`,\
> \`parse\`, \`router\`, \`input\`) with its \`input\`, \`output\`, and any \`errors\`.\
> \
> The \`runId\` is the \`flowJob.\_id\` returned by\
> \`POST /v1/tools/{\_id}/test/run\`. The \`{\_stepId}\` is one of the step ids\
> found in that run's \`metadata\` map. Test-run history is separate from\
> normal run history and is short-lived — fetch step results soon after the\
> run completes.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"responses":{"400-bad-request":{"description":"Bad request. The server could not understand the request because of malformed syntax or invalid parameters.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"404-not-found":{"description":"Not found. The requested resource does not exist or is not visible to the caller.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}}},"paths":{"/v1/tools/{_id}/test/run/{runId}/{_stepId}":{"get":{"summary":"Get a tool test-run step result","description":"Returns the stage-by-stage result of a single step from a prior tool test\nrun. Each entry in `stages[]` describes one stage (e.g. `request`,\n`parse`, `router`, `input`) with its `input`, `output`, and any `errors`.\n\nThe `runId` is the `flowJob._id` returned by\n`POST /v1/tools/{_id}/test/run`. The `{_stepId}` is one of the step ids\nfound in that run's `metadata` map. Test-run history is separate from\nnormal run history and is short-lived — fetch step results soon after the\nrun completes.","operationId":"getToolTestRunStep","tags":["Tools"],"parameters":[{"name":"_id","in":"path","required":true,"description":"The unique identifier of the tool.","schema":{"type":"string","format":"objectId"}},{"name":"runId","in":"path","required":true,"description":"Test run id from the `POST /v1/tools/{_id}/test/run` response\n(`flowJob._id`). Distinct from normal flow-run Job ids.","schema":{"type":"string"}},{"name":"_stepId","in":"path","required":true,"description":"Id of the step whose stage results you want. Find step ids in the\ntest-run `metadata` map.","schema":{"type":"string"}}],"responses":{"200":{"description":"Stage-by-stage result for the step. `stages[]` carries per-stage\n`input`, `output`, and `errors`; the top-level `errors` array\naggregates step-level errors.","content":{"application/json":{"schema":{"type":"object","description":"Step result envelope.","properties":{"stages":{"type":"array","description":"Ordered list of stages that ran for the step.","items":{"type":"object","description":"One stage of the step.","properties":{"name":{"type":"string","description":"Stage name (e.g. `request`, `parse`, `router`, `input`)."},"errors":{"description":"Stage errors, or `null` when the stage had none.","type":["array","null"],"items":{"type":"object","additionalProperties":true}},"output":{"description":"Stage output records, or `null` when the stage produced none.","type":["array","null"],"items":{"type":"object","additionalProperties":true}},"input":{"description":"Stage input records, or `null` when the stage consumed none.","type":["array","null"],"items":{"type":"object","additionalProperties":true}}}}},"errors":{"type":"array","description":"Step-level errors aggregated across stages.","items":{"type":"object","additionalProperties":true}}},"required":["stages","errors"]}}}},"400":{"$ref":"#/components/responses/400-bad-request"},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"$ref":"#/components/responses/404-not-found"}}}}}}
```

## Get request/response logs for a tool test-run step

> Returns the outbound HTTP request/response log pairs captured during a\
> specific step of a tool test run. Only steps that issued outbound HTTP\
> calls (exports, imports, lookups) produce entries — routers, filters, and\
> other in-process stages return \`404\` with code \`req\_res\_logs\_not\_found\`\
> by design.\
> \
> The \`runId\` is the \`flowJob.\_id\` returned by\
> \`POST /v1/tools/{\_id}/test/run\`; the \`{\_stepId}\` is the export or import\
> id of the step you want logs for, found in the test-run \`metadata\` map.\
> \
> Response entries may carry base64-encoded JSON in \`request.body\` and\
> \`response.body\` — decode string bodies before parsing. Test runs are\
> short-lived ephemeral state, so capture logs soon after the run completes.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}}},"paths":{"/v1/tools/{_id}/test/run/{runId}/{_stepId}/logs/requestAndResponse":{"get":{"summary":"Get request/response logs for a tool test-run step","description":"Returns the outbound HTTP request/response log pairs captured during a\nspecific step of a tool test run. Only steps that issued outbound HTTP\ncalls (exports, imports, lookups) produce entries — routers, filters, and\nother in-process stages return `404` with code `req_res_logs_not_found`\nby design.\n\nThe `runId` is the `flowJob._id` returned by\n`POST /v1/tools/{_id}/test/run`; the `{_stepId}` is the export or import\nid of the step you want logs for, found in the test-run `metadata` map.\n\nResponse entries may carry base64-encoded JSON in `request.body` and\n`response.body` — decode string bodies before parsing. Test runs are\nshort-lived ephemeral state, so capture logs soon after the run completes.","operationId":"getToolTestRunStepLogs","tags":["Tools"],"parameters":[{"name":"_id","in":"path","required":true,"description":"The unique identifier of the tool.","schema":{"type":"string","format":"objectId"}},{"name":"runId","in":"path","required":true,"description":"Test run id from the `POST /v1/tools/{_id}/test/run` response\n(`flowJob._id`). Distinct from normal flow-run Job ids.","schema":{"type":"string"}},{"name":"_stepId","in":"path","required":true,"description":"Export or import id of the step whose logs you want. Non-HTTP stages\n(routers, filters) 404 with `req_res_logs_not_found`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Array of request/response log pairs captured during the step.\n`request.body` / `response.body` may be base64-encoded JSON.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"One request/response pair captured by the test engine.","properties":{"request":{"type":"object","description":"Outbound HTTP request envelope.","properties":{"method":{"type":"string","description":"HTTP method (e.g. `GET`, `POST`)."},"url":{"type":"string","description":"Fully-resolved request URL."},"headers":{"type":"object","description":"Request headers; sensitive values redacted server-side.","additionalProperties":true},"body":{"description":"Request body. Often **base64-encoded JSON** — decode\nbefore parsing. Empty for bodyless methods.","type":["string","null"]}}},"response":{"type":"object","description":"Remote response envelope.","properties":{"statusCode":{"type":"integer","description":"HTTP status code returned by the remote."},"headers":{"type":"object","description":"Response headers.","additionalProperties":true},"body":{"description":"Response body. Often **base64-encoded JSON** — decode\nbefore parsing.","type":["string","null"]}}}}}}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"404":{"description":"Either the run/step id didn't resolve, or the step produced no\nrequest/response logs (non-HTTP stage — router, filter, etc.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## List captured debug requests for a tool step

> Lists the debug request records captured for a specific step of a tool.\
> These are the raw request/response payloads the step processed, retained\
> for troubleshooting while debug capture is active for the step — a future\
> \`debugUntil\` on the step's export or import document, or \`input.debugUntil\`\
> / \`output.debugUntil\` on the tool for the Tool Input and Tool Output\
> stages. Capture runs in every execution context (a standalone invoke at\
> any \`x-log-mode\`, an AI agent or MCP caller, a flow or API that calls the\
> tool, a parent tool); test runs write nothing here. Use the \`key\` of an\
> entry with \`GET /v1/tools/{\_id}/{\_stepId}/requests/{key}\` to fetch the\
> full detail of a single captured request.\
> \
> HTTP and NetSuite steps record one entry per request they send. The\
> \`tool\_input\` stage records one entry per incoming record (\`200\` when the\
> record passes the input schema, \`422\` with the validation errors when it\
> does not); the \`tool\_output\` stage records one entry per output\
> validation. Routers, filters and scripts never record.\
> \
> An account owner or administrator lists every entry. A shared user sees\
> only entries from runs invoked under integrations they can access\
> (standalone invokes are recorded under the tool's own integration).\
> \
> When more records match than fit in one page, \`nextPageURL\` returns the\
> relative URL for the next page.\
> \
> \*\*Important:\*\* Time parameters (\`time\_lte\`, \`time\_gt\`) must be epoch\
> milliseconds (integers), not ISO 8601 strings. When \`time\_lte\` is\
> provided, \`time\_gt\` is also required.\
> \
> Requires the \`manage:tool:logs\` permission. Returns \`{requests: \[]}\` when\
> the step has captured no debug requests.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/{_stepId}/requests":{"get":{"summary":"List captured debug requests for a tool step","description":"Lists the debug request records captured for a specific step of a tool.\nThese are the raw request/response payloads the step processed, retained\nfor troubleshooting while debug capture is active for the step — a future\n`debugUntil` on the step's export or import document, or `input.debugUntil`\n/ `output.debugUntil` on the tool for the Tool Input and Tool Output\nstages. Capture runs in every execution context (a standalone invoke at\nany `x-log-mode`, an AI agent or MCP caller, a flow or API that calls the\ntool, a parent tool); test runs write nothing here. Use the `key` of an\nentry with `GET /v1/tools/{_id}/{_stepId}/requests/{key}` to fetch the\nfull detail of a single captured request.\n\nHTTP and NetSuite steps record one entry per request they send. The\n`tool_input` stage records one entry per incoming record (`200` when the\nrecord passes the input schema, `422` with the validation errors when it\ndoes not); the `tool_output` stage records one entry per output\nvalidation. Routers, filters and scripts never record.\n\nAn account owner or administrator lists every entry. A shared user sees\nonly entries from runs invoked under integrations they can access\n(standalone invokes are recorded under the tool's own integration).\n\nWhen more records match than fit in one page, `nextPageURL` returns the\nrelative URL for the next page.\n\n**Important:** Time parameters (`time_lte`, `time_gt`) must be epoch\nmilliseconds (integers), not ISO 8601 strings. When `time_lte` is\nprovided, `time_gt` is also required.\n\nRequires the `manage:tool:logs` permission. Returns `{requests: []}` when\nthe step has captured no debug requests.","operationId":"listToolStepRequests","tags":["Tools"],"parameters":[{"name":"_id","in":"path","required":true,"description":"The unique identifier of the tool.","schema":{"type":"string","format":"objectId"}},{"name":"_stepId","in":"path","required":true,"description":"The step whose captured logs to list: the ObjectId of an export or\nimport step defined directly in this tool's routers, or one of the\ntwo virtual stage ids `tool_input` (the record entering the tool,\nvalidated against the input schema) and `tool_output` (the\npost-mapped record, validated against the output schema). Steps\nnested inside a child tool are addressed on that child tool's id;\nan ObjectId that is not a direct step of this tool returns `404`\n`invalid_ref`. Any other value — including reserved path segments\nsuch as `invocations`, `invoke`, and `test` — returns `400`\n`invalid_ref`.","schema":{"type":"string"}},{"name":"time_gt","in":"query","required":false,"description":"Lower bound of the time window (exclusive), as epoch milliseconds.\nRequired when `time_lte` is provided.","schema":{"type":"integer","format":"int64"}},{"name":"time_lte","in":"query","required":false,"description":"Upper bound of the time window (inclusive), as epoch milliseconds.\nDefaults to now if omitted. When provided, `time_gt` is also required.","schema":{"type":"integer","format":"int64"}},{"name":"method","in":"query","required":false,"description":"Only return records with these HTTP methods. Repeat the parameter to\nfilter on multiple values.","style":"form","explode":true,"schema":{"type":"array","items":{"type":"string"}}},{"name":"statusCode","in":"query","required":false,"description":"Only return records with these response status codes. Repeat the\nparameter to filter on multiple values.","style":"form","explode":true,"schema":{"type":"array","items":{"type":"string"}}},{"name":"stage","in":"query","required":false,"description":"Only return records for these processing stages. Repeat the parameter\nto filter on multiple values.","style":"form","explode":true,"schema":{"type":"array","items":{"type":"string"}}},{"name":"nextPageToken","in":"query","required":false,"description":"Continuation token for pagination. Do not construct this manually —\nfollow the `nextPageURL` returned by the previous page instead.","schema":{"type":"string"}}],"responses":{"200":{"description":"Captured debug request records for the step. Empty `requests` array\nwhen none were captured.","content":{"application/json":{"schema":{"type":"object","description":"Debug request list envelope.","properties":{"requests":{"type":"array","description":"Captured debug request records.","items":{"type":"object","description":"Summary of one captured debug request.","properties":{"key":{"type":"string","description":"Identifier of the captured record; pass it to\n`GET /v1/tools/{_id}/{_stepId}/requests/{key}`."},"time":{"type":"integer","description":"Capture time as epoch milliseconds."},"method":{"type":"string","description":"HTTP method of the captured request."},"statusCode":{"type":"string","description":"Response status code of the captured request."},"stage":{"type":"string","description":"Processing stage the record was captured in, such as\n`lookup` for a lookup step's request. Present on\nrecords written by HTTP and NetSuite steps; absent on\n`tool_input` and `tool_output` records."}},"additionalProperties":true}},"nextPageURL":{"type":"string","description":"Relative URL of the next page. Present only when more\nrecords match than were returned."}},"required":["requests"]}}}},"400":{"description":"`_stepId` is neither an ObjectId nor `tool_input` / `tool_output`.\nError code: `invalid_ref`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"description":"The tool does not exist, or `_stepId` is an ObjectId that is not a\nstep defined directly in this tool. Error code: `invalid_ref`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid time filter parameters. Error code: `invalid_or_missing_field`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete captured debug requests for a tool step

> Deletes captured debug request records for a specific step of a tool, by\
> their \`key\` values from \`GET /v1/tools/{\_id}/{\_stepId}/requests\`. Between\
> 1 and 1000 keys can be deleted per call; the deletion is synchronous and\
> scoped to this step only.\
> \
> The response lists the keys that were deleted and, separately, any keys\
> that failed to delete along with the failure reason. A key that no longer\
> exists is reported under \`deleted\`, not \`errors\`.\
> \
> Requires the \`manage:tool:logs:deletion\` permission. A shared user's\
> delete removes only records from runs invoked under integrations they can\
> access.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/{_stepId}/requests":{"delete":{"summary":"Delete captured debug requests for a tool step","description":"Deletes captured debug request records for a specific step of a tool, by\ntheir `key` values from `GET /v1/tools/{_id}/{_stepId}/requests`. Between\n1 and 1000 keys can be deleted per call; the deletion is synchronous and\nscoped to this step only.\n\nThe response lists the keys that were deleted and, separately, any keys\nthat failed to delete along with the failure reason. A key that no longer\nexists is reported under `deleted`, not `errors`.\n\nRequires the `manage:tool:logs:deletion` permission. A shared user's\ndelete removes only records from runs invoked under integrations they can\naccess.","operationId":"deleteToolStepRequests","tags":["Tools"],"parameters":[{"name":"_id","in":"path","required":true,"description":"The unique identifier of the tool.","schema":{"type":"string","format":"objectId"}},{"name":"_stepId","in":"path","required":true,"description":"The step whose captured logs to delete: the ObjectId of an export or\nimport step defined directly in this tool's routers, or the virtual\nstage id `tool_input` or `tool_output`. Any other value returns\n`400` `invalid_ref`; an ObjectId that is not a direct step of this\ntool returns `404` `invalid_ref`.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Keys of the captured debug requests to delete.","properties":{"keys":{"type":"array","description":"Keys of the records to delete, from the\n`GET /v1/tools/{_id}/{_stepId}/requests` listing.","minItems":1,"maxItems":1000,"items":{"type":"string"}}},"required":["keys"]}}}},"responses":{"200":{"description":"Deletion outcome. `deleted` lists the removed keys; `errors` lists\nkeys that could not be removed with the failure reason.","content":{"application/json":{"schema":{"type":"object","description":"Deletion result envelope.","properties":{"deleted":{"type":"array","description":"Keys of the records that were deleted.","items":{"type":"string"}},"errors":{"type":"array","description":"Records that failed to delete.","items":{"type":"object","properties":{"key":{"type":"string","description":"Key of the record that failed to delete."},"error":{"type":"string","description":"Failure reason."}}}}},"required":["deleted","errors"]}}}},"400":{"description":"The body carries no `keys` array (error code `invalid_url`), or\n`_stepId` is neither an ObjectId nor `tool_input` / `tool_output`\n(error code `invalid_ref`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"description":"The tool does not exist, or `_stepId` is an ObjectId that is not a\nstep defined directly in this tool. Error code: `invalid_ref`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get a captured debug request for a tool step

> Returns the full detail of a single captured debug request for a tool\
> step, identified by its \`key\`. Obtain the \`key\` from\
> \`GET /v1/tools/{\_id}/{\_stepId}/requests\`.\
> \
> For an HTTP or NetSuite step the record carries the adaptor HTTP\
> request/response the step issued, with credentials masked and the\
> bodies as strings. For the \`tool\_input\` and \`tool\_output\` stages (and a\
> ToolImport step) the record is synthetic: \`request.body\` is the JSON\
> record entering the stage, \`response.body\` is the validated JSON record\
> (\`statusCode\` 200) or the validation errors (\`statusCode\` 422), the\
> request carries no \`url\`, and \`context\` names the run that produced it.\
> Synthetic bodies are stored unmasked. Every record carries the \`key\`,\
> record \`id\`, and capture \`time\`.\
> \
> Requires the \`manage:tool:logs\` permission.

```json
{"openapi":"3.2.0","info":{"title":"Tools","version":"1.0.0"},"tags":[{"name":"Tools","description":"Tools are reusable processing units within integrations that encapsulate input\ntransformation, conditional routing, output mapping, and data enrichment logic behind\nan input/output contract. They can be referenced from flows, APIs, AI agents, MCP\nservers, and other tools to promote modularity and reuse.\n\n## Tool schema\n\n{% openapi-schemas spec=\"tool\" schemas=\"Tool\" grouped=\"true\" %}"}],"servers":[{"url":"https://api.integrator.io","description":"Production (US / default region)"},{"url":"https://api.eu.integrator.io","description":"Production (EU region)"},{"url":"https://api.au.integrator.io","description":"Production (AU region)"},{"url":"https://api.ca.integrator.io","description":"Production (CA region)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","description":"Standard error response envelope returned by integrator.io APIs.","properties":{"errors":{"type":"array","description":"List of errors that occurred while processing the request.","items":{"type":"object","properties":{"code":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Machine-readable error code. Usually a string like\n`invalid_ref`, `missing_required_field`, or `unauthorized`;\nmay be an **integer** when the error mirrors an upstream HTTP\nstatus (e.g. `500`) — most commonly returned by connection-ping\nand adaptor-proxy responses."},"message":{"type":"string","description":"Human-readable description of the error."},"field":{"type":"string","description":"Optional pointer to the document field that caused the error.\nUsed by structural validation errors (`missing_required_field`,\n`invalid_ref`) to indicate which field is at fault\n(e.g. `_id`, `type`, `http.baseURI`)."},"source":{"type":"string","description":"Optional origin layer for the error — e.g. `application` when\nthe error came from the remote system the adaptor called,\n`connector` when the adaptor itself rejected the request."}},"required":["message"]}}},"required":["errors"]}},"responses":{"401-unauthorized":{"description":"Unauthorized. The request lacks a valid bearer token, or the provided token\nfailed to authenticate.\n\nNote: the 401 response is produced by the auth middleware **before** the\nrequest reaches the endpoint handler, so it does **not** follow the\nstandard `{errors: [...]}` envelope. Instead the body is a bare\n`{message: string}` object with no `code`, no `errors` array. Callers\nhandling 401s should key off the HTTP status and the `message` string,\nnot try to destructure an `errors[]`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable description of the auth failure. Known values:\n- `\"Unauthorized\"` — no `Authorization` header on the request.\n- `\"Bearer Authentication Failed\"` — header present but token\n  is invalid, revoked, or expired."}},"required":["message"]}}}},"403-forbidden":{"description":"Forbidden. The authenticated caller does not have permission to perform this operation.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/v1/tools/{_id}/{_stepId}/requests/{key}":{"get":{"summary":"Get a captured debug request for a tool step","description":"Returns the full detail of a single captured debug request for a tool\nstep, identified by its `key`. Obtain the `key` from\n`GET /v1/tools/{_id}/{_stepId}/requests`.\n\nFor an HTTP or NetSuite step the record carries the adaptor HTTP\nrequest/response the step issued, with credentials masked and the\nbodies as strings. For the `tool_input` and `tool_output` stages (and a\nToolImport step) the record is synthetic: `request.body` is the JSON\nrecord entering the stage, `response.body` is the validated JSON record\n(`statusCode` 200) or the validation errors (`statusCode` 422), the\nrequest carries no `url`, and `context` names the run that produced it.\nSynthetic bodies are stored unmasked. Every record carries the `key`,\nrecord `id`, and capture `time`.\n\nRequires the `manage:tool:logs` permission.","operationId":"getToolStepRequest","tags":["Tools"],"parameters":[{"name":"_id","in":"path","required":true,"description":"The unique identifier of the tool.","schema":{"type":"string","format":"objectId"}},{"name":"_stepId","in":"path","required":true,"description":"The step the captured request belongs to: the ObjectId of an export\nor import step defined directly in this tool's routers, or the\nvirtual stage id `tool_input` or `tool_output`. Steps nested inside\na child tool are addressed on that child tool's id; an ObjectId that\nis not a direct step of this tool returns `404` `invalid_ref`. Any\nother value — including reserved path segments such as\n`invocations`, `invoke`, and `test` — returns `400` `invalid_ref`.","schema":{"type":"string"}},{"name":"key","in":"path","required":true,"description":"Key identifying the captured debug request, from the\n`GET /v1/tools/{_id}/{_stepId}/requests` listing.","schema":{"type":"string"}}],"responses":{"200":{"description":"The captured debug request detail.","content":{"application/json":{"schema":{"type":"object","description":"A single captured debug request record, including the captured\nrequest/response payloads.","properties":{"key":{"type":"string","description":"Echoes the `key` path parameter."},"id":{"type":"string","description":"Internal storage id parsed from the log filename."},"time":{"type":"integer","format":"int64","description":"Epoch milliseconds when this pair was stored."},"request":{"type":"object","description":"The HTTP request the step's export or import sent, or — for\nthe `tool_input`, `tool_output` and ToolImport stages — the\nrecord entering the stage, wrapped as a `POST` with no `url`.","properties":{"method":{"type":"string","description":"HTTP method (`GET`, `POST`, …); always `POST` on synthetic records."},"url":{"type":"string","description":"Fully-resolved URL the request was sent to. Absent on synthetic records."},"headers":{"type":"object","description":"Headers sent with the request; credential values are masked. Empty on synthetic records.","additionalProperties":true},"body":{"type":["string","object"],"description":"Request body as sent, verbatim, for an HTTP step (absent on a\nbody-less method such as `GET`); the JSON record entering the\nstage on a synthetic record."}}},"response":{"type":"object","description":"The HTTP response the step received, or — on synthetic\nrecords — the outcome of the stage: the validated record\nwith `statusCode` 200, or the validation errors with 422.","properties":{"statusCode":{"type":"integer","description":"HTTP status code the target system returned, or 200 / 422 for a synthetic record."},"headers":{"type":"object","description":"Response headers as received. Empty on synthetic records.","additionalProperties":true},"body":{"type":["string","object"],"description":"For an HTTP or NetSuite step: the response body as received, a\nstring the caller parses per the `content-type` header. On a\nsynthetic record: the validated JSON record as an object, or\n`{errors: [{code, message, …}]}` when validation failed."},"receivedAt":{"type":"integer","format":"int64","description":"Epoch milliseconds when the pair was captured."}}},"context":{"type":"object","description":"The run that produced a synthetic record. Absent on records\nwritten by HTTP and NetSuite steps.","properties":{"_rootContainerId":{"type":"string","description":"Owning flow, API, or tool id of the run that produced this capture."},"_jobId":{"type":"string","description":"Job id of the run that produced this capture."}}}},"additionalProperties":true}}}},"400":{"description":"`_stepId` is neither an ObjectId nor `tool_input` / `tool_output`.\nError code: `invalid_ref`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/401-unauthorized"},"403":{"$ref":"#/components/responses/403-forbidden"},"404":{"description":"The tool or the step does not exist (`invalid_ref`), or no record\ncarries this `key` (`file_not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```
