assets/schemas/blocks/advanced.json
{
"$id": "https://qlik.com/schemas/automations/blocks/advanced.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Qlik Automate block defs - advanced",
"description": "Structure of the Advanced blocks used when constructing an automation workspace: Update Run Title, List Pointers, Update Pointer, Delete Pointer, Compare Object, Redirect, Case, Go To Label, Label, Loop Batch, Exit Loop, Custom Code.",
"$defs": {
"UpdateRunTitleBlock": {
"description": "Sets the title of the run in progress, so runs can be told apart in the run history. Use it to record the outcome or what the run was about, rather than leaving a list of identical entries. Default block name: updateRunTitle.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "UpdateJobBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"allOf": [{"$ref": "../workspace-schema.json#/$defs/InputCommon"}],
"required": ["id", "type"],
"properties": {
"id": {"const": "title"},
"type": {"const": "string"},
"value": {"type": ["string", "null"]}
}
}
},
"settings": {
"type": "array",
"items": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
}
},
"ListPointerBlock": {
"description": "Reports where a connector block that fetches records incrementally has got to. Use it to see what an earlier run already collected, or to check progress before deciding what to do next. Default block name: listPointers.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "ListPointerBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {"$ref": "#/$defs/PointerPickerInput"}
},
"settings": {
"type": "array",
"items": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
}
},
"UpdatePointerBlock": {
"description": "Moves the mark that says how far a connector block has already fetched, so the next run picks up from where you choose. Use it to make a run re-read a period again, or to skip ahead past records you do not want. Default block name: updatePointer.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "UpdatePointerBlock"},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"$ref": "#/$defs/UpdatePointerInput"},
"contains": {"required": ["id"], "properties": {"id": {"const": "pointer_block_id"}}},
"oneOf": [
{
"contains": {"required": ["id"], "properties": {"id": {"const": "last_retrieved_at"}}}
},
{
"contains": {"required": ["id"], "properties": {"id": {"const": "highest_record_retrieved"}}}
}
]
},
"settings": {"type": "array", "maxItems": 0}
}
},
"UpdatePointerInput": {
"description": "An entry in an UpdatePointerBlock `inputs` array: the pointer picker, or one of the two mutually exclusive position inputs.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"enum": ["pointer_block_id", "last_retrieved_at", "highest_record_retrieved"]}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "pointer_block_id"}}},
"then": {"$ref": "#/$defs/PointerPickerInput"}
},
{
"if": {
"required": ["id"],
"properties": {"id": {"enum": ["last_retrieved_at", "highest_record_retrieved"]}}
},
"then": {"properties": {"type": {"const": "string"}, "value": {"type": ["string", "null"]}}}
}
]
},
"DeletePointerBlock": {
"description": "Clears the mark that says how far a connector block has already fetched, so the next run starts from the beginning. Use it to force a full re-fetch after something went wrong. Default block name: deletePointer.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "DeletePointerBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {"$ref": "#/$defs/PointerPickerInput"}
},
"settings": {"type": "array", "maxItems": 0}
}
},
"PointerPickerInput": {
"description": "The `pointer_block_id` input shared by the three pointer blocks (AbstractPointerBlock.createPointerPicker). Type `select`, required, formulas and parent-block references disabled. Its value is the **block id of another block in this same workspace** - one whose endpoint has a pointer (an incremental endpoint) - not a guid from the connector catalog and not a `{$....}` reference.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"const": "pointer_block_id"},
"type": {"const": "select"},
"value": {"type": ["string", "null"]}
}
}
]
},
"CompareObjectBlock": {
"description": "Compares two records and reports either the fields where they differ or the fields where they agree. Use it to see what changed between a before and after - a setting that moved, a value that was updated. Default block name: compareObject.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/BlockBase"},
{
"if": {
"required": ["settings"],
"properties": {
"settings": {
"type": "array",
"contains": {
"required": ["id", "value"],
"properties": {"id": {"const": "compare_mode"}, "value": {"const": "diff"}}
}
}
}
},
"then": {
"$comment": "compare_mode=diff: both ignore_missing_* settings are exported.",
"properties": {
"settings": {
"allOf": [
{
"contains": {
"required": ["id"],
"properties": {"id": {"const": "ignore_missing_in_object1"}}
}
},
{
"contains": {
"required": ["id"],
"properties": {"id": {"const": "ignore_missing_in_object2"}}
}
}
]
}
}
}
},
{
"if": {
"required": ["settings"],
"properties": {
"settings": {
"type": "array",
"contains": {
"required": ["id", "value"],
"properties": {"id": {"const": "compare_mode"}, "value": {"const": "similar"}}
}
}
}
},
"then": {
"$comment": "compare_mode=similar: neither ignore_missing_* setting is exported.",
"properties": {
"settings": {
"not": {
"contains": {
"required": ["id"],
"properties": {"id": {"enum": ["ignore_missing_in_object1", "ignore_missing_in_object2"]}}
}
}
}
}
}
}
],
"required": ["type", "inputs", "settings"],
"properties": {
"type": {"const": "CompareObjectBlock"},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"enum": ["object1", "object2"]},
"type": {"const": "string"},
"value": {"type": ["string", "null"]}
}
}
]
},
"allOf": [
{"contains": {"required": ["id"], "properties": {"id": {"const": "object1"}}}},
{"contains": {"required": ["id"], "properties": {"id": {"const": "object2"}}}}
]
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/CompareObjectSetting"},
"contains": {"required": ["id"], "properties": {"id": {"const": "compare_mode"}}}
}
}
},
"CompareObjectSetting": {
"description": "An entry in a CompareObjectBlock `settings` array.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/SettingCommon"},
{
"properties": {
"id": {
"enum": [
"compare_mode",
"ignore_missing_in_object1",
"ignore_missing_in_object2",
"automations_censor_data"
]
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "compare_mode"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["diff", "similar"]}
}
}
},
{
"if": {
"required": ["id"],
"properties": {"id": {"enum": ["ignore_missing_in_object1", "ignore_missing_in_object2"]}}
},
"then": {
"required": ["value"],
"properties": {"type": {"const": "select"}, "value": {"type": "string", "enum": ["yes", "no"]}}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "automations_censor_data"}}},
"then": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
]
},
"RedirectBlock": {
"description": "Sends whoever started the run on to another web address and ends the run there. It only makes sense when a browser is waiting on the automation. Default block name: redirect.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "RedirectBlock"},
"childId": {"type": "null"},
"inputs": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"const": "url"},
"type": {"const": "string"},
"value": {"type": ["string", "null"]}
}
}
]
}
},
"settings": {"type": "array", "maxItems": 0}
}
},
"CaseBlock": {
"description": "Picks one of several paths by looking at a value, where a condition block would only give two. Use it when a run should behave differently per status, type, or region, with a fallback path for anything unrecognised. Default block name: case.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "statementChildrenIds", "defaultChildId"],
"properties": {
"type": {"const": "CaseBlock"},
"defaultChildId": {"type": ["string", "null"]},
"statementChildrenIds": {"type": "array", "items": {"type": ["string", "null"]}},
"inputs": {
"type": "array",
"minItems": 2,
"allOf": [
{
"contains": {
"type": "object",
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "expression"},
"type": {"const": "string"},
"value": {"type": "string"}
}
}
},
{
"contains": {
"type": "object",
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "statements"},
"type": {"const": "list"},
"value": {"type": "array", "minItems": 1}
}
}
}
]
}
},
"$comment": "JSON Schema cannot enforce that statementChildrenIds.length == inputs[id=statements].value.length. Validate in code."
},
"GotoBlock": {
"description": "Jumps the run to a labelled point elsewhere in the automation, instead of carrying on to the next block. Use it sparingly, to send several branches to a shared ending or to retry an earlier step. Default block name: goToLabel.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "GotoBlock"},
"childId": {"type": "null"},
"inputs": {
"type": "array",
"minItems": 1,
"contains": {
"type": "object",
"required": ["id", "type", "value", "displayValue"],
"properties": {
"id": {"const": "label"},
"type": {"const": "select"},
"value": {"type": "string", "minLength": 1},
"displayValue": {"type": "string"}
}
}
}
}
},
"LabelBlock": {
"description": "Marks a point in the automation that a jump can be aimed at. On its own it does nothing. Default block name: label.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "LabelBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"contains": {
"type": "object",
"required": ["id", "type", "value"],
"properties": {"id": {"const": "name"}, "type": {"const": "string"}, "value": {"type": "string"}}
}
}
}
},
"ForEachBatchBlock": {
"description": "Works through a list in groups rather than one record at a time. Use it when what comes next can take many records in one go, such as writing rows to a file or sending a bulk request, which is far quicker than repeating the call per record. Default block name: loopBatch.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "ForEachBatchBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"enum": ["input", "size"]},
"type": {"const": "string"},
"value": {"type": ["string", "null"]}
}
}
]
},
"allOf": [
{"contains": {"required": ["id"], "properties": {"id": {"const": "input"}}}},
{"contains": {"required": ["id"], "properties": {"id": {"const": "size"}}}}
]
},
"settings": {
"type": "array",
"items": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
}
},
"ForEachBreakBlock": {
"description": "Leaves a loop before it has been through everything. Use it to stop once what you were looking for has been found, or once enough has been done. Default block name: exitLoop.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type"],
"properties": {
"type": {"const": "ForEachBreakBlock"},
"inputs": {"type": "array", "maxItems": 0},
"settings": {"type": "array", "maxItems": 0}
}
},
"CustomCodeBlock": {
"description": "Runs a short piece of code, in PHP, Node.js or Python, for logic no other block can express. Values from the run are handed to it and whatever it prints comes back as its result. Reach for it last, once no combination of the other blocks will do. Default block name: customCode.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "CustomCodeBlock3"},
"inputs": {
"type": "array",
"minItems": 3,
"maxItems": 3,
"items": {"$ref": "#/$defs/CustomCodeInput"},
"allOf": [
{"contains": {"required": ["id"], "properties": {"id": {"const": "language"}}}},
{"contains": {"required": ["id"], "properties": {"id": {"const": "inputs"}}}},
{"contains": {"required": ["id"], "properties": {"id": {"const": "code"}}}}
]
},
"settings": {
"type": "array",
"items": {"$ref": "../workspace-schema.json#/$defs/ConnectorSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"CustomCodeInput": {
"description": "An entry in a CustomCodeBlock (`type: CustomCodeBlock3`) `inputs` array. Only `language`, `inputs` and `code` are valid ids.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{"properties": {"id": {"enum": ["language", "inputs", "code"]}}},
{
"if": {"required": ["id"], "properties": {"id": {"const": "language"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["php", "nodejs", "python"]}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "inputs"}}},
"then": {
"properties": {
"type": {"const": "object"},
"mode": {"type": "string", "enum": ["keyValue", "raw"]},
"value": {
"type": ["array", "null"],
"items": {
"type": "object",
"required": ["key", "value"],
"additionalProperties": true,
"properties": {"key": {"type": "string", "minLength": 1}, "value": {}}
},
"description": "Array of { key, value } variables. The key is the name the snippet reads (`$inputs['First Name']` / `inputs['First Name']`), so keep it a plain identifier-ish string; the value may be a formula."
}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "code"}}},
"then": {"properties": {"type": {"const": "code"}, "value": {"type": ["string", "null"]}}}
}
]
}
}
}
assets/schemas/blocks/basic.json
{
"$id": "https://qlik.com/schemas/automations/blocks/basic.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Qlik Automate block defs - basic",
"description": "Structure of the Basic blocks used when constructing an automation workspace: Output, Inputs, Condition, Loop, Call URL, Stop, Sleep, Call Automation, Variable, Error.",
"$defs": {
"ShowBlock": {
"description": "Puts a value into the run's output, so it can be seen in the run history and is handed back to whatever started the run. Use it to surface a result, or to report what an automation did to the tool or assistant that called it. Default block name: output.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "ShowBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"contains": {
"type": "object",
"required": ["id", "type"],
"properties": {"id": {"const": "input"}, "type": {"const": "string"}}
}
},
"settings": {
"type": "array",
"contains": {
"type": "object",
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "display_mode"},
"type": {"const": "select"},
"value": {"type": "string", "enum": ["add", "set", "merge"]}
}
}
}
}
},
"FormBlock": {
"description": "Asks for values when the automation runs, rather than having them fixed in the workspace. The run pauses until the answers come back, so use it for the details that change from run to run - which app to reload, who to notify. The answers can be remembered so the next run starts from them. Default block name: inputs.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "form", "persistData"],
"properties": {
"type": {"const": "FormBlock"},
"persistData": {"type": "string", "enum": ["yes", "no"]},
"form": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/FormItem"}},
"settings": {
"type": "array",
"contains": {
"type": "object",
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "persist_data"},
"type": {"const": "select"},
"value": {"type": "string", "enum": ["yes", "no"]}
}
}
}
}
},
"FormItem": {
"type": "object",
"required": ["id", "type", "label", "order"],
"additionalProperties": true,
"properties": {
"id": {"type": "string", "minLength": 1},
"type": {
"type": "string",
"enum": ["input", "select", "object", "multi_select", "field_mapping"],
"description": "Builder field types - not raw input editor types."
},
"label": {"type": "string"},
"order": {"type": "integer", "minimum": 0},
"helpText": {"type": ["string", "null"]},
"isRequired": {"type": "boolean"},
"values": {},
"options": {"type": "object"}
}
},
"IfElseBlock": {
"description": "Sends the run down one of two paths depending on a condition, with a branch for when it holds and a branch for when it does not. Use it to react to what earlier blocks found - a missing record, a failed reload, an empty list. Default block name: condition.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "childTrueId", "childFalseId", "inputs"],
"properties": {
"type": {"const": "IfElseBlock"},
"childTrueId": {"type": ["string", "null"]},
"childFalseId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 1,
"prefixItems": [
{
"allOf": [{"$ref": "../workspace-schema.json#/$defs/InputCommon"}],
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "conditions"},
"type": {"const": "custom"},
"value": {"$ref": "../workspace-schema.json#/$defs/ConditionGroup"}
}
}
]
}
}
},
"ForEachBlock": {
"description": "Works through a list one item at a time, running the blocks inside it once for each. Use it to act on every row of data an earlier block returned. Anything placed after the loop instead of inside it waits until the whole list is done. Default block name: loop.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "loopBlockId", "inputs"],
"properties": {
"type": {"const": "ForEachBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 1,
"contains": {
"type": "object",
"required": ["id", "type"],
"properties": {"id": {"const": "input"}, "type": {"const": "string"}}
}
}
}
},
"CallUrlBlock": {
"description": "Makes a web request to any address, for services no connector covers. Nothing signs the request, so use it only for endpoints that need no authentication - never put an API key, password, token, or signed URL in `url`, `headers`, or `params`. If the call needs credentials, use a connection-bound connector block instead. Set `automations_censor_data: true` whenever `headers` or `params` carry values that could be sensitive. Default block name: callURL.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "CallUrlBlock"},
"inputs": {
"type": "array",
"minItems": 2,
"items": {"$ref": "#/$defs/CallUrlInput"},
"allOf": [
{
"contains": {
"type": "object",
"required": ["id", "type"],
"properties": {"id": {"const": "url"}, "type": {"const": "string"}}
}
},
{
"contains": {
"type": "object",
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "method"},
"type": {"const": "select"},
"value": {"type": "string", "enum": ["GET", "PUT", "POST", "PATCH", "DELETE"]}
}
}
}
]
},
"settings": {
"type": "array",
"items": {"$ref": "../workspace-schema.json#/$defs/ConnectorSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"CallUrlInput": {
"description": "An entry in a CallUrlBlock `inputs` array. Only the six built-in ids are valid, and the two `object` inputs carry a `mode`.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {"id": {"enum": ["url", "method", "timeout", "params", "headers", "encoding"]}}
},
{
"if": {"required": ["id"], "properties": {"id": {"enum": ["params", "headers"]}}},
"then": {
"properties": {
"type": {"const": "object"},
"mode": {"type": "string", "enum": ["keyValue", "raw"]}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"enum": ["timeout", "encoding"]}}},
"then": {"properties": {"type": {"const": "string"}, "value": {"type": ["string", "null"]}}}
}
]
},
"StopBlock": {
"description": "Ends the run at that point, treating it as finished rather than failed. Use it to bow out early once there is nothing left worth doing, such as an empty list of records. Default block name: stop.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type"],
"properties": {"type": {"const": "StopBlock"}, "inputs": {"type": "array", "maxItems": 0}}
},
"SleepBlock": {
"description": "Waits for a while before the run carries on, five seconds by default. Use it to give a slow job time to progress before checking on it again. Default block name: sleep.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "SleepBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"contains": {
"type": "object",
"required": ["id", "type"],
"properties": {"id": {"const": "time"}, "type": {"const": "string"}}
}
}
}
},
"CallWidgetBlock": {
"description": "Runs another automation and brings its output back, so shared work can live in one automation and be reused by others instead of being rebuilt. An automation cannot call itself. Default block name: callAutomation.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "CallWidgetBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"maxItems": 2,
"items": {"$ref": "#/$defs/CallWidgetInput"},
"contains": {
"type": "object",
"required": ["id", "type"],
"properties": {"id": {"const": "guid"}, "type": {"const": "api-search"}}
}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/CallWidgetSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"CallWidgetInput": {
"description": "An entry in a CallWidgetBlock `inputs` array. Only `guid` and `widgetInputs` are valid ids.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{"properties": {"id": {"enum": ["guid", "widgetInputs"]}}},
{
"if": {"required": ["id"], "properties": {"id": {"const": "guid"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "api-search"},
"value": {
"type": "string",
"pattern": "^[A-Za-z0-9-]+$",
"description": "Target automation id. Letters, digits and dashes only - the backend rejects anything else, and rejects an empty value at save time."
}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "widgetInputs"}}},
"then": {
"properties": {
"type": {"const": "object"},
"mode": {"type": "string", "enum": ["keyValue", "raw"]},
"value": {
"type": ["array", "string", "null"],
"description": "In keyValue mode an array of { key, value } entries keyed by the target's input prompts (exported entries may also carry `required` and `type`). Omit the whole input when it would be null or empty - that is what the editor exports."
}
}
}
}
]
},
"CallWidgetSetting": {
"description": "An entry in a CallWidgetBlock `settings` array. Adds the `cache` select on top of the shared connector setting shapes.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/ConnectorSetting"},
{
"if": {"required": ["id"], "properties": {"id": {"const": "cache"}}},
"then": {
"properties": {
"type": {"const": "select"},
"value": {"type": ["string", "null"], "enum": ["enabled", "disabled", null]}
}
}
}
]
},
"VariableBlock": {
"description": "Changes one of the workspace's variables as the run goes along - setting it, adding to it, or clearing it. Use it to carry a value between blocks, or to collect results across a loop and use them once it ends. Default block name: variable.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "variableGuid", "operations"],
"properties": {
"type": {"const": "VariableBlock"},
"variableGuid": {"$ref": "../workspace-schema.json#/$defs/Uuid"},
"operations": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/VariableOperation"}}
}
},
"VariableOperation": {
"description": "Variable operation entry. `key` is a fresh UUIDv4 unique to the entry; `name` is the operation's editor label. Allowed `id`s depend on the target variable's `type`: string - empty, set_value, append; list - empty, add_item, merge; table - empty, add_row; object - empty, set, set_key_value, merge, rename_key, remove_key; number - set_value, add, subtract, multiply, divide (numbers have no empty). `value` is required for all operations except `empty`.",
"type": "object",
"required": ["id", "key", "name"],
"additionalProperties": true,
"if": {"required": ["id"], "properties": {"id": {"not": {"const": "empty"}}}},
"then": {"required": ["value"]},
"properties": {
"id": {
"type": "string",
"enum": [
"empty",
"set_value",
"append",
"add_item",
"merge",
"add_row",
"set",
"set_key_value",
"rename_key",
"remove_key",
"add",
"subtract",
"multiply",
"divide"
]
},
"key": {"type": "string", "minLength": 1},
"name": {"type": "string", "minLength": 1},
"value": {}
}
},
"ErrorBlock": {
"description": "Fails the run on purpose with a message you write, so a problem is reported plainly in the run history instead of passing unnoticed. Use it where the automation finds something it cannot sensibly continue with. It can also record the message as a warning and carry on. Default block name: error.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "ErrorBlock"},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"$ref": "#/$defs/ErrorInput"},
"allOf": [
{
"contains": {
"type": "object",
"required": ["id", "type"],
"properties": {"id": {"const": "message"}, "type": {"const": "string"}}
}
},
{
"contains": {
"type": "object",
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "action"},
"type": {"const": "select"},
"value": {"type": "string", "enum": ["stop", "continue"]}
}
}
}
]
},
"settings": {"type": "array", "maxItems": 0}
}
},
"ErrorInput": {
"description": "An entry in an ErrorBlock `inputs` array. Only `message` and `action` are valid ids.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{"properties": {"id": {"enum": ["message", "action"]}}},
{
"if": {"required": ["id"], "properties": {"id": {"const": "message"}}},
"then": {"properties": {"type": {"const": "string"}, "value": {"type": ["string", "null"]}}}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "action"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["stop", "continue"]}
}
}
}
]
}
}
}
assets/schemas/blocks/cloud-storage.json
{
"$id": "https://qlik.com/schemas/automations/blocks/cloud-storage.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Qlik Automate block defs - cloud-storage",
"description": "Structure of the Cloud storage blocks used when constructing an automation workspace: Open File, Create File, Copy File, Read Data From File, Write Line To File, Save And Close File, Delete File, Move File, Check If File Exists, List Files.",
"$defs": {
"OpenFileBlock": {
"description": "Opens an existing file on a cloud storage connector so its contents can be used later in the run. On its own it only opens the file - pair it with Read Data From File to turn the contents into data. It can also fetch a file over HTTP. Default block name: openFile.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "OpenFileBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"prefixItems": [
{
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"const": "datasourcetype"},
"type": {"const": "select"},
"value": {
"anyOf": [{"$ref": "#/$defs/FileAdapterName"}, {"const": "HTTP"}, {"type": "null"}]
}
}
}
]
}
],
"items": {"$ref": "#/$defs/FileAdapterExtraInput"}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileConnectorSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"CreateFileBlock": {
"description": "Creates a new, empty file on a cloud storage connector, ready to be written into. Use it to start building a file the automation produces - a CSV export, a log, a report - then add the rows with Write Line To File and finish with Save And Close File. Default block name: createFile.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "CreateFileBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"prefixItems": [{"$ref": "#/$defs/FileAdapterInput"}],
"items": {"$ref": "#/$defs/FileAdapterExtraInput"}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileConnectorSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"CopyFileBlock": {
"description": "Copies a file the run already opened to another location, which can be on a different cloud storage connector. Use it to hand a file over between systems, such as putting a file fetched from one service into a bucket on another. Default block name: copyFile.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "CopyFileBlock"},
"inputs": {
"type": "array",
"minItems": 2,
"prefixItems": [
{
"allOf": [{"$ref": "#/$defs/FilePickerInput"}, {"properties": {"id": {"const": "source"}}}]
},
{
"allOf": [
{"$ref": "#/$defs/FileAdapterInput"},
{"not": {"properties": {"value": {"const": "Qlik Cloud Services"}}}}
]
}
],
"items": {"$ref": "#/$defs/FileAdapterExtraInput"}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileConnectorSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"ReadDataFromFileBlock": {
"description": "Turns the contents of an opened file into data the automation can work with, reading CSV, JSON, or plain lines. It goes through the file record by record, so the blocks inside it run once for each row. This is the usual way to bring an uploaded or exported file into an automation. Default block name: readDataFromFile.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "ReadDataFromFileBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 1,
"maxItems": 6,
"items": {
"allOf": [
{"$ref": "#/$defs/FileFormatInput"},
{
"properties": {
"id": {
"enum": ["file", "mode", "delimiter", "file_encoding", "csv_headers", "csv_columns"]
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "file"}}},
"then": {"$ref": "#/$defs/FilePickerInput"}
}
]
},
"contains": {"required": ["id"], "properties": {"id": {"const": "file"}}}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileNoDatasourceSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"WriteLineToFileBlock": {
"description": "Adds one row to a file the run created. Put it inside a loop to build a file up record by record, then close the file once the loop is done. It writes CSV, JSON, or plain lines. Default block name: writeLineToFile.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "WriteLineToFileBlock"},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 7,
"items": {
"allOf": [
{"$ref": "#/$defs/FileFormatInput"},
{
"properties": {
"id": {
"enum": [
"file",
"data",
"mode",
"delimiter",
"file_encoding",
"csv_columns",
"csv_headers"
]
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "file"}}},
"then": {"$ref": "#/$defs/FilePickerInput"}
}
]
},
"allOf": [
{"contains": {"required": ["id"], "properties": {"id": {"const": "file"}}}},
{"contains": {"required": ["id"], "properties": {"id": {"const": "data"}}}}
]
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileNoDatasourceSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"CloseFileBlock": {
"description": "Finishes a file the run created and stores it on the connector. Until this runs the file is still being written, so a run that adds rows but never closes the file leaves nothing usable behind. Default block name: saveAndCloseFile.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "CloseFileBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"allOf": [{"$ref": "#/$defs/FilePickerInput"}, {"properties": {"id": {"const": "file"}}}]
}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileNoDatasourceSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"DeleteFileBlock": {
"description": "Deletes a file from a cloud storage connector - use it to clear away a file once it has been processed. Default block name: deleteFile.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "DeleteFileBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"prefixItems": [
{
"allOf": [
{"$ref": "#/$defs/FileAdapterInput"},
{"not": {"properties": {"value": {"const": "Qlik Cloud Services"}}}}
]
}
],
"items": {"$ref": "#/$defs/FileAdapterExtraInput"}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileConnectorSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"MoveFileBlock": {
"description": "Moves a file to another location on the same cloud storage connector, which is also how you rename one. A common use is shifting a file into a processed or archive folder once the run has dealt with it. Default block name: moveFile.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "MoveFileBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"prefixItems": [
{
"allOf": [
{"$ref": "#/$defs/FileAdapterInput"},
{"not": {"properties": {"value": {"const": "Qlik Cloud Services"}}}}
]
}
],
"items": {"$ref": "#/$defs/FileAdapterExtraInput"}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileConnectorSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"CheckFileExistsBlock": {
"description": "Reports whether a file is present on a cloud storage connector. Use it before reading a file that may not be there, or before writing one you do not want to overwrite, and branch on the answer with a condition. Default block name: checkIfFileExists.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "CheckFileExistsBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"prefixItems": [{"$ref": "#/$defs/FileAdapterInput"}],
"items": {"$ref": "#/$defs/FileAdapterExtraInput"}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileConnectorSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"ListFilesBlock": {
"description": "Lists what is in a folder on a cloud storage connector, going through the files one by one so the blocks inside it run once for each. Use it to work through everything dropped in a folder. Default block name: listFiles.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "ListFilesBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 1,
"prefixItems": [
{
"allOf": [
{"$ref": "#/$defs/FileAdapterInput"},
{"not": {"properties": {"value": {"const": "Qlik Cloud Services"}}}}
]
}
],
"items": {"$ref": "#/$defs/FileAdapterExtraInput"}
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/FileConnectorSetting"},
"contains": {"$ref": "../workspace-schema.json#/$defs/BlendrOnErrorSetting"}
}
}
},
"FileAdapterName": {
"description": "Value of a `datasourcetype` input on a file block. Not a guid - it is the file **adapter** name, spelled exactly as listed here, and it matches the connector's catalog name (workflow step 1.3 uses that to resolve the connection). Extend this list when a new file connector adapter is added. `HTTP` is deliberately absent: only OpenFileBlock offers it.",
"type": "string",
"enum": [
"Qlik Cloud Services",
"FTP",
"SFTP",
"Amazon S3",
"Dropbox",
"Google Drive",
"Google Cloud Storage",
"Google Cloud Storage with service account",
"Azure Blob Storage",
"Microsoft SharePoint",
"Microsoft OneDrive"
]
},
"FileAdapterInput": {
"description": "The `datasourcetype` input on a file block: a `select` over adapter names, required, formulas disabled. Selecting it is what makes the editor append the adapter's own inputs (see FileAdapterExtraInput), so it is read **positionally** and must stay first in the `inputs` array.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"const": "datasourcetype"},
"type": {"const": "select"},
"value": {"anyOf": [{"$ref": "#/$defs/FileAdapterName"}, {"type": "null"}]}
}
}
]
},
"FileAdapterExtraInput": {
"description": "Any input a file block appends after `datasourcetype` is chosen. These are adapter-driven, so the id set is open and cannot be closed here: `path` on most blocks (`origin` and `destination` on MoveFileBlock), plus whatever the adapter declares - `bucket` and `region` and the `server_side_encryption` / `sse_kms_key_id` pair for Amazon S3, `container_name` for Azure Blob Storage, `drive_id` for OneDrive, `project_id` for Google Cloud Storage, `site_id` for SharePoint, `ttl` for Qlik Cloud Services, `encoding` / `headers` / `params` / `timeout` for HTTP. An unconfigured block exports none of them, since they only exist once an adapter is picked.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/InputCommon"}]
},
"FilePickerInput": {
"description": "A `file` (or `source`) file-picker input: a `select` whose value is the **block id of an earlier file block in this workspace** - not a path and not a `{$....}` reference. Required, formulas disabled. The candidate list is type-restricted: ReadDataFromFileBlock picks an OpenFileBlock, WriteLineToFileBlock and CloseFileBlock pick a CreateFileBlock, and CopyFileBlock's `source` accepts any block that returns a file (isFileOutput, or an endpoint/snippet with role `returns_file`).",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"enum": ["file", "source"]},
"type": {"const": "select"},
"value": {"type": ["string", "null"]}
}
}
]
},
"FileFormatInput": {
"description": "One of the data-format inputs shared by ReadDataFromFileBlock and WriteLineToFileBlock. All of them are conditional, so an unconfigured block exports none of them: `mode` appears only once a file block is selected, `delimiter` / `csv_columns` only for `mode: csv`, `file_encoding` for `csv` or `raw`, and `csv_headers` for `csv` (on WriteLineToFileBlock also only once `csv_columns` has a value). `csv_headers` is a `yes`/`no` select, not a checkbox, and `delimiter` must be a single character.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"if": {"required": ["id"], "properties": {"id": {"const": "mode"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["auto", "csv", "json", "raw"]}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "csv_headers"}}},
"then": {
"required": ["value"],
"properties": {"type": {"const": "select"}, "value": {"type": "string", "enum": ["yes", "no"]}}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "delimiter"}}},
"then": {
"properties": {
"type": {"const": "string"},
"value": {"type": ["string", "null"], "pattern": "^.$"}
}
}
},
{
"if": {
"required": ["id"],
"properties": {"id": {"enum": ["file_encoding", "csv_columns", "data"]}}
},
"then": {"properties": {"type": {"const": "string"}, "value": {"type": ["string", "null"]}}}
}
]
},
"FileConnectorSetting": {
"description": "Settings on a file block that picks its own adapter. `blendr_on_error` is always present. `datasource` is present for every adapter that needs a connection - all of them except Qlik Cloud Services, and HTTP on OpenFileBlock (Microsoft OneDrive keeps it); as everywhere else its value must be null.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/ConnectorSetting"}],
"properties": {"id": {"enum": ["blendr_on_error", "datasource", "automations_censor_data"]}}
},
"FileNoDatasourceSetting": {
"description": "Settings on a file block that inherits its connection from the file block it points at (ReadDataFromFileBlock, WriteLineToFileBlock, CloseFileBlock). These never carry a `datasource` setting - only `blendr_on_error` and `automations_censor_data`.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/ConnectorSetting"}],
"properties": {"id": {"enum": ["blendr_on_error", "automations_censor_data"]}}
}
}
}
assets/schemas/blocks/lists.json
{
"$id": "https://qlik.com/schemas/automations/blocks/lists.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Qlik Automate block defs - lists",
"description": "Schema of the Lists blocks used when constructing an automation workspace: Merge Lists, Filter List, Lookup Item In List, Deduplicate List, Compare Lists, Replace Field Names In List, Transform List, Limit.",
"$defs": {
"MergeListsBlock": {
"description": "Brings two lists together by matching them on a shared value, the way a join does in a database. Use it to enrich one set of records with details held in another - orders with customer names, users with their group. The result is either one combined record per match, or the second list's records nested inside the first's. Default block name: mergeLists.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/BlockBase"},
{
"if": {
"properties": {
"settings": {
"type": "array",
"contains": {
"properties": {"id": {"const": "merge_strategy"}, "value": {"const": "merge"}},
"required": ["id", "value"]
}
}
},
"required": ["settings"]
},
"then": {
"$comment": "merge_strategy=merge: the unique-key trio is exported, the set-only trio is not.",
"properties": {
"settings": {
"allOf": [
{
"contains": {"properties": {"id": {"const": "list1_unique_key"}}, "required": ["id"]}
},
{
"contains": {"properties": {"id": {"const": "list2_unique_key"}}, "required": ["id"]}
},
{
"contains": {"properties": {"id": {"const": "on_duplicate_key"}}, "required": ["id"]}
},
{
"not": {
"contains": {
"properties": {"id": {"enum": ["children_key", "list1_key", "list2_key"]}},
"required": ["id"]
}
}
}
]
}
}
}
},
{
"if": {
"properties": {
"settings": {
"type": "array",
"contains": {
"properties": {"id": {"const": "merge_strategy"}, "value": {"const": "set"}},
"required": ["id", "value"]
}
}
},
"required": ["settings"]
},
"then": {
"$comment": "merge_strategy=set: the children/key trio is exported, the merge-only settings are not.",
"properties": {
"settings": {
"allOf": [
{
"contains": {"properties": {"id": {"const": "children_key"}}, "required": ["id"]}
},
{
"contains": {"properties": {"id": {"const": "list1_key"}}, "required": ["id"]}
},
{
"contains": {"properties": {"id": {"const": "list2_key"}}, "required": ["id"]}
},
{
"not": {
"contains": {
"properties": {
"id": {
"enum": [
"list1_unique_key",
"list2_unique_key",
"on_duplicate_key",
"on_existing_item_key"
]
}
},
"required": ["id"]
}
}
}
]
}
}
}
},
{
"if": {
"properties": {
"settings": {
"type": "array",
"contains": {"properties": {"id": {"const": "on_existing_item_key"}}, "required": ["id"]}
}
},
"required": ["settings"]
},
"then": {
"$comment": "on_existing_item_key is only exported while on_duplicate_key is merge.",
"properties": {
"settings": {
"contains": {
"properties": {"id": {"const": "on_duplicate_key"}, "value": {"const": "merge"}},
"required": ["id", "value"]
}
}
}
}
}
],
"required": ["type", "inputs", "settings"],
"properties": {
"type": {"const": "MergeListsBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"$ref": "#/$defs/MergeListsInput"},
"allOf": [
{"contains": {"properties": {"id": {"const": "list1"}}, "required": ["id"]}},
{"contains": {"properties": {"id": {"const": "list2"}}, "required": ["id"]}}
]
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/MergeListsSetting"},
"contains": {"properties": {"id": {"const": "merge_strategy"}}, "required": ["id"]}
}
}
},
"MergeListsInput": {
"description": "An entry in a MergeListsBlock `inputs` array. Only `list1` and `list2` are valid ids; both are plain string references to a list.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"enum": ["list1", "list2"]},
"type": {"const": "string"},
"value": {"type": ["string", "null"]}
}
}
]
},
"MergeListsSetting": {
"description": "An entry in a MergeListsBlock `settings` array. `merge_strategy: merge` combines a list 1 item and a list 2 item into one item and uses `list1_unique_key` / `list2_unique_key` (both `field` inputs naming a property inside a list item, not a `{$....}` reference), `on_duplicate_key`, and - only when that is `merge` - `on_existing_item_key`. `merge_strategy: set` instead nests list 2 items under list 1 items and uses `children_key` (the property on the list 1 item to hold them) plus `list1_key` / `list2_key`.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/SettingCommon"},
{
"properties": {
"id": {
"enum": [
"merge_strategy",
"list1_unique_key",
"list2_unique_key",
"on_duplicate_key",
"on_existing_item_key",
"children_key",
"list1_key",
"list2_key",
"case_insensitive",
"automations_censor_data"
]
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "merge_strategy"}}},
"then": {
"required": ["value"],
"properties": {"type": {"const": "select"}, "value": {"type": "string", "enum": ["merge", "set"]}}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "on_duplicate_key"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["ignore", "merge"]}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "on_existing_item_key"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["keep_list1", "keep_list2", "keep_both"]}
}
}
},
{
"if": {
"required": ["id"],
"properties": {"id": {"enum": ["list1_unique_key", "list2_unique_key", "list1_key", "list2_key"]}}
},
"then": {"properties": {"type": {"const": "field"}, "value": {"type": ["string", "null"]}}}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "children_key"}}},
"then": {"properties": {"type": {"const": "string"}, "value": {"type": ["string", "null"]}}}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "case_insensitive"}}},
"then": {
"properties": {"type": {"const": "checkbox"}, "value": {"type": ["boolean", "null"]}}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "automations_censor_data"}}},
"then": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
]
},
"FilterListBlock": {
"description": "Narrows a list down to the records that meet a condition, and carries on with just those. Use it to act on part of a set - the failed reloads, the apps in one space, the rows above a threshold. Default block name: filterList.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "loopBlockId", "inputs"],
"properties": {
"type": {"const": "FilterListBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"prefixItems": [
{
"allOf": [{"$ref": "../workspace-schema.json#/$defs/InputCommon"}],
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "list"},
"type": {"const": "string"},
"value": {"type": "string", "minLength": 1}
}
},
{
"allOf": [{"$ref": "../workspace-schema.json#/$defs/InputCommon"}],
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "conditions"},
"type": {"const": "custom"},
"value": {"$ref": "../workspace-schema.json#/$defs/ConditionGroup"}
}
}
]
}
}
},
"LookupItemBlock": {
"description": "Finds one particular record in a list. Use it to pick out a single thing by name or id - the space you want, the user with a given email. It comes back empty when nothing matches, so check before relying on what it found. Default block name: lookupItemInList.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "LookupItemBlock"},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"prefixItems": [
{
"allOf": [{"$ref": "../workspace-schema.json#/$defs/InputCommon"}],
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "list"},
"type": {"const": "string"},
"value": {"type": "string", "minLength": 1}
}
},
{
"allOf": [{"$ref": "../workspace-schema.json#/$defs/InputCommon"}],
"required": ["id", "type", "value"],
"properties": {
"id": {"const": "conditions"},
"type": {"const": "custom"},
"value": {"$ref": "../workspace-schema.json#/$defs/ConditionGroup"}
}
}
]
},
"collapsed": {"type": "array", "maxItems": 0},
"settings": {
"type": "array",
"items": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
}
},
"DeduplicateListBlock": {
"description": "Removes repeated records from a list, either by comparing whole records or by one value they share. Use it to clean up a set gathered from several places before acting on it, so nothing is handled twice. Default block name: deduplicateList.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "DeduplicateListBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"$ref": "#/$defs/DeduplicateListInput"},
"allOf": [
{
"contains": {
"type": "object",
"required": ["id", "type"],
"properties": {"id": {"const": "list"}, "type": {"const": "string"}}
}
},
{
"contains": {
"type": "object",
"required": ["id", "type"],
"properties": {"id": {"const": "key"}, "type": {"const": "field"}}
}
}
]
},
"settings": {"type": "array", "items": {"$ref": "#/$defs/DeduplicateListSetting"}}
}
},
"DeduplicateListInput": {
"description": "An entry in a DeduplicateListBlock `inputs` array. Only `list` and `key` are valid ids.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{"properties": {"id": {"enum": ["list", "key"]}}},
{
"if": {"required": ["id"], "properties": {"id": {"const": "list"}}},
"then": {"properties": {"type": {"const": "string"}, "value": {"type": ["string", "null"]}}}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "key"}}},
"then": {"properties": {"type": {"const": "field"}, "value": {"type": ["string", "null"]}}}
}
]
},
"DeduplicateListSetting": {
"description": "An entry in a DeduplicateListBlock `settings` array. Only `case_insensitive` and `automations_censor_data` are valid ids.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/SettingCommon"},
{"properties": {"id": {"enum": ["case_insensitive", "automations_censor_data"]}}},
{
"if": {"required": ["id"], "properties": {"id": {"const": "case_insensitive"}}},
"then": {
"properties": {"type": {"const": "checkbox"}, "value": {"type": ["boolean", "null"]}}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "automations_censor_data"}}},
"then": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
]
},
"CompareListsBlock": {
"description": "Matches two lists against each other and returns either what they have in common or what only the first one has. Use it to work out what changed between two sets - which users are new, which records are missing on the other side. Default block name: compareLists.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/BlockBase"},
{
"if": {
"required": ["settings"],
"properties": {
"settings": {
"type": "array",
"contains": {
"required": ["id", "value"],
"properties": {"id": {"const": "compare_mode"}, "value": {"const": "diff"}}
}
}
}
},
"then": {
"$comment": "compare_mode=diff: the intersect-only settings are not exported.",
"properties": {
"settings": {
"not": {
"contains": {
"required": ["id"],
"properties": {"id": {"enum": ["result_mode", "on_existing_item_key"]}}
}
}
}
}
}
},
{
"if": {
"required": ["settings"],
"properties": {
"settings": {
"type": "array",
"contains": {
"required": ["id", "value"],
"properties": {"id": {"const": "compare_mode"}, "value": {"const": "intersect"}}
}
}
}
},
"then": {
"$comment": "compare_mode=intersect: result_mode is exported (the worker defaults it to list1 when absent).",
"properties": {
"settings": {"contains": {"required": ["id"], "properties": {"id": {"const": "result_mode"}}}}
}
}
},
{
"if": {
"required": ["settings"],
"properties": {
"settings": {
"type": "array",
"contains": {"required": ["id"], "properties": {"id": {"const": "on_existing_item_key"}}}
}
}
},
"then": {
"$comment": "on_existing_item_key is only meaningful while merging an intersect. The editor's own test for it reads settings[3] (case_insensitive) instead of settings[4] (result_mode), an off-by-one that keeps the field hidden in the UI - but CollectionIntersect::createMergeGenerator does read it, so authored workspaces may set it.",
"properties": {
"settings": {
"allOf": [
{
"contains": {
"required": ["id", "value"],
"properties": {"id": {"const": "compare_mode"}, "value": {"const": "intersect"}}
}
},
{
"contains": {
"required": ["id", "value"],
"properties": {"id": {"const": "result_mode"}, "value": {"const": "merge"}}
}
}
]
}
}
}
}
],
"required": ["type", "inputs", "settings"],
"properties": {
"type": {"const": "CompareListsBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"$ref": "#/$defs/MergeListsInput"},
"allOf": [
{"contains": {"required": ["id"], "properties": {"id": {"const": "list1"}}}},
{"contains": {"required": ["id"], "properties": {"id": {"const": "list2"}}}}
]
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/CompareListsSetting"},
"allOf": [
{"contains": {"required": ["id"], "properties": {"id": {"const": "compare_mode"}}}},
{
"contains": {"required": ["id"], "properties": {"id": {"const": "list1_unique_key"}}}
},
{
"contains": {"required": ["id"], "properties": {"id": {"const": "list2_unique_key"}}}
}
]
}
}
},
"CompareListsSetting": {
"description": "An entry in a CompareListsBlock `settings` array. Note `on_existing_item_key` allows only `keep_list1` and `keep_both` here - unlike MergeListsBlock, which also allows `keep_list2` - and the worker treats anything other than `keep_list1` as keep-both.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/SettingCommon"},
{
"properties": {
"id": {
"enum": [
"list1_unique_key",
"list2_unique_key",
"compare_mode",
"case_insensitive",
"result_mode",
"on_existing_item_key",
"automations_censor_data"
]
}
}
},
{
"if": {
"required": ["id"],
"properties": {"id": {"enum": ["list1_unique_key", "list2_unique_key"]}}
},
"then": {"properties": {"type": {"const": "field"}, "value": {"type": ["string", "null"]}}}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "compare_mode"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["diff", "intersect"]}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "result_mode"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["list1", "merge"]}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "on_existing_item_key"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["keep_list1", "keep_both"]}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "case_insensitive"}}},
"then": {
"properties": {"type": {"const": "checkbox"}, "value": {"type": ["boolean", "null"]}}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "automations_censor_data"}}},
"then": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
]
},
"ReplaceFieldNamesBlock": {
"description": "Renames the fields on every record of a list, and can drop the ones you do not name. Use it to reshape data into the field names another system expects, or to keep only the handful of fields you actually need. Default block name: replaceFieldNamesInList.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs", "settings"],
"properties": {
"type": {"const": "ReplaceFieldNamesBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"$ref": "#/$defs/ReplaceFieldNamesInput"},
"allOf": [
{"contains": {"required": ["id"], "properties": {"id": {"const": "list"}}}},
{"contains": {"required": ["id"], "properties": {"id": {"const": "mapping"}}}}
]
},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/ReplaceFieldNamesSetting"},
"contains": {"required": ["id"], "properties": {"id": {"const": "keepOtherFields"}}}
}
}
},
"ReplaceFieldNamesInput": {
"description": "An entry in a ReplaceFieldNamesBlock `inputs` array. Only `list` and `mapping` are valid ids.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{"properties": {"id": {"enum": ["list", "mapping"]}}},
{
"if": {"required": ["id"], "properties": {"id": {"const": "list"}}},
"then": {"properties": {"type": {"const": "string"}, "value": {"type": ["string", "null"]}}}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "mapping"}}},
"then": {
"properties": {
"type": {"const": "object"},
"mode": {"type": "string", "enum": ["keyValue", "raw"]},
"value": {
"type": ["array", "null"],
"items": {
"type": "object",
"required": ["key", "value"],
"additionalProperties": true,
"properties": {
"key": {"type": "string", "minLength": 1},
"value": {"type": "string", "minLength": 1}
}
},
"description": "Array of { key: old field name, value: new field name } entries. Both sides are required and plain text - formulas are disabled on this input."
}
}
}
}
]
},
"ReplaceFieldNamesSetting": {
"description": "An entry in a ReplaceFieldNamesBlock `settings` array. Only `keepOtherFields` and `automations_censor_data` are valid ids. Note `keepOtherFields` is camelCase, unlike the snake_case ids on every other block.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/SettingCommon"},
{"properties": {"id": {"enum": ["keepOtherFields", "automations_censor_data"]}}},
{
"if": {"required": ["id"], "properties": {"id": {"const": "keepOtherFields"}}},
"then": {
"required": ["value"],
"properties": {
"type": {"const": "select"},
"value": {"type": "string", "enum": ["remove", "keep"]}
}
}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "automations_censor_data"}}},
"then": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
]
},
"TransformListBlock": {
"description": "Rebuilds a list into a new shape, deciding what each record should contain and where each value comes from. Use it when a payload needs restructuring rather than just renaming - flattening nested data, combining fields, or computing a value per record. Default block name: transformList.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "TransformListBlock"},
"loopBlockId": {"type": ["string", "null"]},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"$ref": "#/$defs/TransformListInput"},
"allOf": [
{"contains": {"required": ["id"], "properties": {"id": {"const": "list"}}}},
{"contains": {"required": ["id"], "properties": {"id": {"const": "mapping"}}}}
]
},
"settings": {
"type": "array",
"items": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
}
},
"TransformListInput": {
"description": "An entry in a TransformListBlock `inputs` array. Only `list` and `mapping` are valid ids.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{"properties": {"id": {"enum": ["list", "mapping"]}}},
{
"if": {"required": ["id"], "properties": {"id": {"const": "list"}}},
"then": {"properties": {"type": {"const": "string"}, "value": {"type": ["string", "null"]}}}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "mapping"}}},
"then": {
"properties": {
"type": {"const": "object"},
"mode": {"type": "string", "enum": ["keyValue", "raw"]},
"value": {
"type": ["array", "null"],
"items": {
"type": "object",
"required": ["key", "value"],
"additionalProperties": true,
"properties": {"key": {"type": "string"}, "value": {"type": ["string", "null"]}}
},
"description": "Array of { key: output field name, value: expression } entries. Neither side is flagged required by the editor, and values may be formulas referencing the current item."
}
}
}
}
]
},
"LimitBlock": {
"description": "Keeps only the first few records of a list. Use it to cap how much work follows, or to try an automation on a small sample before letting it loose on everything. Default block name: limit.",
"allOf": [{"$ref": "../workspace-schema.json#/$defs/BlockBase"}],
"required": ["type", "inputs"],
"properties": {
"type": {"const": "LimitBlock"},
"inputs": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"$ref": "#/$defs/LimitBlockInput"},
"allOf": [
{"contains": {"required": ["id"], "properties": {"id": {"const": "list"}}}},
{"contains": {"required": ["id"], "properties": {"id": {"const": "limit"}}}}
]
},
"settings": {
"type": "array",
"items": {"$ref": "../workspace-schema.json#/$defs/CensorDataSetting"}
}
}
},
"LimitBlockInput": {
"description": "An entry in a LimitBlock `inputs` array. Only `list` and `limit` are valid ids, and both are string inputs.",
"allOf": [
{"$ref": "../workspace-schema.json#/$defs/InputCommon"},
{
"properties": {
"id": {"enum": ["list", "limit"]},
"type": {"const": "string"},
"value": {"type": ["string", "null"]}
}
}
]
}
}
}
assets/schemas/workspace-schema.json
{
"$id": "https://qlik.com/schemas/automations/workspace-schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Qlik Automate Workspace Schema",
"description": "Defines the structure of an automation workspace. Use it to construct the workspace JSON for a Qlik Automate Automation, and to confirm the result is well-formed.",
"type": "object",
"required": ["blocks", "variables"],
"additionalProperties": true,
"properties": {
"blocks": {
"type": "array",
"minItems": 1,
"description": "Every block is validated by `Block`: it must satisfy BlockBase, and if its `type` is a known block it must also satisfy that type's schema. Exactly one block must be a StartBlock.",
"items": {"$ref": "#/$defs/Block"},
"contains": {"properties": {"type": {"const": "StartBlock"}}, "required": ["type"]},
"minContains": 1,
"maxContains": 1
},
"variables": {"type": "array", "items": {"$ref": "#/$defs/Variable"}}
},
"$defs": {
"Uuid": {
"type": "string",
"pattern": "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$"
},
"BlockId": {
"description": "Block id. Uppercase UUIDv4 in production.",
"type": "string",
"pattern": "^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$"
},
"KnownBlockType": {
"description": "Block types that have a dedicated schema. Any other type is validated against BlockBase only.",
"enum": [
"StartBlock",
"EndpointBlock",
"SnippetBlock",
"IfElseBlock",
"CaseBlock",
"LabelBlock",
"GotoBlock",
"ForEachBlock",
"FilterListBlock",
"SleepBlock",
"ShowBlock",
"StopBlock",
"FormBlock",
"VariableBlock",
"UpdateJobBlock",
"CallUrlBlock",
"CallWidgetBlock",
"ErrorBlock",
"MergeListsBlock",
"LookupItemBlock",
"DeduplicateListBlock",
"CompareListsBlock",
"ReplaceFieldNamesBlock",
"TransformListBlock",
"LimitBlock",
"ListPointerBlock",
"UpdatePointerBlock",
"DeletePointerBlock",
"CompareObjectBlock",
"RedirectBlock",
"ForEachBatchBlock",
"ForEachBreakBlock",
"CustomCodeBlock3",
"OpenFileBlock",
"CreateFileBlock",
"CopyFileBlock",
"ReadDataFromFileBlock",
"WriteLineToFileBlock",
"CloseFileBlock",
"DeleteFileBlock",
"MoveFileBlock",
"CheckFileExistsBlock",
"ListFilesBlock"
]
},
"EditorType": {
"description": "Editor type tag for inputs and settings. `table` and `number` are Variable types, not input types - see Variable.type.",
"type": "string",
"enum": [
"string",
"select",
"checkbox",
"date",
"json",
"object",
"list",
"longtext",
"custom",
"field",
"code",
"documentation",
"api-search",
"file"
]
},
"BlendrOnError": {
"type": "string",
"enum": ["ignore", "warning", "stop"],
"description": "Allowed on-error values. `continue` is rejected by import."
},
"Variable": {
"description": "Top-level variable declaration. Authored workspaces emit guid, name and type only - initial state is seeded by a VariableBlock in the flow, not by a value here.",
"type": "object",
"required": ["guid", "name", "type"],
"additionalProperties": true,
"properties": {
"guid": {"$ref": "#/$defs/Uuid"},
"name": {"type": "string", "minLength": 1},
"type": {"type": "string", "enum": ["string", "list", "table", "object", "number"]},
"value": {}
}
},
"InputCommon": {
"description": "Shared shape for an entry in `inputs`. `id` matches a catalog input guid for connector blocks, or a built-in id (`run_mode`, `conditions`, `expression`, `statements`, `name`, `label`, `input`, `time`, `schedule_*`, `datasourcetype_guid`, `webhook_event_guid`, `test_payload`, or `webhook_param_<guid>`).",
"type": "object",
"required": ["id", "type"],
"properties": {
"id": {"type": "string", "minLength": 1},
"value": {},
"type": {"$ref": "#/$defs/EditorType"},
"structure": {"type": ["array", "object"]},
"mode": {"type": "string"},
"displayValue": {"type": "string"}
},
"additionalProperties": true
},
"SettingCommon": {
"type": "object",
"required": ["id"],
"properties": {
"id": {"type": "string", "minLength": 1},
"value": {},
"type": {"$ref": "#/$defs/EditorType"},
"structure": {"type": ["array", "object"]}
},
"additionalProperties": true
},
"DatasourceSetting": {
"description": "Connection bound to a connector block. `value` is a connection id from qlik_list_automation_connections (filter by connectorId), or null when the tenant has no matching connection. The REST create/update path stores this verbatim and never auto-binds a null value - only the editor's JSON-import pipeline (CleanDatasourcesPipe) rewrites it, stripping guids that are not connections of the importing account.",
"allOf": [{"$ref": "#/$defs/SettingCommon"}],
"properties": {"id": {"const": "datasource"}, "type": {"const": "select"}, "value": {"type": ["string", "null"]}}
},
"CensorDataSetting": {
"description": "Exclude the block's inputs and outputs from run logs. This is a checkbox, so `type` is `checkbox` and `value` is a boolean - not a select over \"yes\"/\"no\".",
"allOf": [{"$ref": "#/$defs/SettingCommon"}],
"required": ["value"],
"properties": {
"id": {"const": "automations_censor_data"},
"type": {"const": "checkbox"},
"value": {"type": "boolean"}
}
},
"BlendrOnErrorSetting": {
"allOf": [{"$ref": "#/$defs/SettingCommon"}],
"required": ["value"],
"properties": {
"id": {"const": "blendr_on_error"},
"type": {"const": "select"},
"value": {"$ref": "#/$defs/BlendrOnError"}
}
},
"ConnectorSetting": {
"description": "An entry in an EndpointBlock / SnippetBlock `settings` array. Any setting id is allowed, but the three with fixed shapes (`datasource`, `blendr_on_error`, `automations_censor_data`) are held to them.",
"allOf": [
{"$ref": "#/$defs/SettingCommon"},
{
"if": {"required": ["id"], "properties": {"id": {"const": "datasource"}}},
"then": {"$ref": "#/$defs/DatasourceSetting"}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "blendr_on_error"}}},
"then": {"$ref": "#/$defs/BlendrOnErrorSetting"}
},
{
"if": {"required": ["id"], "properties": {"id": {"const": "automations_censor_data"}}},
"then": {"$ref": "#/$defs/CensorDataSetting"}
}
]
},
"CollapsedEntry": {
"type": "object",
"required": ["name", "isCollapsed"],
"additionalProperties": false,
"properties": {"name": {"type": "string", "minLength": 1}, "isCollapsed": {"type": "boolean"}}
},
"BlockBase": {
"description": "Fields shared by every block.",
"type": "object",
"required": ["id", "type", "name", "inputs"],
"properties": {
"id": {"$ref": "#/$defs/BlockId"},
"type": {"type": "string"},
"name": {"type": "string", "minLength": 1},
"displayName": {"type": "string"},
"comment": {"type": ["string", "null"]},
"disabled": {"type": "boolean"},
"collapsed": {"type": "array", "items": {"$ref": "#/$defs/CollapsedEntry"}},
"inputs": {"type": "array", "items": {"$ref": "#/$defs/InputCommon"}},
"settings": {"type": "array", "items": {"$ref": "#/$defs/SettingCommon"}},
"childId": {"type": ["string", "null"]}
},
"additionalProperties": true
},
"Block": {
"description": "Validates any block: it must satisfy BlockBase AND either match one of the known block schemas or carry a type outside the known set (in which case BlockBase alone applies).",
"allOf": [
{"$ref": "#/$defs/BlockBase"},
{
"anyOf": [
{"$ref": "#/$defs/StartBlock"},
{"$ref": "#/$defs/EndpointBlock"},
{"$ref": "#/$defs/SnippetBlock"},
{"$ref": "blocks/basic.json#/$defs/IfElseBlock"},
{"$ref": "blocks/advanced.json#/$defs/CaseBlock"},
{"$ref": "blocks/advanced.json#/$defs/LabelBlock"},
{"$ref": "blocks/advanced.json#/$defs/GotoBlock"},
{"$ref": "blocks/basic.json#/$defs/ForEachBlock"},
{"$ref": "blocks/lists.json#/$defs/FilterListBlock"},
{"$ref": "blocks/basic.json#/$defs/SleepBlock"},
{"$ref": "blocks/basic.json#/$defs/ShowBlock"},
{"$ref": "blocks/basic.json#/$defs/StopBlock"},
{"$ref": "blocks/basic.json#/$defs/FormBlock"},
{"$ref": "blocks/basic.json#/$defs/VariableBlock"},
{"$ref": "blocks/advanced.json#/$defs/UpdateRunTitleBlock"},
{"$ref": "blocks/basic.json#/$defs/CallUrlBlock"},
{"$ref": "blocks/basic.json#/$defs/CallWidgetBlock"},
{"$ref": "blocks/basic.json#/$defs/ErrorBlock"},
{"$ref": "blocks/lists.json#/$defs/MergeListsBlock"},
{"$ref": "blocks/lists.json#/$defs/LookupItemBlock"},
{"$ref": "blocks/lists.json#/$defs/DeduplicateListBlock"},
{"$ref": "blocks/lists.json#/$defs/CompareListsBlock"},
{"$ref": "blocks/lists.json#/$defs/ReplaceFieldNamesBlock"},
{"$ref": "blocks/lists.json#/$defs/TransformListBlock"},
{"$ref": "blocks/lists.json#/$defs/LimitBlock"},
{"$ref": "blocks/advanced.json#/$defs/ListPointerBlock"},
{"$ref": "blocks/advanced.json#/$defs/UpdatePointerBlock"},
{"$ref": "blocks/advanced.json#/$defs/DeletePointerBlock"},
{"$ref": "blocks/advanced.json#/$defs/CompareObjectBlock"},
{"$ref": "blocks/advanced.json#/$defs/RedirectBlock"},
{"$ref": "blocks/advanced.json#/$defs/ForEachBatchBlock"},
{"$ref": "blocks/advanced.json#/$defs/ForEachBreakBlock"},
{"$ref": "blocks/advanced.json#/$defs/CustomCodeBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/OpenFileBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/CreateFileBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/CopyFileBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/ReadDataFromFileBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/WriteLineToFileBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/CloseFileBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/DeleteFileBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/MoveFileBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/CheckFileExistsBlock"},
{"$ref": "blocks/cloud-storage.json#/$defs/ListFilesBlock"},
{"not": {"properties": {"type": {"$ref": "#/$defs/KnownBlockType"}}}}
]
}
]
},
"IfElseConditionEntry": {
"type": "object",
"required": ["input1", "operator"],
"additionalProperties": false,
"properties": {
"input1": {"type": "string"},
"input2": {"type": ["string", "null"]},
"operator": {
"type": "string",
"enum": [
"=",
"!=",
">",
">=",
"<",
"<=",
"empty",
"notEmpty",
"contain",
"doesntContain",
"inList",
"notInList",
"isText",
"isNotText",
"isNumber",
"isNotNumber",
"isObject",
"isNotObject",
"isList",
"isNotList",
"isEmail",
"isNotEmail",
"isPhone",
"isNotPhone",
"isTrue",
"isFalse"
]
}
}
},
"ConditionGroup": {
"description": "Value of a `conditions` input. Shared by IfElseBlock and FilterListBlock. `mode: all` ANDs the entries, `mode: some` ORs them.",
"type": "object",
"required": ["mode", "conditions"],
"additionalProperties": false,
"properties": {
"mode": {"type": "string", "enum": ["all", "some"]},
"conditions": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/IfElseConditionEntry"}}
}
},
"StartBlock": {
"description": "Starts the automation and sets how it is triggered. `run_mode` comes first and is one of `manual`, `scheduled`, `triggered` or `webhook`. Use `triggered` when something outside the automation starts it - an Answers assistant action, Qlik MCP, a sheet button or the run URL - and add an `async` input, where `no` sends the run output back to the caller. A webhook trigger also needs the connector, the event, and one input per event parameter; a scheduled trigger also needs its start, stop time, interval and timezone. Every workspace has exactly one. Default block name: Start.",
"allOf": [{"$ref": "#/$defs/BlockBase"}],
"required": ["type"],
"properties": {
"type": {"const": "StartBlock"},
"inputs": {
"type": "array",
"minItems": 1,
"prefixItems": [
{
"allOf": [{"$ref": "#/$defs/InputCommon"}],
"required": ["value"],
"properties": {
"id": {"const": "run_mode"},
"type": {"const": "select"},
"value": {"type": "string", "enum": ["manual", "scheduled", "triggered", "webhook"]}
}
}
]
}
}
},
"EndpointBlock": {
"description": "Calls one action on a connector - the main way an automation talks to Qlik or an outside service. `datasourcetype_guid` is the connector and `endpoint_guid` is the action; take both from the connector detail, and list the action's inputs in the order the connector gives them. Bind the `datasource` setting to a matching connection id from qlik_list_automation_connections, or null when the tenant has none.",
"allOf": [{"$ref": "#/$defs/BlockBase"}],
"required": ["type", "datasourcetype_guid", "endpoint_guid"],
"properties": {
"type": {"const": "EndpointBlock"},
"datasourcetype_guid": {"$ref": "#/$defs/Uuid"},
"endpoint_guid": {"$ref": "#/$defs/Uuid"},
"endpoint_role": {"type": "string", "minLength": 1},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/ConnectorSetting"},
"contains": {"$ref": "#/$defs/BlendrOnErrorSetting"}
}
}
},
"SnippetBlock": {
"description": "Runs one ready-made routine from a connector, as opposed to the single action an EndpointBlock calls. `datasourcetype_guid` is the connector and `snippet_guid` is the routine; take both from the connector detail, and list its inputs in the order the connector gives them. Bind the `datasource` setting to a matching connection id from qlik_list_automation_connections, or null when the tenant has none.",
"allOf": [{"$ref": "#/$defs/BlockBase"}],
"required": ["type", "datasourcetype_guid", "snippet_guid"],
"properties": {
"type": {"const": "SnippetBlock"},
"datasourcetype_guid": {"$ref": "#/$defs/Uuid"},
"snippet_guid": {"$ref": "#/$defs/Uuid"},
"settings": {
"type": "array",
"items": {"$ref": "#/$defs/ConnectorSetting"},
"contains": {"$ref": "#/$defs/BlendrOnErrorSetting"}
}
}
}
}
}
SKILL.md
---
name: qlik-automation-builder
description: >
Build automations for Qlik Automate. Translate natural-language requests into automation workspace JSON and, when requested, create, update, or delete automations using the automation management tools exposed through qlik-mcp.
license: Apache-2.0
allowed-tools: qlik_skill_view qlik_search qlik_create_automation qlik_update_automation qlik_delete_automation qlik_get_automation_by_id qlik_list_automation_connectors qlik_get_automation_connector qlik_list_automation_connections bash
metadata:
author: yeshQ
version: 0.1.0
tags:
- qlik
- analytics
- workflow-automation
---
# Create Automation Skill
Build automation `workspace` JSON, then create, update, or delete automations via qlik-mcp tools.
## Scope
When the platform the automation needs to reach has no dedicated connector, or the resolved connector does not expose the needed block, do **not** stop — fall back to the generic connectors or the built-in `CallUrlBlock` as long as the platform has an HTTP API. See [No dedicated block: generic connectors and raw API](#no-dedicated-block-generic-connectors-and-raw-api). Stop and tell the user the skill cannot help only when even that fallback cannot express the request (no HTTP API, or an auth flow the generic connectors do not support).
Never draft scheduled, triggered, or webhook automations. StartBlock must always use run_mode: "manual".
## Workflow
### 1. Discover connectors and pick blocks via qlik-mcp tools
1. Resolve connectors via the MCP tool `qlik_list_automation_connectors`. It returns the live tenant connector list, each entry carrying `id`, `name`, `description` and other connector related information. Reference the returned `id` as `datasourcetype_guid` on connector blocks. ex: Filter syntax: `filter='name eq "Qlik Cloud Services"'`.
- If the `qlik-mcp` is not connected, stop and ask the user to connect it. Do not guess or hardcode connector guids.
2. Fetch full connector detail (blocks, parameters, snippet templates) with `qlik_get_automation_connector(connector_id=<connector-guid>)`. Each entry in `blocks` carries `id` (used as `endpoint_guid` / `snippet_guid`), `name`, `description`, `snippet` (`true` = `SnippetBlock`, `false` = `EndpointBlock`), `role`, `objectType`, and `inputs[]` (per-input `id`, `name`, `fieldType`, `optional`, `description`, `options`). On import the editor rebuilds each input list from connector metadata and pairs values by `id`, so emit every `id` exactly as returned — one missing or misspelled lands as an empty input. Filter blocks in-memory after fetching (e.g. `snippet == false and name matches /reload/i` for endpoint blocks, or pick an `id` and read its `inputs`).
3. For external connector blocks (any block that carries a `datasource` setting — QCS-native blocks don't), resolve the connection: call `qlik_list_automation_connections` with `filter='connectorId eq "<connector-guid>"'`, once per distinct connector. Exactly one match → bind its `id` as the block's `datasource` value; warn when it reports `isConnected: false` (runs will fail at the service until the user reconnects it). Several matches → ask the user which to bind. None → leave `value: null` and record in `Assumptions` that the user must create and bind a connection before execution. For cloud-storage file blocks there is no `datasourcetype_guid` — their `datasourcetype` is an adapter name — so first get the connector guid with `qlik_list_automation_connectors` using `filter='name eq "<adapter name>"'` (the adapter name is the connector's catalog name), then resolve the connection as above. The create/update API stores `datasource` verbatim — a null is never auto-bound on import, so an unbound block stays unbound.
4. If no connector or block fits, don't stop — see [No dedicated block: generic connectors and raw API](#no-dedicated-block-generic-connectors-and-raw-api).
### 2. Build workspace JSON
1. Pick block types and confirm their shapes via `$defs`. Each block type's entry opens with what it is for and when to reach for it — read a candidate's description before picking it (that selection guide covers every supported type), then confirm required top-level fields, `inputs[*]` shape constraints, and any conditional rules. To find an entry, look up the type in `$defs.Block` in `assets/schemas/workspace-schema.json`: every type is listed there as a `$ref` pointing at its definition, either in the same file or in a `blocks/` category file — e.g. `{"$ref": "blocks/basic.json#/$defs/IfElseBlock"}` means read `assets/schemas/blocks/basic.json`. Open only the files the draft needs.
- Prefer a `SnippetBlock` over a hand-built equivalent when the connector ships one, and do not inline-expand a snippet into its constituent blocks unless the user explicitly asks.
- Add a `FormBlock` only when the flow genuinely needs runtime user input — an interactive caller supplying parameters, not a value you could resolve while drafting.
2. Draft `workspace` JSON. Fill only `value` fields, names, comments, and edges. Use the connector-detail `inputs[*].id` as `inputs[*].id` on the workspace block. Translate `fieldType` to the schema's `type` using the [fieldType mapping](#fieldtype-to-input-type-mapping).
3. Validate the draft — see [Validation](#validation).
4. Return the result — see [Return format](#return-format).
If the request is underspecified, ask only for blocking business values (app ids, recipient ids, message text). Do not ask questions the connector detail can answer.
Decide from the connector-detail `role` whether a chosen block owns an internal loop, and wire accordingly — see [Iterable blocks and loops](#iterable-blocks-and-loops).
If you don't understand how blocks should be wired, ask rather than assume.
### 3. Create, update, or delete automations via qlik-mcp tools
- `qlik_create_automation` to create a new automation (pass `workspace` with the built JSON).
- `qlik_get_automation_by_id` to inspect an automation's configuration.
- `qlik_update_automation` to change name, description, workspace, or concurrency. Do not set schedules — see [Scope](#scope).
- `qlik_delete_automation` to delete an automation.
Lifecycle guidance:
1. Resolve required ids first (automation id, connector, connection, space).
2. Validate request completeness before calling write operations.
3. For mutating operations (`create`, `update`, `delete`), echo key identifiers and resulting status.
4. Do not fabricate ids. Ask for missing ids only when they cannot be discovered from prior results.
5. After a successful `qlik_create_automation` (or `qlik_update_automation`), call `qlik_search` with `resourceType="automation"` and the automation's name as the query, match the result whose `id` equals the returned automation id, and share that result's `open` URL so the user can open the automation in the editor. Never hand-build or guess the URL — if no result matches, say the link could not be resolved and report the automation id instead.
## No dedicated block: generic connectors and raw API
When `qlik_list_automation_connectors` has no dedicated connector for the platform being called, or the resolved connector's blocks don't cover the needed operation, escalate in this order instead of stopping:
1. **Generic connector matched to how the platform's API authenticates** — ordinary connectors, so the normal workflow applies (resolve the guid from the connector list, fetch blocks with `qlik_get_automation_connector`): [`API Key`](https://community.qlik.com/t5/Official-Support-Articles/How-to-Getting-started-with-the-API-key-connector-in-Qlik/ta-p/2036563) (key sent as an HTTP header or query-string parameter), [`Basic Authentication`](https://community.qlik.com/t5/Official-Support-Articles/How-to-get-started-with-the-Basic-Authentication-connector-in/ta-p/2057190) (username/password), or [`OAuth2`](https://community.qlik.com/t5/Official-Support-Articles/How-to-Getting-started-with-the-Oauth2-connector-in-Qlik/ta-p/1880261) (authorization-code flow with automatic token refresh). Each exposes the same [raw API block family](https://community.qlik.com/t5/Official-Support-Articles/How-to-use-the-Raw-API-Request-blocks-in-Qlik-Automate/ta-p/1844965): one `raw API request` `EndpointBlock` (`path` relative to the connection's base URL, `method`, `query_parameters`, `request_body`, `headers`) for single calls, and `raw API list request - <paging>` variants (cursor-token, offset, page-number, or no paging) for lists — pick the variant matching the API's pagination and set `json_path_for_output_records` to the JSON path of the record array. Base URL and credentials live on the **connection**, not the workspace: resolve and bind the connection id like any other connector (workflow step 1.3); when the tenant has none, keep `value: null` and note in `Assumptions` that the user must create and bind a connection (connector name, auth type, credentials, base URL) before execution.
2. **Built-in [`CallUrlBlock`](https://help.qlik.com/en-US/cloud-services/Subsystems/Hub/Content/Sense_QlikAutomation/basic/call-url-block.htm)** — only for endpoints needing no authentication, or when a connection-bound connector cannot be used. Nothing signs the request; never inline secrets (API keys, passwords, tokens) into `headers` or `params` — use a generic connector for authenticated APIs.
Tell the user which fallback was chosen and why. Known limitation: the `API Key` connector cannot send extra fields in the authorize request itself; if the auth flow needs that, say so and stop.
## Workspace shape
```json
{ "blocks": [], "variables": [] }
```
- Exactly one `StartBlock`. Use uppercase UUIDv4s for block ids and variable guids. Keep block names unique and stable so JSON-path references stay readable.
- For `EndpointBlock` and `SnippetBlock`, emit the **full** visible input list from the matching connector-detail block's `inputs` array — including optional ones (`optional: true`) with `value: null`. Each `inputs[*].id` is the connector-detail `inputs[*].id`; each `inputs[*].type` is derived from `fieldType` via the [fieldType mapping](#fieldtype-to-input-type-mapping).
- Most blocks should carry `collapsed: [{ "name": "loop", "isCollapsed": false }]`. `IfElseBlock` uses `[{name:"both"}, {name:"yes"}, {name:"no"}]`. `CaseBlock` uses `[{name:"default"}]`.
- Settings to add for connector blocks: `blendr_on_error` (allowed: `ignore`, `warning`, `stop` — never `continue`; default `stop`; use `warning` on a block whose failure downstream logic inspects and classifies, `stop` everywhere else), `automations_censor_data` (default `false`), and `datasource` only when the connector persists one (Qlik Cloud Services blocks omit it; external connectors carry the connection id resolved in workflow step 1.3, or `value: null` when the tenant has no matching connection — return the workspace anyway and note that the user must bind a connection before execution). For list/search/listnew/listupdated roles also add `maxitemcount`. For listnew/listupdated also add `blendr_pointer: "yes"`.
### Variables
Variables are workspace-level named values (`string`, `number`, `list`, `object`, `table`) for carrying state across blocks — use one when a value must be accumulated, flagged, or read outside the block that produced it.
- Declare each variable as `{ "guid": ..., "name": ..., "type": ... }` only — never a `value`. Seed initial state (a zeroed counter, a pre-filled list) with a `VariableBlock` early in the flow: start with an `empty` operation, then the seeding operations (`set_value`, `add_item`, …). `number` variables have no `empty` — start them with `set_value`.
- Variable names: letters, digits, and underscores only, starting with a letter or underscore; unique among variables and distinct from every block name — `{$.name}` references resolve blocks and variables in one namespace, and other characters get mangled or quoted in the derived path.
- Name each `VariableBlock` exactly after its variable; repeats get `#2`, `#3`, … (this mirrors how the editor names them, so imported and authored blocks stay consistent).
- Operations must match the variable's `type` — the mapping is in the schema's `VariableOperation` description. Each operation entry carries a fresh UUIDv4 `key`, the operation `id`, its editor label as `name`, and a `value` (except `empty`).
- Accumulate results in `list` variables. Avoid `table` variables and `add_row` in authored workspaces — the editor UI can configure them, but authored rows do not reliably survive the runtime.
### References in input values
- `{$.blockName}` or `{$.blockName.field}` references an earlier block. Use this exact form — no spaces inside the braces.
- When the referenced block returns a **list** and the referencing block sits inside that list's loop, reference the current element with `item` — not the list itself. `{$.listApps.item}` is the element the iteration is on and `{$.listApps.item.<field>}` is one field of it, while `{$.listApps}` is the whole list. This applies both to blocks in a `ForEachBlock` body and to blocks placed in an `EndpointBlock` / `SnippetBlock` internal loop via `loopBlockId`. Emit the bare `{$.blockName}` form only where a whole list is genuinely wanted — e.g. the `input` of a `ForEachBlock` that runs *after* the producing block rather than inside its loop. Where a whole list is wanted, the reference must be the input's **entire** value — never embed a list reference inside a longer string (concatenation flattens the list into text).
- `<field>` is not a fixed choice — never default to `id`. Take it from what the user asked to chain (an id, a name, an email, a url, a nested path like `owner.email`) and confirm the list element actually exposes it. If the request doesn't name a field, or the element's shape doesn't make the right one unambiguous, stop and ask the user which field to chain instead of guessing. Reference the whole element as `{$.blockName.item}` only when the downstream input genuinely takes the full object.
- `VariableBlock` outputs use the **variable name**, not the block name. If `variables[*].name = "reload_failed"`, use `{$.reload_failed}` — not `{$.setFailureFlag1}`.
- `{explode: {$.inputs.reload_ids}, ','}` for explode helpers.
### Iterable blocks and loops
A block is **iterable** — it owns a loop body and accepts `loopBlockId` — by block type, not by the shape of its output. Never infer it from a `collapsed` entry: a `loop` entry is the editor's default for every block except `IfElseBlock` and `CaseBlock`, iterable or not, and an authored `collapsed` array is stored verbatim, so it says nothing about iterability.
- `EndpointBlock` / `SnippetBlock`: iterable when the connector-detail `role` is `list`, `search`, `listnew`, or `listupdated` — the same four roles that get a `maxitemcount` setting. Every other role (`get`, `create`, `update`, `delete`, anything else, or none) is **not** iterable.
- Always iterable regardless of role or inputs: `ForEachBlock`, `ForEachBatchBlock`, `FilterListBlock`, `TransformListBlock`, `DeduplicateListBlock`, `MergeListsBlock`, `CompareListsBlock`, `ReplaceFieldNamesBlock`, `ListFilesBlock`, `ReadDataFromFileBlock`.
- Never iterable, despite being list blocks: `LookupItemBlock` (returns one matched item) and `LimitBlock`.
- Connector-specific block types outside this schema's `KnownBlockType` list (legacy FTP/SFTP/S3 file blocks, data-store list blocks) can be iterable independently of their role. Don't author those types; prefer the generic cloud-storage blocks above.
Consequences for the draft:
- Set `loopBlockId` only on an iterable block. A non-iterable block keeps the field in memory but never renders a loop body, and the editor omits `loopBlockId` when serializing it — so the chain hanging off it is invisible on the canvas and lost the first time the automation is saved from the editor.
- On an `EndpointBlock`, emit `endpoint_role` with the connector-detail `role`. It is the only role the editor has until the connector metadata request resolves, so without it the block counts as non-iterable in that window — and anything that serializes the workspace before the metadata lands drops its `loopBlockId`.
- Wiring a per-item action off a list: put it inside the producer's own loop via `loopBlockId` — no `ForEachBlock` needed. Reach for a separate `ForEachBlock` chained via `childId` only when the producer is not iterable, when the list is a variable or an earlier block's output rather than the immediate parent, or when work must run once over the whole list before the iteration.
- Inside a loop, reference the current element through `item`, per [References in input values](#references-in-input-values).
### Edges and parent uniqueness
Edge fields: `childId`, `childTrueId`, `childFalseId`, `loopBlockId`, `statementChildrenIds`, `defaultChildId`. All edge values must reference an existing block id or be `null`.
A block id may appear **only once** as a target across all edge fields in the whole workspace. Merge-style rejoin wiring is invalid for import even when the runtime logic looks fine.
`GotoBlock` jumps via `inputs[].id = "label"`, not via an edge field. `LabelBlock`s reached only via `GotoBlock` may be intentionally disconnected from the `StartBlock` chain.
#### Branch recipe
Converging several branches on one later block is the usual way parent uniqueness gets violated. Instead: declare a top-level variable, set it with a `VariableBlock` inside each branch that should trigger the later action, continue the main sequence through `IfElseBlock.childId` rather than through branch leaves, then read the variable once in a final `IfElseBlock` and hang the action off its `childTrueId`.
So `checkReload1.childFalseId` and `checkReload2.childFalseId` both pointing at `sendFailureMessage` is invalid; each pointing at its own `VariableBlock` that sets `reload_failed = true` is not.
### fieldType to input type mapping
The connector detail describes inputs with `fieldType`; the schema's `inputs[*].type` uses editor types. The two vocabularies do not overlap and no connector-detail input reports its editor type, so this translation cannot be looked up at runtime — it has to live here.
| connector-detail `fieldType` | schema `inputs[*].type` | notes |
|---|---|---|
| `string`, `input`, `int`, `json`, `json_list` | `string` | The wrapper type is `string`; the agent still writes a stringified JSON list for `json_list`. |
| `enum`, `select` | `select` | Allowed values come from the connector-detail `options` array — but roughly half of QCS `select` inputs return `options: []`, and then the input's `description` is what states the default and the alternatives. |
| `object`, `json_object` | `object` | Add `"mode": "keyValue"` on the input alongside `type`. |
| `longtext`, `textarea` | `longtext` | Multi-line text. |
| `file` | `file` | File payload passed from an upstream block, e.g. a cloud-storage `Open File` output. |
| `date` | `date` | |
The remaining `EditorType` values — `checkbox`, `list`, `custom`, `field`, `code`, `documentation`, `api-search` — are never the target of this translation. They belong to built-in block inputs and connector settings (`conditions` is `custom`, `automations_censor_data` is `checkbox`), not to connector-detail inputs.
## Validation
Validate every draft with [ajv-cli](https://github.com/ajv-validator/ajv-cli) before returning it as final. No project-level install is needed — invoke through `npx`:
```bash
npx -y -p ajv-cli@5 -p ajv-formats@3 ajv validate \
-s assets/schemas/workspace-schema.json \
-r 'assets/schemas/blocks/*.json' \
-d path/to/workspace.json \
--spec=draft2020 --strict=false -c ajv-formats
```
- The schema is JSON Schema **Draft 2020-12** (hence `--spec=draft2020 --strict=false`) and split across a core file plus four category files under `assets/schemas/blocks/`, which must be loaded with `-r`.
- Success output is `path/to/workspace.json valid`. Anything else (including `invalid` plus an error list) means fix the draft and re-validate before responding.
- Omitting `-r` is not a lenient run — `ajv` reports `schema assets/schemas/workspace-schema.json is invalid / error: can't resolve reference blocks/basic.json#/$defs/IfElseBlock` instead of a verdict. That means the command was wrong, not the draft.
- Validate several drafts in one call with multiple `-d` flags or a glob (`-d 'examples/*.workspace.json'`); only `-d` varies.
- If `npx` cannot reach the registry, install once (`npm i -g ajv-cli@5 ajv-formats@3`) and run `ajv validate` with the same flags, `-r` included.
The validator reports schema violations only. Cross-cutting rules above (parent uniqueness, `statementChildrenIds` length, real connector-detail guids) are not expressible in JSON Schema and must still be checked in `Preflight`.
## JSON output rules
These rules govern the workspace JSON emitted in the response. Violating any of them produces malformed or unimportable output.
- The workspace is **one** JSON object with exactly one top-level `blocks` array and one top-level `variables` array, emitted as **exactly one** fenced ` ```json ` block per response — never split across code blocks or followed by a second object (`{...}{...}`). If the user asks to extend an automation, regenerate the whole workspace from scratch.
- Plain JSON only: no trailing commas, no `//` or `/* */` comments, no unquoted keys, no `undefined` / `NaN` / `Infinity`, ASCII double quotes only (no smart quotes `“ ” ‘ ’`), no em-dashes inside JSON strings unless the user asked for them in literal text.
- No placeholder strings in the final output. Replace every `BLOCK-ID`, `LABEL-BLOCK-ID`, `LOOP-BLOCK-ID`, `VARIABLE-GUID`, `<endpoint-guid>`, `OPERATION-KEY`, `VALUE-HERE`, etc. with real connector-detail guids or freshly generated uppercase UUIDv4s. If a value is genuinely unknown (e.g. connection id), use `null`, not a placeholder string.
- Do **not** copy block snippets from `$defs` examples or from prior chat output verbatim — those examples contain sentinel ids. Construct each block fresh, using connector-detail ids and your own UUIDs.
## Return format
**For workspace building:**
1. Final `workspace` JSON in one fenced ` ```json ` block.
2. `Preflight` confirming each of:
- the workspace validates against `assets/schemas/workspace-schema.json` plus `assets/schemas/blocks/*.json` (ajv-cli command in [Validation](#validation), `-r` included, returns `valid`), and its sole `StartBlock` has `inputs[0].id: "run_mode"`, `inputs[0].type: "select"`, and `inputs[0].value: "manual"`
- every `EndpointBlock` / `SnippetBlock` `inputs[*].id` matches an `inputs[*].id` for that block in the `qlik_get_automation_connector` response, with none left out — including optional inputs, which carry `value: null`
- every `EndpointBlock` / `SnippetBlock` `datasourcetype_guid` is the `id` of a connector returned by `qlik_list_automation_connectors` in this session (e.g. `61a87510-c7a3-11ea-95da-0fb0c241e75c` for Qlik Cloud Services), and matches the connector whose block detail supplied that block's `endpoint_guid` / `snippet_guid`
- every `IfElseBlock` uses `type: "custom"` with `value.mode` and at least one `value.conditions` entry
- every `CaseBlock` keeps `statementChildrenIds` aligned with `inputs['statements'].value`, and every `GotoBlock` points to a real `LabelBlock.id` with matching `displayValue`
- every `VariableBlock` is named after its variable, its `variableGuid` matches a real `variables[*].guid`, and every operation `id` is one its variable's `type` allows
- every external connector block's `datasource` value is a connection `id` returned by `qlik_list_automation_connections` this session, or `null` with a matching `Assumptions` entry
- no `inputs[*].value` in the workspace holds a literal credential (API key, password, token, secret, signed URL) — authenticated calls go through a connection-bound connector block, and any `CallUrlBlock` with `headers` or `params` sets `automations_censor_data: true`
- every `loopBlockId` sits on an iterable block — a built-in loop block, or an `EndpointBlock` / `SnippetBlock` whose connector-detail `role` is `list`, `search`, `listnew`, or `listupdated` — and each such `EndpointBlock` carries a matching `endpoint_role`
- every reference made from inside a loop to the list-producing block that owns that loop goes through `item` (`{$.blockName.item...}`), not the bare list (`{$.blockName...}`), and the field after `item` is one the user named or the element demonstrably exposes — not an assumed `id`
- no block has multiple parents
3. Short `Assumptions` section listing unresolved connections or guesses.
`Preflight` and `Assumptions` are plain Markdown **after** the JSON code block — never embed them as extra keys (e.g. `"_preflight"`) on the workspace object. If schema validation fails, or any `Preflight` item cannot be truthfully confirmed, **do not emit the JSON block** — report the failure or ask a clarifying question instead.
**For automation lifecycle operations:**
1. `Action` summary: tool called, why, and what changed.
2. `Inputs` used: ids, filters, or payload fields sent to the tool.
3. `Result`: automation id, status, and key returned fields from the tool response. For a create or update, include the editor link resolved via `qlik_search` (lifecycle guidance step 5).
4. `Next steps` only when additional input or actions are required (e.g., binding a connection before execution).