{
  "openapi": "3.0.1",
  "info": {
    "title": "Dataset Join & Merge (VLOOKUP for Datasets)",
    "description": "Join two datasets (or JSON arrays) on a key field like SQL or VLOOKUP: inner, left, right, full, anti joins and union. Enrich scraped leads with CRM data, combine two scrapers' output, find rows in one list missing from another, then download as CSV/Excel.",
    "version": "0.1",
    "x-build-id": "kFgI8K8wGt3yMTH6A"
  },
  "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 data (inline)' below, not both. 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."
          },
          "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."
          },
          "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 data (inline)' below, not both."
          },
          "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."
          },
          "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
          },
          "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": []
          },
          "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
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}