{
  "openapi": "3.0.1",
  "info": {
    "title": "Merge, Deduplicate & Join Datasets",
    "description": "Merge up to 10 of your Apify datasets into one, remove duplicates by key fields (keep the first or the last), and join extra fields from a lookup dataset like VLOOKUP. Optional loose key matching: spaces, case, full-width/half-width and kana. Works with limited permissions.",
    "version": "0.1",
    "x-build-id": "Lkl3eMYdjhJVfrnSE"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/quietworkslab~merge-dedup-datasets/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-quietworkslab-merge-dedup-datasets",
        "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/quietworkslab~merge-dedup-datasets/runs": {
      "post": {
        "operationId": "runs-sync-quietworkslab-merge-dedup-datasets",
        "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/quietworkslab~merge-dedup-datasets/run-sync": {
      "post": {
        "operationId": "run-sync-quietworkslab-merge-dedup-datasets",
        "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": "Dataset",
            "type": "string",
            "description": "The first (or only) dataset to merge. Choose it with the picker or paste its ID (names are not found: this Actor runs with limited permissions). When this Actor runs after your scraper from the scraper's Integrations tab, set it to {{resource.defaultDatasetId}}. Leave it and the next field empty to try the Actor on built-in sample data."
          },
          "datasetIds": {
            "title": "More datasets to merge",
            "maxItems": 9,
            "uniqueItems": true,
            "type": "array",
            "description": "Other datasets to append after the dataset above, in this order (at most 10 datasets in all; choose them with the picker or paste their IDs). The order decides which duplicate \"First\" and \"Last\" keep: to keep the newest version with \"Last\", put the newest data last. {{resource.defaultDatasetId}} also works in this list. Leave empty to de-duplicate (or join) a single dataset. Hidden fields (names starting with #) are not copied."
          },
          "removeDuplicates": {
            "title": "Remove duplicates",
            "type": "boolean",
            "description": "If checked, only one item is kept for each key (see the next fields). Uncheck to just append the datasets (and join them) without removing anything.",
            "default": true
          },
          "dedupeKeys": {
            "title": "Duplicate key fields",
            "type": "array",
            "description": "Fields that identify the same item, for example url, sku or id; with several fields, the combination is the key. Use dots for nested fields (product.id). If empty, two items are duplicates only when all their fields (or only the fields to keep, if you set them) are equal. Items without a value in these fields are always kept.",
            "items": {
              "type": "string"
            }
          },
          "keep": {
            "title": "Which duplicate to keep",
            "enum": [
              "first",
              "last"
            ],
            "type": "string",
            "description": "Which item of a group of duplicates is written. Only the order of the datasets decides: Dataset first, then More datasets to merge in their order. To keep the newest version with \"Last\", put the newest data last; in an integration where the new run is in Dataset, \"First\" keeps the new run. \"Last\" (and the duplicate count below) reads the datasets twice, but each item is charged once, and the maximum cost per run must cover all items.",
            "default": "first"
          },
          "lookupDatasetId": {
            "title": "Lookup dataset",
            "type": "string",
            "description": "A dataset to look up extra fields in, for example a product list with categories or a supplier list. Choose it with the picker or paste its ID. Leave empty to only merge. The lookup dataset is held in memory while the run works."
          },
          "joinKey": {
            "title": "Join key in your items",
            "type": "string",
            "description": "The field of the merged items to look up, for example sku or seller.id (dots for nested fields). Required when a lookup dataset is chosen."
          },
          "lookupKey": {
            "title": "Key in the lookup dataset",
            "type": "string",
            "description": "The field of the lookup dataset that holds the same values, for example productCode. Leave empty if it has the same name as the join key. If several lookup items have the same key, the first one is used."
          },
          "joinType": {
            "title": "Items without a match",
            "enum": [
              "left",
              "inner"
            ],
            "type": "string",
            "description": "What to do with merged items whose join key is not in the lookup dataset (or is empty). An inner join can legitimately drop most items, for example with a short watch list as the lookup dataset.",
            "default": "left"
          },
          "lookupFields": {
            "title": "Lookup fields to add",
            "type": "array",
            "description": "Fields of the lookup item to add, for example category and supplierEmail (dots for nested fields). Leave empty to add all of its fields. With a list, items without a match get these fields as null, so every row has the same columns; it also needs less memory.",
            "items": {
              "type": "string"
            }
          },
          "lookupPrefix": {
            "title": "Prefix for lookup fields",
            "type": "string",
            "description": "Put before the name of every added field, so that it does not replace a field of your item: category becomes lookup_category. If a name is still taken, your item's own value is kept. In the JSON input it can be an empty string.",
            "default": "lookup_"
          },
          "keyNormalization": {
            "title": "Key matching",
            "uniqueItems": true,
            "type": "array",
            "description": "How loosely key values are compared, for duplicates and for the join (also in the built-in sample). Only the comparison changes: the output keeps the original values. The spaces option also counts line breaks, tabs and no-break or ideographic spaces; the full-width/half-width option also unifies canonically equivalent characters (Unicode NFC). Without dedupe key fields, it applies to every text value compared. Clear all to compare values exactly.",
            "items": {
              "type": "string",
              "enum": [
                "trim",
                "case",
                "width",
                "kana"
              ],
              "enumTitles": [
                "Ignore spaces at both ends and repeated spaces",
                "Ignore upper and lower case (ABC = abc)",
                "Fold full-width and half-width characters (ＡＢ１ = AB1, ｶﾅ = カナ)",
                "Treat katakana and hiragana as the same (カナ = かな)"
              ]
            },
            "default": [
              "trim"
            ]
          },
          "fields": {
            "title": "Fields to keep",
            "type": "array",
            "description": "The output columns, in this order, for example url, name, price, lookup_category: fields of your items and the added lookup fields (with their prefix). A nested field (offer.price) becomes a column named offer.price. A column that an item does not have is written as null. Leave empty to keep all fields. _sourceDataset and _duplicateCount are added by their own options.",
            "items": {
              "type": "string"
            }
          },
          "renameFields": {
            "title": "Rename fields",
            "type": "object",
            "description": "New names for output columns, as {\"old name\": \"new name\"}, for example {\"title\": \"name\", \"lookup_category\": \"category\"}. Use the names as they are written (after the lookup prefix). _sourceDataset and _duplicateCount are added after the renames. Leave empty to keep the names."
          },
          "addSourceDataset": {
            "title": "Add the source dataset (_sourceDataset)",
            "type": "boolean",
            "description": "If checked, every row gets a _sourceDataset field with the ID of the dataset it came from. It replaces a field of the same name in your data (rename yours to keep it).",
            "default": false
          },
          "addDuplicateCount": {
            "title": "Add the duplicate count (_duplicateCount)",
            "type": "boolean",
            "description": "If checked, every row gets a _duplicateCount field: how many other items had the same key and were removed (0 for unique items). This reads the datasets twice (each item is charged once), and the maximum cost per run must cover all items.",
            "default": false
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}