{
  "openapi": "3.0.1",
  "info": {
    "title": "Dataset Cleaner & LLM Processor: Filter, Dedupe, AI per Row",
    "description": "Clean, filter and dedupe Apify Dataset rows, then run an AI prompt on each row with no API key of your own. $1 per 1,000 rows kept, plus $1 per 1,000 AI rows, plus model usage on your Apify account.",
    "version": "0.1",
    "x-build-id": "bvozTElt1doLjjcCR"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/automa-flow~dataset-filter-transform/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-automa-flow-dataset-filter-transform",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/automa-flow~dataset-filter-transform/runs": {
      "post": {
        "operationId": "runs-sync-automa-flow-dataset-filter-transform",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/automa-flow~dataset-filter-transform/run-sync": {
      "post": {
        "operationId": "run-sync-automa-flow-dataset-filter-transform",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "datasetId": {
            "title": "Source Dataset",
            "pattern": "^(?:[A-Za-z0-9]{17})?$",
            "type": "string",
            "description": "Dataset to read. Leave this empty when you paste rows in Inline rows. The run needs read access."
          },
          "data": {
            "title": "Inline rows",
            "maxItems": 1000,
            "type": "array",
            "description": "Up to 1,000 JSON objects to clean in this run. Leave this empty when datasetId is set. For more rows, write them to a Dataset first.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "filterMode": {
            "title": "Filter mode",
            "enum": [
              "AND",
              "OR"
            ],
            "type": "string",
            "description": "AND keeps a row only when every filter matches. OR keeps a row when any filter matches. No filters keeps every row that transforms cleanly.",
            "default": "AND"
          },
          "filters": {
            "title": "Filters",
            "maxItems": 40,
            "type": "array",
            "description": "Up to 40 filters, each {\"op\", \"field\", ...}. equals, notEquals, contains, notContains, startsWith, endsWith: \"value\" to compare. gt, gte, lt, lte: \"value\" as a number, an ISO 8601 date such as \"2026-10-01\" or \"2026-10-01T08:00:00Z\", or a relative bound \"now\", \"now-7d\", \"now-24h\" or \"now-30m\" counted from the run start. A date without a time is midnight UTC. between: \"value\" [low, high] using those same numbers or dates. in, notIn: \"value\" list. regex: \"pattern\", a Python regular expression. empty, notEmpty, true, false: no value. Add \"caseSensitive\": false to compare text in any letter case. Comparisons are exact: the number 10 is not the string \"10\" until a cast transform runs. Missing fields are empty, and they are not equal to null. A dotted name such as price.value or metadata.title reads the value nested inside objects; a top-level field with that exact name wins. Filters read the names after the transforms. A name only renames moved is read under its new name; a name a drop or keep removed, or a renamed field another transform changed, fails the run before a row is read.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "required": [
                "op",
                "field"
              ],
              "properties": {
                "op": {
                  "title": "Operation",
                  "type": "string",
                  "editor": "select",
                  "description": "equals, notEquals, contains, notContains, startsWith, endsWith, regex, in, notIn, gt, gte, lt, lte, between, empty, notEmpty, true or false. gt, gte, lt, lte and between take a number or an ISO 8601 date, including now, now-7d, now-24h and now-30m.",
                  "enum": [
                    "equals",
                    "notEquals",
                    "contains",
                    "notContains",
                    "startsWith",
                    "endsWith",
                    "regex",
                    "in",
                    "notIn",
                    "gt",
                    "gte",
                    "lt",
                    "lte",
                    "between",
                    "empty",
                    "notEmpty",
                    "true",
                    "false"
                  ]
                },
                "field": {
                  "title": "Field",
                  "type": "string",
                  "editor": "textfield",
                  "description": "Field name, 1-200 characters. A dotted name such as price.value reads a nested value.",
                  "minLength": 1,
                  "maxLength": 200
                },
                "pattern": {
                  "title": "Regular expression",
                  "type": "string",
                  "editor": "textfield",
                  "description": "Python regular expression for the regex filter. 1-200 characters. It searches the field; it does not have to match the whole value.",
                  "minLength": 1,
                  "maxLength": 200
                },
                "caseSensitive": {
                  "title": "Case sensitive",
                  "type": "boolean",
                  "editor": "checkbox",
                  "description": "For equals, notEquals, contains, notContains, startsWith, endsWith, in, notIn and regex. false compares text in any letter case. Default true."
                }
              }
            }
          },
          "transforms": {
            "title": "Transforms",
            "maxItems": 40,
            "type": "array",
            "description": "Up to 40 transforms, applied in order before filters, each {\"op\", \"field\", ...}. rename: \"field\", \"to\". drop: \"field\" or \"fields\". keep: \"fields\". trim, lowercase, uppercase: \"field\" or \"fields\", or one \"field\" and \"to\" to write the result to a new field. cast: \"field\", \"to\" string, number, integer or boolean (a blank cell becomes null). default: \"field\", \"value\". regexReplace: \"field\", \"pattern\", \"replacement\" (empty removes the matches). regexExtract: \"field\", \"pattern\", optional \"to\", optional \"group\" (a group number or name; by default the first group that matched). template: \"field\", \"template\" with {{otherField}}. A transform that cannot be applied drops that row and records a code in RUN_SUMMARY. It does not write a guessed value. A dotted \"field\" such as metadata.title changes the nested value where it is, and rename moves it to the top-level name in \"to\". A field that does not exist yet, and every \"to\", is a top-level name. llm: \"prompt\" with {{field}} placeholders, and \"to\" for a text answer or \"outputFields\" for typed answers; optional \"model\" (an OpenRouter id, default openai/gpt-4o-mini), \"maxTokens\" (1-2048, default 256), \"temperature\" (0-2, default 0), \"system\" and \"onError\" (keep or drop). The model runs through apify/openrouter with no API key of your own; its usage is billed to your Apify account. One llm transform per run. Filters that read fields the llm step and later transforms do not change run before the model, so rows they remove cost no model call.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "required": [
                "op"
              ],
              "properties": {
                "op": {
                  "title": "Operation",
                  "type": "string",
                  "editor": "select",
                  "description": "rename, drop, keep, trim, lowercase, uppercase, cast, default, regexReplace, regexExtract, template or llm.",
                  "enum": [
                    "rename",
                    "drop",
                    "keep",
                    "trim",
                    "lowercase",
                    "uppercase",
                    "cast",
                    "default",
                    "regexReplace",
                    "regexExtract",
                    "template",
                    "llm"
                  ]
                },
                "field": {
                  "title": "Field",
                  "type": "string",
                  "editor": "textfield",
                  "description": "Field to read or, for template and default, the field to write.",
                  "minLength": 1,
                  "maxLength": 200
                },
                "fields": {
                  "title": "Fields",
                  "type": "array",
                  "editor": "stringList",
                  "description": "Field names for drop, keep, trim, lowercase or uppercase. At most 100. A dotted name such as metadata.title is a nested value; keep keeps it under its path.",
                  "maxItems": 100,
                  "items": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                },
                "to": {
                  "title": "Destination or cast",
                  "type": "string",
                  "editor": "textfield",
                  "description": "New field name for rename, regexExtract, or a one-field trim, lowercase or uppercase. For cast, string, number, integer or boolean. For llm, the field that gets the text answer.",
                  "minLength": 1,
                  "maxLength": 200
                },
                "pattern": {
                  "title": "Regular expression",
                  "type": "string",
                  "editor": "textfield",
                  "description": "Pattern for regexReplace or regexExtract. 1-200 characters.",
                  "minLength": 1,
                  "maxLength": 200
                },
                "replacement": {
                  "title": "Replacement",
                  "type": "string",
                  "editor": "textfield",
                  "description": "Replacement text for regexReplace, including backreferences such as \\1 or \\g<name>. JavaScript $1 and $<name> are read the same way. Empty or omitted removes the matches. Up to 2,000 characters.",
                  "maxLength": 2000
                },
                "template": {
                  "title": "Template",
                  "type": "string",
                  "editor": "textfield",
                  "description": "New field value. {{field}} inserts a scalar, also a nested one such as {{metadata.title}}. Missing fields become empty. Up to 2,000 characters.",
                  "minLength": 1,
                  "maxLength": 2000
                },
                "prompt": {
                  "title": "Prompt (llm)",
                  "type": "string",
                  "editor": "textarea",
                  "description": "For llm: the question for each row, with {{field}} placeholders, also nested ones such as {{metadata.title}}. A missing field becomes empty; an object or list is inserted as JSON. 1-8,000 characters before the row's values are filled in.",
                  "minLength": 1,
                  "maxLength": 8000
                },
                "outputFields": {
                  "title": "Output fields (llm)",
                  "type": "object",
                  "editor": "json",
                  "description": "For llm, instead of \"to\": 1-10 fields the model fills, each \"string\", \"number\", \"integer\", \"boolean\" or a list of allowed answers, for example {\"segment\": [\"B2B\", \"B2C\"], \"employees\": \"integer\"}. An answer that does not fit is a row failure, never a guessed value."
                },
                "model": {
                  "title": "Model (llm)",
                  "type": "string",
                  "editor": "textfield",
                  "description": "OpenRouter model id for llm. Default openai/gpt-4o-mini. Tested: openai/gpt-4o-mini, openai/gpt-4.1-nano, openai/gpt-4.1-mini, google/gemini-2.5-flash-lite, google/gemini-2.5-flash. Any other id runs at that model's price.",
                  "maxLength": 200,
                  "pattern": "^[a-z0-9][a-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._:~+-]*$"
                },
                "maxTokens": {
                  "title": "Max answer tokens (llm)",
                  "type": "integer",
                  "description": "Longest answer per row for llm, 1-2048. Default 256. 2,048 is the apify/openrouter limit on the FREE plan.",
                  "minimum": 1,
                  "maximum": 2048
                },
                "temperature": {
                  "title": "Temperature (llm)",
                  "type": "number",
                  "description": "Sampling temperature for llm, 0-2. Default 0, the most repeatable answer for the same row.",
                  "minimum": 0,
                  "maximum": 2
                },
                "system": {
                  "title": "System message (llm)",
                  "type": "string",
                  "editor": "textarea",
                  "description": "Optional instructions sent before every prompt, as written (no placeholders). Up to 4,000 characters.",
                  "maxLength": 4000
                },
                "onError": {
                  "title": "When the model fails (llm)",
                  "type": "string",
                  "editor": "select",
                  "enum": [
                    "keep",
                    "drop"
                  ],
                  "description": "keep (default) writes the row with the answer fields null and the code in llmError. drop leaves the row out; it is charged as read, not kept."
                }
              }
            }
          },
          "dedupeBy": {
            "title": "Remove duplicates by",
            "maxItems": 10,
            "type": "array",
            "description": "Field names that identify a row, such as email or url. After the transforms and filters, only the first row for each value is written; later rows are counted in rowsDuplicate and charged as read, not kept. Values compare exactly, so lowercase an email first. Rows whose key fields are all empty are kept. A dotted name such as metadata.title reads a nested value. Up to 10 fields.",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          },
          "maxItems": {
            "title": "Maximum kept rows",
            "minimum": 1,
            "maximum": 10000000,
            "type": "integer",
            "description": "Stop after this many kept rows. 1 to 10000000. Omit it to process the whole source. Filtered, duplicate, skipped and failed rows do not count toward this cap."
          },
          "llmConcurrency": {
            "title": "Parallel model calls",
            "minimum": 1,
            "maximum": 32,
            "type": "integer",
            "description": "How many rows are sent to the model at once, 1-32. Default 8. Lower it if the model provider throttles."
          },
          "maxLlmRows": {
            "title": "Maximum model calls",
            "minimum": 1,
            "maximum": 1000000,
            "type": "integer",
            "description": "The run sends at most this many rows to the model, 1-1000000. Default 1000. Rows past it get llmError LLM_LIMIT and are kept or dropped by onError. Identical prompts answered from the cache do not count."
          },
          "maxLlmCostUsd": {
            "title": "Maximum model usage (USD)",
            "minimum": 0.01,
            "maximum": 1000,
            "type": "number",
            "description": "Stop sending rows to the model once the usage.cost that apify/openrouter reports adds up to this amount, 0.01-1000. Default 1. Calls already in flight finish, so the total can pass it by a few calls."
          },
          "llmCache": {
            "title": "Reuse answers to identical prompts",
            "type": "boolean",
            "description": "Default true. Rows whose filled-in prompt is identical share one model call. They still get the answer and are charged as AI rows."
          },
          "outputDatasetName": {
            "title": "Output dataset name",
            "pattern": "^(?:[A-Za-z0-9_-]{1,63})?$",
            "maxLength": 63,
            "type": "string",
            "description": "Optional. Empty writes the run's default dataset, which a webhook can pass on as datasetId. A name stores rows in that dataset instead. Leave it empty if the run cannot create datasets.",
            "default": ""
          },
          "payload": {
            "title": "Integration payload",
            "type": "object",
            "description": "Added by an Apify Actor-to-Actor integration. It is accepted so the run can start and is not read. Set datasetId to {{resource.defaultDatasetId}} in the integration input."
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}