{
  "openapi": "3.0.1",
  "info": {
    "title": "Dataset Join & Merge (VLOOKUP for Datasets)",
    "description": "Returns two Apify datasets, files or Google Sheets by URL, or JSON arrays joined on key fields like SQL or VLOOKUP: left, inner, right, full and anti joins plus union, with CSV/Excel export, named-dataset append and webhook delivery. Agent-ready: pay per event (x402, MCP), per output row.",
    "version": "0.1",
    "x-build-id": "jNuwMNjzPXewrn58e"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/nerolabs~dataset-join-merge/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-nerolabs-dataset-join-merge",
        "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/nerolabs~dataset-join-merge/runs": {
      "post": {
        "operationId": "runs-sync-nerolabs-dataset-join-merge",
        "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/nerolabs~dataset-join-merge/run-sync": {
      "post": {
        "operationId": "run-sync-nerolabs-dataset-join-merge",
        "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": {
          "leftDatasetId": {
            "title": "Left dataset (the main table)",
            "type": "string",
            "description": "Pick an existing Apify dataset for the LEFT side of the join, the table whose rows you want to keep and enrich. Use this OR 'Left file URL' OR 'Left data (inline)' below. Declaring it this way is what lets this Actor run with limited permissions: it may read the dataset you point at, and nothing else on your account."
          },
          "leftFileUrl": {
            "title": "Left file URL (CSV, Excel, JSON or Google Sheet)",
            "type": "string",
            "description": "Instead of a dataset, download the LEFT rows from a public link: a CSV or TSV file, an Excel .xlsx file (first sheet, header row), a JSON array or JSON Lines file, or a Google Sheet (paste the normal sheet link, sharing set to 'Anyone with the link can view'). The format is detected automatically. Up to 100 MB. The download is never charged; only output rows are. Ignored when 'Left dataset' is set."
          },
          "leftData": {
            "title": "Left data (inline)",
            "type": "array",
            "description": "A JSON array of records for the LEFT side, for ad-hoc data instead of a dataset ID or file URL."
          },
          "rightDatasetId": {
            "title": "Right dataset (the lookup table)",
            "type": "string",
            "description": "Pick an existing Apify dataset for the RIGHT side, the table you want to pull matching columns from. Use this OR 'Right file URL' OR 'Right data (inline)' below."
          },
          "rightFileUrl": {
            "title": "Right file URL (CSV, Excel, JSON or Google Sheet)",
            "type": "string",
            "description": "Instead of a dataset, download the RIGHT rows (the lookup table) from a public link: a CSV or TSV file, an Excel .xlsx file, a JSON or JSON Lines file, or a Google Sheet shared as 'Anyone with the link can view'. Typical use: a sheet a colleague maintains, joined onto a scraper's dataset. Format detected automatically, up to 100 MB, never charged. Ignored when 'Right dataset' is set."
          },
          "rightData": {
            "title": "Right data (inline)",
            "type": "array",
            "description": "A JSON array of records for the RIGHT side, for ad-hoc data instead of a dataset ID or file URL."
          },
          "fileFormat": {
            "title": "File format (for file URLs)",
            "enum": [
              "auto",
              "csv",
              "tsv",
              "json",
              "jsonl",
              "xlsx"
            ],
            "type": "string",
            "description": "Only needed if automatic detection gets a file URL's format wrong. Applies to both file URLs.",
            "default": "auto"
          },
          "leftKeyFields": {
            "title": "Key field(s) on the left",
            "type": "array",
            "description": "The field name(s) on the LEFT rows to match on (e.g. 'email', 'sku', 'id', or several fields together like 'firstName' + 'lastName'). Required for every join type except Union.",
            "items": {
              "type": "string"
            }
          },
          "rightKeyFields": {
            "title": "Key field(s) on the right",
            "type": "array",
            "description": "Optional. The matching field name(s) on the RIGHT rows, in the same order as the left key fields. Leave empty if the right side uses the same field names as the left (e.g. 'email' on both).",
            "items": {
              "type": "string"
            }
          },
          "joinType": {
            "title": "Join type",
            "enum": [
              "left",
              "inner",
              "right",
              "full",
              "leftAnti",
              "rightAnti",
              "union"
            ],
            "type": "string",
            "description": "Which rows to keep. 'Left join' keeps every left row and adds right columns where a match exists (the VLOOKUP / enrichment case). 'Inner' keeps only rows that match on both sides. 'Full' keeps everything from both sides. 'Left anti' keeps only left rows with NO match on the right (e.g. leads not yet in your CRM). 'Right anti' is the reverse. 'Union' stacks both tables into one (no key needed).",
            "default": "left"
          },
          "keyMatching": {
            "title": "Key matching",
            "enum": [
              "normalized",
              "exact"
            ],
            "type": "string",
            "description": "'Normalized' matches keys case-insensitively, ignoring surrounding and repeated whitespace, and treats the number 123 and the text '123' as the same, which is what you usually want for emails, names and IDs coming from two different systems. 'Exact' requires byte-for-byte identical values.",
            "default": "normalized"
          },
          "rightFields": {
            "title": "Right fields to bring in",
            "type": "array",
            "description": "Optional. Only copy these specific fields from the matching right row (like choosing which VLOOKUP columns to return). Leave empty to bring in every right field.",
            "items": {
              "type": "string"
            }
          },
          "onFieldConflict": {
            "title": "When a field exists on both sides",
            "enum": [
              "prefixRight",
              "keepLeft",
              "keepRight"
            ],
            "type": "string",
            "description": "What to do when a right field has the same name as a left field (other than the key itself). 'Prefix right' keeps both by renaming the right one (e.g. 'right_price'). 'Keep left' ignores the right value. 'Keep right' overwrites the left value.",
            "default": "prefixRight"
          },
          "rightFieldPrefix": {
            "title": "Prefix for conflicting right fields",
            "type": "string",
            "description": "Used only when 'Prefix the right field' is selected above.",
            "default": "right_"
          },
          "multipleMatches": {
            "title": "If a key matches several right rows",
            "enum": [
              "all",
              "first"
            ],
            "type": "string",
            "description": "'All' outputs one row per matching pair, exactly like a SQL join (a left row matching 3 right rows becomes 3 output rows). 'First' keeps only the first matching right row, exactly like a spreadsheet VLOOKUP.",
            "default": "all"
          },
          "includeJoinInfo": {
            "title": "Add join-status fields to each row",
            "type": "boolean",
            "description": "When on, every output row gets '_joinStatus' ('matched', 'left_only' or 'right_only'; 'left'/'right' in Union mode) and '_matchCount' (how many rows on the other side matched). Handy for filtering afterwards. Turn off for a clean output with only your own columns.",
            "default": true
          },
          "maxItems": {
            "title": "Max rows to load per side",
            "type": "integer",
            "description": "Safety cap on how many rows to load from each side. Leave at 0 for no cap (up to the Actor's own 100,000-rows-per-side hard limit).",
            "default": 0
          },
          "outputDatasetName": {
            "title": "Also append to a named dataset",
            "type": "string",
            "description": "Optional. A name (3 to 63 letters, digits or hyphens, e.g. 'enriched-leads'). The joined rows are appended to a dataset with this name in your account, created on the first run, so a scheduled pipeline accumulates into one place instead of one dataset per run. Not charged again."
          },
          "exportFormats": {
            "title": "Export result as file",
            "type": "array",
            "description": "Optional. Generate a real downloadable file of the joined result on top of the dataset. Leave empty to skip file export entirely.",
            "items": {
              "type": "string",
              "enum": [
                "csv",
                "xlsx"
              ],
              "enumTitles": [
                "CSV",
                "Excel (.xlsx)"
              ]
            },
            "default": []
          },
          "webhookUrl": {
            "title": "Webhook URL (optional)",
            "type": "string",
            "description": "Optional. If set, the joined rows (plus download links and the join summary with both sides' match rates) are POSTed here as JSON the moment this run finishes, so you don't have to come back and check the dataset yourself. Works with a Zapier/Make/n8n catch-hook, your own API endpoint, or a Slack incoming webhook. Only charged when the endpoint actually confirms receipt (HTTP 2xx); a failed delivery is reported as a warning in the run's output and costs nothing."
          }
        }
      },
      "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
                  }
                }
              },
              "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}