{
  "openapi": "3.0.1",
  "info": {
    "title": "Actor A/B Tester — Compare Two Actors Side by Side",
    "description": "Run two Apify actors with identical input in parallel and compare results side by side. Measures result count, field coverage, execution speed, and compute cost. Declares a winner with percentage diffs. Returns JSON/CSV/Excel.",
    "version": "1.1",
    "x-build-id": "H1OkAg0zlb4knahVM"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/ryanclinton~actor-ab-tester/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-ryanclinton-actor-ab-tester",
        "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/ryanclinton~actor-ab-tester/runs": {
      "post": {
        "operationId": "runs-sync-ryanclinton-actor-ab-tester",
        "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/ryanclinton~actor-ab-tester/run-sync": {
      "post": {
        "operationId": "run-sync-ryanclinton-actor-ab-tester",
        "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",
        "required": [
          "actorA",
          "actorB",
          "testInput"
        ],
        "properties": {
          "actorA": {
            "title": "Actor A (current / baseline)",
            "type": "string",
            "description": "Actor ID or name for the first actor (e.g. 'apify/web-scraper' or the opaque actor ID). When testing a replacement or a migration, put your current / production actor here as the baseline.",
            "default": "apify/web-scraper"
          },
          "actorB": {
            "title": "Actor B (candidate / new)",
            "type": "string",
            "description": "Actor ID or name for the second actor (e.g. 'apify/cheerio-scraper' or the opaque actor ID). When testing a replacement, put the candidate / new actor here — the one you'd switch to if it wins.",
            "default": "apify/cheerio-scraper"
          },
          "testInput": {
            "title": "Test Input",
            "type": "object",
            "description": "JSON input passed identically to both actors. Must be compatible with both actors' input schemas.",
            "default": {
              "startUrls": [
                {
                  "url": "https://example.com"
                }
              ]
            }
          },
          "mode": {
            "title": "Test mode",
            "enum": [
              "smoke",
              "standard",
              "decision",
              "high_stakes"
            ],
            "type": "string",
            "description": "Preset that maps to a runs-per-actor count and a readiness ceiling. 'smoke' (1 run) is capped at 'monitor' readiness and can never return 'actionable'. 'standard' (3) is the sensible default for routine comparison. 'decision' (5) is suitable for production switching. 'high_stakes' (10) is for decisions where the verdict needs to survive scrutiny.",
            "default": "standard"
          },
          "decisionProfile": {
            "title": "Decision profile",
            "enum": [
              "balanced",
              "speed_first",
              "cost_first",
              "output_first",
              "reliability_first"
            ],
            "type": "string",
            "description": "How the winner is weighted. 'balanced' spreads weight across all metrics. 'speed_first' / 'cost_first' / 'output_first' / 'reliability_first' upweight one dimension. The chosen profile is reported alongside the verdict so the result stays auditable."
          },
          "comparisonType": {
            "title": "Comparison type (the job)",
            "enum": [
              "general",
              "replacement",
              "regression",
              "cost_optimization"
            ],
            "type": "string",
            "description": "What the comparison is FOR — orthogonal to the decision profile (which weights metrics). 'general' is a neutral A/B. 'replacement' and 'regression' require schema overlap before recommending a switch (a candidate that drops fields the current actor returns is held back from 'actionable'); 'regression' additionally refuses to switch when the candidate (B) loses output quality vs the current actor (A). 'cost_optimization' leans cost-first. Each type also picks a sensible default profile when you haven't set one (your explicit profile always wins).",
            "default": "general"
          },
          "runs": {
            "title": "Runs per actor (override)",
            "minimum": 1,
            "maximum": 10,
            "type": "integer",
            "description": "Override the runs count set by the mode preset. If set, this wins over mode. Range 1–10. More runs = less noise, N× the Apify platform bill."
          },
          "includeStoreContext": {
            "title": "Include Store popularity context",
            "type": "boolean",
            "description": "Fetch each actor's Apify Store stats (monthly users, star rating, categories) and attach to the result as informational context. Store signals NEVER affect the winner score — they are reported separately so reviewers can weigh trust context without contaminating the comparator.",
            "default": true
          },
          "compareToLastComparableRun": {
            "title": "Compare to last comparable run",
            "type": "boolean",
            "description": "Look up the previous run for the same pair + same testInput + same mode + same profile, and report delta (winner change, confidence delta, metric drift). First run of a pair emits 'found: false' — not a failure.",
            "default": false
          },
          "fieldEquivalence": {
            "title": "Field equivalence map (optional)",
            "type": "object",
            "description": "Map a canonical field name to the equivalent names the other actor uses for the same data, so two actors returning identical data under different names aren't penalised on field coverage or flagged as divergent. Example: {\"url\": [\"link\", \"sourceUrl\"], \"title\": [\"name\", \"paperTitle\"]}. Applied only to the comparison math — the per-actor 'fields' lists keep their real names."
          },
          "qualityRules": {
            "title": "Output quality rules (optional)",
            "type": "object",
            "description": "Drives the comparative output-quality dimension so a fast actor that returns garbage doesn't win on speed alone. Two optional keys: 'requiredFields' (array of field names that must be present and non-null — sets requiredFieldCompleteness), and 'uniqueField' (the field used as the dedup key for duplicateRate; falls back to a full-record hash). Example: {\"requiredFields\": [\"title\", \"url\"], \"uniqueField\": \"url\"}."
          },
          "autoMatchFields": {
            "title": "Auto-match field name variants",
            "type": "boolean",
            "description": "Fold field names that differ only by case or separators (companyName ≈ company_name ≈ CompanyName) together before computing field overlap, coverage, and compatibility — so two actors using different naming conventions for the same field aren't falsely flagged as divergent. Assertive synonyms (url ≈ link) are NOT auto-guessed; declare those explicitly in fieldEquivalence. Default on.",
            "default": true
          },
          "timeout": {
            "title": "Timeout per run (seconds)",
            "minimum": 10,
            "maximum": 3600,
            "type": "integer",
            "description": "Maximum time in seconds to wait for each child actor run to complete. Both actors run their N runs in parallel."
          },
          "memory": {
            "title": "Memory per run (MB)",
            "minimum": 128,
            "maximum": 32768,
            "type": "integer",
            "description": "Memory allocation in megabytes for each child run. Higher memory = faster execution but higher sub-actor cost."
          },
          "maxChildRunChargeUsd": {
            "title": "Max charge per child run (USD, optional)",
            "minimum": 0,
            "type": "number",
            "description": "Optional safety cap on each child run's own pay-per-event spend (e.g. 0.50). Passed to every child run as maxTotalChargeUsd so a runaway sub-actor self-aborts at this ceiling instead of billing without bound. Leave blank for no cap. Note: this caps the COMPARED actors' spend, not this actor's $0.15 comparison fee."
          },
          "apifyToken": {
            "title": "Apify API Token (optional)",
            "type": "string",
            "description": "Your Apify API token, used to start and poll the two child actors. The run's built-in token is often scoped and can't start third-party actors (you'll see every run fail with a permissions error and a no_call verdict) — paste a full-access token here to compare actors outside the runner's scope. Find it at https://console.apify.com/settings/integrations"
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}