# Actor Release Gate — 9 Pre-Deploy Checks (`ryanclinton/cicd-release-gate`) Actor

Runs 9 pre-release checks on Apify actors before every deploy: input validation, run success, dataset quality, schema conformance, golden baselines, log anomalies. Gate 1-100+ actors per run. GitHub Actions integration. $0.10 per actor.

- **URL**: https://apify.com/ryanclinton/cicd-release-gate.md
- **Developed by:** [Ryan Clinton](https://apify.com/ryanclinton) (community)
- **Categories:** Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Apify Actor Release Gate

Pre-release validation gate that runs 9 automated checks on Apify actors before every deploy — covering Store health criteria and 5 extended data quality checks your CI/CD pipeline needs. Built for actor developers who run portfolios of 1 to 100+ actors, this gate automates the pre-release checks the Apify Store runs on every new build — and adds 5 extended checks the Store does not cover.

The actor runs 9 automated checks across 3 configurable profiles, processes multiple actors in parallel with concurrency control, and outputs a structured REPORT.json to key-value store for GitHub Actions integration. Gate N actors per run, compare outputs against golden baselines, detect log anomalies, and catch performance regressions — all on pay-per-event billing at $0.42 per gate check.

### What data can you extract?

| Data Point | Source | Example |
|---|---|---|
| 📋 **Gate verdict** | All checks combined | `overallPassed: true` |
| ✅ **Check A: Effective input** | Input schema analysis | `Effective input satisfies all 3 required fields` |
| 🏃 **Check B: Run succeeded** | Actor run status | `Run abc123 finished with status: SUCCEEDED` |
| 📊 **Check C: Non-empty dataset** | Dataset item count | `Default dataset contains 47 items` |
| ⏱️ **Check D: Duration** | Run timestamps | `Run duration: 42s (max allowed: 300s)` |
| 📐 **Check E: Schema conformance** | Output field types | `All schema fields conform on 47 items checked` |
| 📈 **Check F: Field completeness** | Null/empty rates | `email: null=4.3%, empty=0.0%, nonNull=45` |
| 🔄 **Check G: Golden comparison** | Baseline dataset diff | `No significant drift vs baseline (dataset_xyz)` |
| 🔍 **Check H: Log patterns** | Run log text analysis | `No bad patterns detected in run log` |
| ⚡ **Check I: Perf regression** | Duration vs baseline | `Duration 42s is 1.05x baseline (40s)` |
| 📝 **Sample items** | Dataset output | First 5 items from the actor's default dataset |
| 📜 **Log tail** | Run log | Last 50 lines of the actor's run log |
| 🔀 **Golden diff** | Baseline comparison | Added/removed fields, count drift, sample changes |

### Why use Apify Actor Release Gate?

Deploying an Apify actor update without testing is a gamble. A missing prefill value, a broken API endpoint, or a schema change in the target website can silently break your actor. Users get empty datasets, runs fail, reviews drop, and revenue stops. Checking manually takes 15-30 minutes per actor — and if you manage a portfolio of 10+, that is an entire day of clicking through the Apify Console.

This actor automates the entire pre-release validation pipeline. Point it at one actor or fifty, pick a check profile, and get a pass/fail verdict in minutes. The same checks the Apify Store runs on your builds (plus 5 more the Store does not cover) are executed automatically and reported in a machine-readable format for CI/CD pipelines.

- **Scheduling** — run nightly or before every deploy to catch regressions before users do
- **API access** — trigger gate checks from GitHub Actions, GitLab CI, or any HTTP client
- **Proxy rotation** — the gate actor itself needs no proxies; your target actors use their own proxy configuration
- **Monitoring** — get Slack/email alerts when a gate run fails via Apify webhooks
- **Integrations** — connect to Zapier, Make, or webhooks to block deploys when checks fail

### Features

- **9 automated checks** — 4 Store-equivalent hard gates (A-D) plus 5 extended checks (E-I) covering schema conformance, field completeness, golden dataset comparison, log pattern detection, and performance regression
- **4 operating modes** — `gate` (fail on ERROR), `approveBaseline` (save current output as new golden), `bootstrapBaseline` (first-time baseline setup), `dryRun` (run all checks but never fail)
- **3 check profiles** — `store-default` runs checks A-D only (mirrors Apify Store validation), `extended` runs all 9 checks, `custom` lets you pick checks per target
- **Portfolio processing** — gate 1 to 100+ actors in a single run with configurable concurrency (default 3 parallel targets)
- **Stop on first failure** — optionally halt the portfolio after the first target fails, saving compute on known-broken builds
- **Golden baseline management** — snapshot test pattern: approve a known-good dataset as baseline, then detect field additions, removals, count drift, and value changes on every subsequent run
- **Baseline approval workflow** — `approveBaseline` mode saves passing run outputs to a named KV store, creating versioned snapshots with change reason audit trail
- **Log anomaly detection** — scans run logs for 13 bad patterns including captcha, 429 rate limits, uncaught exceptions, TypeError, ECONNREFUSED, and socket hang up
- **Performance regression detection** — flags runs that take 2x longer than the baseline duration
- **Schema conformance validation** — checks output dataset items against a declared field-type map, reporting type mismatches and missing fields across up to 10 sampled items
- **Field completeness scoring** — measures null rate, empty rate, and non-null count per field against configurable thresholds (maxNullRate, maxEmptyRate, minNonNullCount)
- **REPORT.json output** — structured JSON report stored in the default KV store, designed for parsing by GitHub Actions or CI scripts
- **PPE billing** — pay per gate check, configurable per-target or per-run billing with spending limit support
- **Effective input resolution** — builds the same input the Apify Store uses by merging `prefill` and `default` values from the actor's input schema

### Use cases for Apify actor pre-release validation

#### Pre-deploy validation in CI/CD pipelines

DevOps engineers and actor developers add the release gate as a step in GitHub Actions or GitLab CI. Every push to main triggers the gate actor via API. If any check fails, the pipeline blocks the deploy. The REPORT.json in the KV store provides structured evidence for the failure.

#### Nightly regression testing for actor portfolios

Teams managing 10-50+ actors schedule the gate to run every night with the `extended` profile. The summary report surfaces which actors have degraded — empty datasets, schema drift, new error log patterns — before users report issues. Combine with Apify webhooks to get Slack alerts on failures.

#### Golden dataset snapshot testing

QA-focused teams use `bootstrapBaseline` to capture known-good outputs, then run `gate` with the `extended` profile on every build. Check G detects when output fields are added or removed, when item counts drift beyond 50%, and when specific record values change — similar to snapshot testing in frontend frameworks.

#### Store submission readiness check

Before submitting a new actor to the Apify Store, developers run the gate with `store-default` profile to verify the same checks the Store approval process runs: effective input validation, successful run, non-empty dataset, and duration under 300 seconds.

#### Post-incident verification

After fixing a broken actor, teams run the gate in `dryRun` mode to verify all checks pass without risking a hard failure. Once confident, they switch to `gate` mode and deploy.

### How to validate Apify actors before release

1. **Add your actors** — In the Targets field, list each actor by ID or name (e.g., `ryanclinton/my-actor`). Specify the build tag to test (`latest` or a specific version).
2. **Pick a profile** — Choose `Store Default` for the 4 core checks, `Extended` for all 9, or `Custom` if you only want specific checks on specific targets.
3. **Run the gate** — Click "Start" and wait. A single actor check takes 30-120 seconds depending on the target actor's runtime. A portfolio of 10 actors with concurrency 3 takes 2-5 minutes.
4. **Read the report** — Open the Dataset tab for the full gate report, or fetch REPORT.json from the KV store for CI/CD integration. Each target shows pass/fail with per-check evidence.

### Input parameters

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `mode` | string | No | `gate` | Operating mode: `gate` (fail on ERROR), `approveBaseline` (save current output as golden), `bootstrapBaseline` (first-time baseline setup), `dryRun` (report only, never fail) |
| `profile` | string | No | `store-default` | Check profile: `store-default` (checks A-D), `extended` (all 9 checks), `custom` (per-target configuration) |
| `targets` | array | Yes | — | Array of actors to gate-check. Each entry requires `actorIdOrName`. Optionally specify `build`, `testCases` with custom inputs and expectations |
| `portfolio` | object | No | `{ concurrency: 3, stopOnFirstFailure: false }` | Portfolio processing settings. `concurrency` limits parallel runs; `stopOnFirstFailure` halts after the first target fails |
| `billing` | object | No | `{ chargeEventName: "gate-check", chargePerTarget: true }` | PPE billing configuration. `chargePerTarget: true` bills once per target actor; `false` bills once per gate run |
| `report` | object | No | `{ format: "full", includeDiffs: true, includeSampleItems: true, includeLogTail: true }` | Controls REPORT.json content: include golden diffs, sample dataset items, and run log tails |

#### Input examples

**Simple Store-equivalent check on one actor:**
```json
{
    "mode": "gate",
    "profile": "store-default",
    "targets": [
        {
            "actorIdOrName": "ryanclinton/website-contact-scraper",
            "build": "latest"
        }
    ]
}
````

**Extended checks on a portfolio of 5 actors:**

```json
{
    "mode": "gate",
    "profile": "extended",
    "targets": [
        { "actorIdOrName": "ryanclinton/website-contact-scraper", "build": "latest" },
        { "actorIdOrName": "ryanclinton/email-pattern-finder", "build": "latest" },
        { "actorIdOrName": "ryanclinton/google-maps-email-extractor", "build": "latest" },
        { "actorIdOrName": "ryanclinton/bulk-email-verifier", "build": "latest" },
        { "actorIdOrName": "ryanclinton/trustpilot-review-analyzer", "build": "latest" }
    ],
    "portfolio": { "concurrency": 5, "stopOnFirstFailure": false }
}
```

**Custom test case with explicit input and schema validation:**

```json
{
    "mode": "gate",
    "profile": "extended",
    "targets": [
        {
            "actorIdOrName": "ryanclinton/website-contact-scraper",
            "build": "latest",
            "testCases": [
                {
                    "id": "custom-smoke",
                    "inputMode": "explicit",
                    "input": { "urls": ["https://acmecorp.com"] },
                    "expectations": {
                        "requireSucceeded": true,
                        "requireNonEmptyDefaultDataset": true,
                        "maxDurationSeconds": 120,
                        "dataset": {
                            "requiredFields": ["email", "domain"],
                            "schema": { "email": "string", "domain": "string" },
                            "fieldThresholds": { "email": { "maxNullRate": 0.1 } }
                        }
                    }
                }
            ]
        }
    ]
}
```

#### Input tips

- **Start with `store-default` profile** — the 4 core checks (A-D) cover what the Apify Store validates. Graduate to `extended` once baselines are established.
- **Use `dryRun` mode first** — run all checks without failing the gate to understand which checks your actor currently passes or fails.
- **Bootstrap baselines before gating** — run once with `bootstrapBaseline` mode to capture known-good outputs. Subsequent `gate` runs with extended profile will compare against these baselines.
- **Set concurrency based on your target actors' memory needs** — if each target actor uses 1GB+, reduce concurrency to 2 to avoid platform memory pressure.
- **Use explicit inputs for edge-case testing** — the default `prefill` input mode mirrors Store behavior, but `explicit` mode lets you test specific scenarios.

### Output example

```json
{
    "type": "gate-report",
    "meta": {
        "mode": "gate",
        "profile": "extended",
        "startedAt": "2026-03-28T14:00:00.000Z",
        "finishedAt": "2026-03-28T14:02:47.000Z",
        "gateActorRunId": "abc123def456"
    },
    "summary": {
        "totalTargets": 3,
        "passedTargets": 2,
        "failedTargets": 1,
        "totalTestCases": 3,
        "passedTestCases": 2,
        "failedTestCases": 1,
        "errorChecks": 1,
        "warnChecks": 2,
        "overallPassed": false
    },
    "targets": [
        {
            "actorIdOrName": "ryanclinton/website-contact-scraper",
            "build": "latest",
            "passed": true,
            "testCases": [
                {
                    "id": "default-smoke",
                    "run": {
                        "runId": "run_7Xk9mZpL2n",
                        "status": "SUCCEEDED",
                        "startedAt": "2026-03-28T14:00:02.000Z",
                        "finishedAt": "2026-03-28T14:00:38.000Z",
                        "durationSeconds": 36,
                        "defaultDatasetId": "ds_mN4kRp8Wq2",
                        "datasetItemCount": 47,
                        "exitCode": 0,
                        "buildId": "bld_Xp9Lm2Kn"
                    },
                    "checks": [
                        { "id": "A-effective-input", "severity": "ERROR", "passed": true, "evidence": "Effective input satisfies all 3 required fields" },
                        { "id": "B-run-succeeded", "severity": "ERROR", "passed": true, "evidence": "Run run_7Xk9mZpL2n finished with status: SUCCEEDED" },
                        { "id": "C-non-empty-dataset", "severity": "ERROR", "passed": true, "evidence": "Default dataset contains 47 items" },
                        { "id": "D-duration", "severity": "ERROR", "passed": true, "evidence": "Run duration: 36s (max allowed: 300s)" },
                        { "id": "E-schema-conformance", "severity": "ERROR", "passed": true, "evidence": "All schema fields conform on 47 items checked" },
                        { "id": "H-log-patterns", "severity": "WARN", "passed": true, "evidence": "No bad patterns detected in run log" }
                    ],
                    "artifacts": {
                        "sampleItems": [
                            { "domain": "acmecorp.com", "email": "info@acmecorp.com", "phone": "+1-555-0147" }
                        ],
                        "logTail": "INFO  Finished processing 47 results\nINFO  Actor finished in 36s"
                    }
                }
            ]
        }
    ]
}
```

### Output fields

| Field | Type | Description |
|---|---|---|
| `type` | string | Always `gate-report` |
| `meta.mode` | string | Operating mode used for this run |
| `meta.profile` | string | Check profile used |
| `meta.startedAt` | string | ISO timestamp when the gate run started |
| `meta.finishedAt` | string | ISO timestamp when the gate run completed |
| `meta.gateActorRunId` | string | Apify run ID of the gate actor itself |
| `summary.totalTargets` | number | Number of actor targets processed |
| `summary.passedTargets` | number | Targets where all ERROR-severity checks passed |
| `summary.failedTargets` | number | Targets with at least one ERROR-severity check failure |
| `summary.totalTestCases` | number | Total test cases across all targets |
| `summary.passedTestCases` | number | Test cases with no ERROR-severity failures |
| `summary.failedTestCases` | number | Test cases with at least one ERROR failure |
| `summary.errorChecks` | number | Total failed checks with ERROR severity |
| `summary.warnChecks` | number | Total failed checks with WARN severity |
| `summary.overallPassed` | boolean | `true` if all targets passed (or mode is `dryRun`) |
| `targets[].actorIdOrName` | string | Actor identifier as provided in input |
| `targets[].build` | string | Build tag tested |
| `targets[].passed` | boolean | Whether this target passed all ERROR checks |
| `targets[].testCases[].id` | string | Test case identifier |
| `targets[].testCases[].run` | object | Run metadata: runId, status, timestamps, dataset info |
| `targets[].testCases[].checks[]` | array | Individual check results with id, severity, passed, evidence |
| `targets[].testCases[].artifacts` | object | Optional: sampleItems, logTail, golden diff |

### How much does it cost to validate Apify actors?

Apify Actor Release Gate uses **pay-per-event pricing** — you pay **$0.42 per gate check**. Platform compute costs are included. When `chargePerTarget` is `true` (default), you pay per target actor checked. When `false`, you pay once per gate run regardless of target count.

| Scenario | Targets | Cost per target | Total cost |
|----------|---------|-----------------|------------|
| Quick test | 1 | $0.42 | $0.42 |
| Small portfolio | 5 | $0.42 | $2.10 |
| Medium portfolio | 15 | $0.42 | $6.30 |
| Large portfolio | 50 | $0.42 | $21.00 |
| Enterprise fleet | 100 | $0.42 | $42.00 |

You can set a **maximum spending limit** per run to control costs. The actor stops when your budget is reached and reports results for the targets already checked.

Compare this to manual pre-release testing at 15-30 minutes per actor — validating 50 actors manually costs 12-25 hours of developer time. With Apify Actor Release Gate, 50 actors cost $21.00 and complete in under 10 minutes.

### Validate Apify actors using the API

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")

run = client.actor("ryanclinton/cicd-release-gate").call(run_input={
    "mode": "gate",
    "profile": "store-default",
    "targets": [
        {"actorIdOrName": "ryanclinton/website-contact-scraper", "build": "latest"},
        {"actorIdOrName": "ryanclinton/email-pattern-finder", "build": "latest"},
    ],
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    summary = item.get("summary", {})
    print(f"Gate passed: {summary.get('overallPassed')}")
    print(f"Targets: {summary.get('passedTargets')}/{summary.get('totalTargets')} passed")
    print(f"Errors: {summary.get('errorChecks')}, Warnings: {summary.get('warnChecks')}")
```

#### JavaScript

```javascript
import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: "YOUR_API_TOKEN" });

const run = await client.actor("ryanclinton/cicd-release-gate").call({
    mode: "gate",
    profile: "store-default",
    targets: [
        { actorIdOrName: "ryanclinton/website-contact-scraper", build: "latest" },
        { actorIdOrName: "ryanclinton/email-pattern-finder", build: "latest" },
    ],
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
    const { summary } = item;
    console.log(`Gate passed: ${summary.overallPassed}`);
    console.log(`Targets: ${summary.passedTargets}/${summary.totalTargets} passed`);
    console.log(`Errors: ${summary.errorChecks}, Warnings: ${summary.warnChecks}`);
}
```

#### cURL

```bash
## Start the gate run
curl -X POST "https://api.apify.com/v2/acts/ryanclinton~cicd-release-gate/runs?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "gate",
    "profile": "store-default",
    "targets": [{"actorIdOrName": "ryanclinton/website-contact-scraper", "build": "latest"}]
  }'

## Fetch results (replace DATASET_ID from the run response)
curl "https://api.apify.com/v2/datasets/DATASET_ID/items?token=YOUR_API_TOKEN&format=json"
```

#### GitHub Actions

```yaml
- name: Apify Release Gate
  run: |
    RESPONSE=$(curl -s -X POST "https://api.apify.com/v2/acts/ryanclinton~cicd-release-gate/runs?token=${{ secrets.APIFY_TOKEN }}&waitForFinish=300" \
      -H "Content-Type: application/json" \
      -d '{"mode":"gate","profile":"store-default","targets":[{"actorIdOrName":"ryanclinton/my-actor","build":"latest"}]}')
    DATASET_ID=$(echo $RESPONSE | jq -r '.data.defaultDatasetId')
    REPORT=$(curl -s "https://api.apify.com/v2/datasets/$DATASET_ID/items?token=${{ secrets.APIFY_TOKEN }}")
    PASSED=$(echo $REPORT | jq -r '.[0].summary.overallPassed')
    if [ "$PASSED" != "true" ]; then
      echo "Release gate FAILED"
      echo $REPORT | jq '.[0].summary'
      exit 1
    fi
```

### How Apify Actor Release Gate works

#### Phase 1: Input resolution and schema fetch

For each target actor, the gate fetches the actor's input schema from its latest tagged build via the Apify REST API. It reads the build's source files, locates `input_schema.json`, and parses the schema properties. The effective input is built by merging `prefill` values first, then falling back to `default` values — exactly matching how the Apify Store constructs test inputs. Three input modes are supported: `prefill` (Store behavior), `default` (defaults only, no prefills), and `explicit` (user-provided input).

#### Phase 2: Actor execution and monitoring

The gate starts each target actor run via the Apify API with configurable timeout, memory, and spending limits. It polls for completion using Apify's long-poll parameter (`waitForFinish=60`), checking status every 60 seconds for up to 120 rounds (2 hours maximum). Portfolio targets are processed in parallel using a semaphore-based concurrency limiter (default 3). If `stopOnFirstFailure` is enabled, the gate halts remaining targets after the first ERROR.

#### Phase 3: Check execution

The 9 checks are executed in order:

- **Check A (effective input)** — Validates that the resolved input satisfies all `required` fields in the schema. Missing required fields produce an ERROR.
- **Check B (run succeeded)** — Verifies the run reached `SUCCEEDED` status. Terminal statuses `FAILED`, `TIMED-OUT`, and `ABORTED` are failures.
- **Check C (non-empty dataset)** — Confirms the default dataset contains at least 1 item.
- **Check D (duration)** — Compares run duration against `maxDurationSeconds` (default 300s).
- **Check E (schema conformance)** — Validates up to 10 dataset items against a declared field-type map. Reports missing fields and type mismatches (e.g., expected `string`, got `number`).
- **Check F (field completeness)** — Measures null rate, empty rate, and non-null count per required field. Configurable thresholds per field: `maxNullRate`, `maxEmptyRate`, `minNonNullCount`.
- **Check G (golden comparison)** — Loads the approved baseline dataset from a named KV store (`release-gate-baselines`), strips ignored fields, and computes field-set diff, count drift, and per-record value changes using an identity key. Drift exceeding 50% of item count triggers a warning.
- **Check H (log patterns)** — Scans the full run log against 13 regex patterns: `captcha`, `429`, `too many requests`, `blocked`, `login required`, `access denied`, `uncaught exception`, `Error:`, `TypeError:`, `ReferenceError:`, `SyntaxError:`, `UnhandledPromiseRejection`, `ECONNREFUSED`, `ETIMEDOUT`, `socket hang up`.
- **Check I (performance regression)** — Compares run duration to the baseline duration. Runs exceeding 2x the baseline are flagged.

#### Phase 4: Report assembly and billing

The gate builds a structured `GateReport` with metadata, summary statistics, per-target results, and optional artifacts (sample items, log tail, golden diffs). The report is stored as `REPORT.json` in the default KV store and pushed to the dataset. PPE billing charges are applied per-target or per-run based on configuration. If the spending limit is reached mid-portfolio, the gate stops gracefully and reports partial results.

### Tips for best results

1. **Run `bootstrapBaseline` before using golden checks.** Extended profile checks G and I require an approved baseline. Without one, these checks are skipped with a WARN. Run `bootstrapBaseline` once on a known-good build to create the initial snapshot.

2. **Use `store-default` profile for Store submission prep.** The 4 core checks (A-D) mirror the exact validation the Apify Store runs during the approval process. Pass these, and your Store submission is less likely to be rejected.

3. **Set `maxDurationSeconds` per test case, not globally.** Some actors (deep research, multi-step enrichment) take 60+ seconds. Others finish in 5. Configure duration thresholds per test case to avoid false positives.

4. **Combine with webhooks for CI/CD blocking.** Use Apify webhooks to POST the gate result to your CI/CD system. The REPORT.json in the KV store contains structured pass/fail data parseable by GitHub Actions, GitLab CI, or Jenkins.

5. **Schedule nightly runs on your full portfolio.** Create an Apify schedule that runs the gate with `extended` profile on all your published actors. The summary tells you which actors have degraded overnight before users report issues.

6. **Use identity keys in golden comparisons.** If your actor outputs records with a stable ID (e.g., `url`, `businessId`), set `identityKey` in the golden config to enable per-record change detection instead of positional comparison.

7. **Review WARN checks, not just ERRORs.** WARN-severity checks (log patterns, golden drift, perf regression) do not fail the gate but signal quality issues worth investigating before release.

### Combine with other Apify actors

| Actor | How to combine |
|-------|---------------|
| [Actor Input Tester](https://apify.com/ryanclinton/actor-input-tester) | Validate input schemas before the gate runs the full actor — catch bad defaults early |
| [Actor Quality Audit](https://apify.com/ryanclinton/actor-quality-audit) | Run a broader quality audit on Store metadata, documentation, and code patterns alongside gate checks |
| [Actor Health Monitor](https://apify.com/ryanclinton/actor-health-monitor) | Monitor production actors post-deploy; use the gate pre-deploy, health monitor post-deploy |
| [Actor Regression Suite](https://apify.com/ryanclinton/actor-regression-suite) | Run the regression suite for deep functional testing, then gate for Store-equivalent validation |
| [Actor Schema Diff](https://apify.com/ryanclinton/actor-schema-diff) | Detect input/output schema changes between versions before running the gate |
| [Website Contact Scraper](https://apify.com/ryanclinton/website-contact-scraper) | Gate-check your contact scraper before deploying updates to production |
| [Actor Deprecation Monitor](https://apify.com/ryanclinton/actor-deprecation-monitor) | Monitor for deprecated dependencies, then gate-check updated builds |

### Limitations

- **Does not test with proxies configured on the gate actor.** Each target actor uses its own proxy configuration from its input. The gate actor itself does not need or use proxies.
- **Maximum 2-hour wait per target run.** The gate polls for up to 120 rounds at 60 seconds each (2 hours). Long-running actors that exceed this will be reported as failures.
- **Golden comparison is field-level, not value-exact.** Check G detects field additions/removals and count drift, but does not perform exact value matching across all records (only samples up to 20 records with identity key).
- **Performance regression requires stored baseline duration.** Check I currently does not persist baseline duration across runs. It will skip if baseline duration data is unavailable.
- **No JavaScript rendering.** The gate actor fetches input schemas via API calls. It does not render target actor UIs or test browser-based interactions.
- **Log pattern detection uses fixed regex list.** The 13 built-in patterns cover common failure modes but may not catch domain-specific errors. Custom pattern configuration is not yet supported.
- **Dataset sampling limited to 100 items.** Schema conformance and field completeness checks sample up to 100 items from the target actor's dataset. Actors producing 10,000+ items are only partially validated.
- **Baseline KV store is shared.** All baseline snapshots are stored in a single named KV store (`release-gate-baselines`). Multiple concurrent `approveBaseline` runs on the same actor+testCase combination may conflict.

### Integrations

- [GitHub Actions](https://docs.github.com/en/actions) — add the gate as a CI step to block deploys when actors fail health checks
- [Zapier](https://apify.com/integrations/zapier) — trigger downstream workflows (Slack alerts, Jira tickets) when gate checks fail
- [Make](https://apify.com/integrations/make) — build multi-step automation that gates, deploys, and notifies in sequence
- [Apify API](https://docs.apify.com/api/v2) — trigger gate runs programmatically from any language or CI system
- [Webhooks](https://docs.apify.com/platform/integrations/webhooks) — push gate results to your own endpoints for custom processing
- [Google Sheets](https://apify.com/integrations/google-sheets) — export gate reports to a spreadsheet for portfolio-level tracking

### Troubleshooting

- **Gate fails on Check A (effective input) despite actor working in Console** — The gate builds effective input from `prefill` and `default` values in the input schema. If your actor requires fields that have neither a prefill nor a default, Check A will fail. Add prefill values to your `input_schema.json` for all required fields.

- **Target actor run times out but works manually** — The gate starts runs with the target actor's default timeout. If your actor needs more time, configure `runOptions.timeoutSeconds` in the test case expectations. The gate itself has a 1-hour timeout by default.

- **Golden comparison always shows WARN with "No baseline found"** — You need to run the gate once with `bootstrapBaseline` or `approveBaseline` mode to create the initial baseline. Baselines are stored in the `release-gate-baselines` named KV store.

- **Portfolio stops after one target when using extended profile** — Check if `stopOnFirstFailure` is set to `true`. Set it to `false` to process all targets regardless of individual failures.

- **Run charges more than expected** — With `chargePerTarget: true` (default), you pay per target actor. A portfolio of 20 actors costs 20 gate checks. Set `chargePerTarget: false` to pay once per run regardless of target count.

### Responsible use

- This actor only executes actors you own or have permission to run on the Apify platform.
- Gate runs consume compute resources on your Apify account for both the gate actor and each target actor run.
- Ensure your target actors comply with the terms of service of the websites they interact with.
- Do not use the gate to repeatedly run actors against third-party services in a way that constitutes abuse or denial of service.
- For guidance on web scraping legality, see [Apify's guide](https://blog.apify.com/is-web-scraping-legal/).

### FAQ

**How many actors can I validate in one release gate run?**
There is no hard limit on the number of targets per run. The gate processes them with configurable concurrency (default 3 parallel). Portfolios of 50-100+ actors work well. The main constraint is the gate actor's timeout (default 1 hour) and your spending limit.

**What checks does the Apify Actor Release Gate run?**
Nine checks across two tiers. Store-equivalent checks (A-D): effective input validation, run SUCCEEDED status, non-empty dataset, duration under threshold. Extended checks (E-I): output schema conformance, field completeness scoring, golden dataset comparison, log pattern detection, performance regression detection.

**Does the release gate actually run my actor?**
Yes. The gate starts a real run of each target actor via the Apify API using the resolved input. This is not a dry-run simulation — your actor executes fully, consumes its own compute, and produces real output that the gate then validates.

**How is this different from the Apify Store's built-in checks?**
The Store runs checks equivalent to A-D (effective input, run status, non-empty dataset, duration). The gate adds 5 checks the Store does not: schema conformance (E), field completeness (F), golden dataset comparison (G), log pattern detection (H), and performance regression (I). The gate also supports portfolio batch processing and CI/CD integration.

**Can I use Apify Actor Release Gate with GitHub Actions?**
Yes. The gate outputs a structured REPORT.json to the KV store. Use the cURL-based GitHub Actions example in this README to start a gate run, wait for completion, fetch the report, and fail the CI step if `overallPassed` is `false`.

**Is it possible to validate actors I do not own?**
You can gate-check any public actor on Apify, but the target actor run will execute under your account and consume your compute credits. This is useful for validating third-party actors before integrating them into your pipelines.

**How accurate is the golden dataset comparison?**
Check G detects structural drift (added/removed fields) and count drift (more than 50% change in item count). With an identity key configured, it also detects per-record value changes across up to 20 sampled records. It does not perform full record-by-record equality across the entire dataset.

**What happens if my actor's run exceeds the default 300-second limit?**
Check D will report an ERROR. Override the limit per test case by setting `expectations.maxDurationSeconds` to a higher value. Actors that legitimately take 10+ minutes (e.g., deep research actors) should have their duration threshold set accordingly.

**Can I schedule the Apify Actor Release Gate to run periodically?**
Yes. Create an Apify schedule with your gate input. Nightly runs with the `extended` profile on your full actor portfolio catch regressions before users report them. Combine with Apify webhooks or Slack integration for automated alerting.

**How much does it cost to gate-check 50 actors?**
With the default `chargePerTarget: true` setting, 50 actors cost 50 gate checks at $0.42 each = $21.00 for the gate actor. Target actors also consume their own compute credits during the test run.

**What log patterns does Check H detect?**
The gate scans for 13 patterns: captcha, HTTP 429, too many requests, blocked, login required, access denied, uncaught exception, Error:, TypeError:, ReferenceError:, SyntaxError:, UnhandledPromiseRejection, ECONNREFUSED, ETIMEDOUT, and socket hang up. Any match produces a WARN.

**Can I use custom checks instead of the built-in profiles?**
Yes. Set the profile to `custom` and configure specific checks per target in the `testCases` array. Custom profile only runs checks explicitly configured in the test case expectations.

### Help us improve

If you encounter issues, you can help us debug faster by enabling run sharing in your Apify account:

1. Go to [Account Settings > Privacy](https://console.apify.com/account/privacy)
2. Enable **Share runs with public Actor creators**

This lets us see your run details when something goes wrong, so we can fix issues faster. Your data is only visible to the actor developer, not publicly.

### Support

Found a bug or have a feature request? Open an issue in the [Issues tab](https://console.apify.com/actors/cicd-release-gate/issues) on this actor's page. For custom solutions or enterprise integrations, reach out through the Apify platform.

# Actor input Schema

## `mode` (type: `string`):

gate = run checks and fail if any ERROR; approveBaseline = save current output as new golden; bootstrapBaseline = first-time baseline setup; dryRun = run all checks but never fail.

## `profile` (type: `string`):

store-default = checks A-D only (Store equivalent); extended = all 9 checks; custom = use only checks explicitly configured in targets.

## `targets` (type: `array`):

Array of actors to gate-check. Each entry requires actorIdOrName. Optionally specify build tag and test cases with custom inputs and expectations.

## `portfolio` (type: `object`):

Controls how multiple targets are processed. concurrency limits parallel runs; stopOnFirstFailure halts after the first target fails.

## `billing` (type: `object`):

PPE billing configuration. chargeEventName sets the event billed per run. chargePerTarget bills once per target actor rather than once for the whole gate run.

## `report` (type: `object`):

Controls what is included in REPORT.json. format: summary | full. includeDiffs requires extended profile with golden check.

## Actor input object example

```json
{
  "mode": "gate",
  "profile": "store-default",
  "targets": [
    {
      "actorIdOrName": "ryanclinton/my-actor",
      "build": "latest",
      "testCases": [
        {
          "id": "default-smoke",
          "inputMode": "prefill",
          "expectations": {
            "requireSucceeded": true,
            "requireNonEmptyDefaultDataset": true,
            "maxDurationSeconds": 300
          }
        }
      ]
    }
  ],
  "portfolio": {
    "concurrency": 3,
    "stopOnFirstFailure": false
  },
  "billing": {
    "chargeEventName": "gate-check",
    "chargePerTarget": true
  },
  "report": {
    "format": "full",
    "includeDiffs": true,
    "includeSampleItems": true,
    "includeLogTail": true
  }
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "targets": [
        {
            "actorIdOrName": "ryanclinton/my-actor",
            "build": "latest",
            "testCases": [
                {
                    "id": "default-smoke",
                    "inputMode": "prefill",
                    "expectations": {
                        "requireSucceeded": true,
                        "requireNonEmptyDefaultDataset": true,
                        "maxDurationSeconds": 300
                    }
                }
            ]
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ryanclinton/cicd-release-gate").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "targets": [{
            "actorIdOrName": "ryanclinton/my-actor",
            "build": "latest",
            "testCases": [{
                    "id": "default-smoke",
                    "inputMode": "prefill",
                    "expectations": {
                        "requireSucceeded": True,
                        "requireNonEmptyDefaultDataset": True,
                        "maxDurationSeconds": 300,
                    },
                }],
        }] }

# Run the Actor and wait for it to finish
run = client.actor("ryanclinton/cicd-release-gate").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "targets": [
    {
      "actorIdOrName": "ryanclinton/my-actor",
      "build": "latest",
      "testCases": [
        {
          "id": "default-smoke",
          "inputMode": "prefill",
          "expectations": {
            "requireSucceeded": true,
            "requireNonEmptyDefaultDataset": true,
            "maxDurationSeconds": 300
          }
        }
      ]
    }
  ]
}' |
apify call ryanclinton/cicd-release-gate --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=ryanclinton/cicd-release-gate",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Actor Release Gate — 9 Pre-Deploy Checks",
        "description": "Runs 9 pre-release checks on Apify actors before every deploy: input validation, run success, dataset quality, schema conformance, golden baselines, log anomalies. Gate 1-100+ actors per run. GitHub Actions integration. $0.10 per actor.",
        "version": "1.0",
        "x-build-id": "QeDzVEfbjzTBkfeQf"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/ryanclinton~cicd-release-gate/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-ryanclinton-cicd-release-gate",
                "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~cicd-release-gate/runs": {
            "post": {
                "operationId": "runs-sync-ryanclinton-cicd-release-gate",
                "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~cicd-release-gate/run-sync": {
            "post": {
                "operationId": "run-sync-ryanclinton-cicd-release-gate",
                "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": [
                    "targets"
                ],
                "properties": {
                    "mode": {
                        "title": "Mode",
                        "enum": [
                            "gate",
                            "approveBaseline",
                            "bootstrapBaseline",
                            "dryRun"
                        ],
                        "type": "string",
                        "description": "gate = run checks and fail if any ERROR; approveBaseline = save current output as new golden; bootstrapBaseline = first-time baseline setup; dryRun = run all checks but never fail.",
                        "default": "gate"
                    },
                    "profile": {
                        "title": "Check Profile",
                        "enum": [
                            "store-default",
                            "extended",
                            "custom"
                        ],
                        "type": "string",
                        "description": "store-default = checks A-D only (Store equivalent); extended = all 9 checks; custom = use only checks explicitly configured in targets.",
                        "default": "store-default"
                    },
                    "targets": {
                        "title": "Targets",
                        "type": "array",
                        "description": "Array of actors to gate-check. Each entry requires actorIdOrName. Optionally specify build tag and test cases with custom inputs and expectations."
                    },
                    "portfolio": {
                        "title": "Portfolio Settings",
                        "type": "object",
                        "description": "Controls how multiple targets are processed. concurrency limits parallel runs; stopOnFirstFailure halts after the first target fails.",
                        "default": {
                            "concurrency": 3,
                            "stopOnFirstFailure": false
                        }
                    },
                    "billing": {
                        "title": "Billing Settings",
                        "type": "object",
                        "description": "PPE billing configuration. chargeEventName sets the event billed per run. chargePerTarget bills once per target actor rather than once for the whole gate run.",
                        "default": {
                            "chargeEventName": "gate-check",
                            "chargePerTarget": true
                        }
                    },
                    "report": {
                        "title": "Report Settings",
                        "type": "object",
                        "description": "Controls what is included in REPORT.json. format: summary | full. includeDiffs requires extended profile with golden check.",
                        "default": {
                            "format": "full",
                            "includeDiffs": true,
                            "includeSampleItems": true,
                            "includeLogTail": true
                        }
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
