Dataset Output Acceptance Gate avatar

Dataset Output Acceptance Gate

Pricing

from $50.00 / 1,000 run assesseds

Go to Apify Store
Dataset Output Acceptance Gate

Dataset Output Acceptance Gate

Decide whether a dataset is safe to send downstream. Check schema, coverage, duplicates, currency, geography, canaries, and a saved baseline, then return accepted, partial, or rejected.

Pricing

from $50.00 / 1,000 run assesseds

Rating

0.0

(0)

Developer

Olalekan Johnson

Olalekan Johnson

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

12 days ago

Last modified

Security

Limited permissions

Share

Decide whether a dataset is safe to send into a CRM, warehouse, report, or customer workflow. You give it a dataset from your own Apify account, or a public JSON/CSV file, plus a contract. It returns accepted, partial, or rejected, with the reasons a webhook can branch on.

It is built for automation agencies and small SaaS teams that already collect data and need a gate in front of the next step. A run that merely finishes is not treated as a complete business result.

The default input needs no token and no dataset. It checks the bundled office-supply sample against the products contract and returns an accepted verdict.

What you put in

InputRole
SourceBundled sample, a dataset ID, a run ID, a public JSON/CSV URL, or pasted JSON/CSV
PresetProducts, jobs, places, reviews, or a custom contract
ContractIdentity keys, types, required-field coverage, allowed values such as currency and country, min/max business rows, canaries
Empty allowanceAccept zero business rows when the source is quiet, including a status row that says no_results
BaselineA named key-value store, plus an optional previous dataset. Set a stable baseline key for scheduled runs
Fail on rejectionMark this run failed after the report is saved, so an integration stops

Dataset and baseline-dataset fields request read access only. The baseline store requests read and write. The Actor uses limited permissions. It does not ask for access to the rest of your account.

Run ID lookup uses the run's default dataset. If the limited-permission token cannot read that run, copy the run's default dataset ID into Dataset ID instead.

Public URLs must be http or https. Localhost, private IPs, link-local addresses, and embedded credentials are rejected.

What you get back

The default dataset is flat, so a webhook, Make, n8n, or a spreadsheet can filter it without unpacking nested objects.

The first row is the verdict:

{
"recordType": "verdict",
"verdict": "accepted",
"coverageStatus": "complete",
"confidence": "medium",
"gateFailed": false,
"materialChange": true,
"businessRows": 16,
"uniqueEntities": 16,
"duplicateRatio": 0,
"statusOnlyRows": 0,
"dominantCurrency": "USD",
"dominantCountry": "US",
"currencyShift": false,
"geographyShift": false,
"coverageCollapse": false,
"legitimatelyEmpty": false,
"lastKnownGoodDatasetId": null,
"chargedRunAssessed": 1,
"chargedThousandRows": 0,
"sourceRef": "bundled:samples/products.json",
"preset": "products",
"profileKey": "products:bundled-sample",
"reasons": "",
"observedAt": "2026-10-23T04:19:00Z"
}

Further rows are checks (recordType: "finding") and per-field coverage (recordType: "field"). The same verdict is repeated on every row.

coverageStatus is separate from the verdict:

StatusMeaning
completeThe whole source was read and it contains business rows
partialThe read stopped at the row cap, so the rest was not judged
emptyNo business rows
blockedThe source could not be read (permissions, private URL, HTTP error)
unsupportedThe body was not a JSON or CSV table

The run key-value store holds:

  • OUTPUT — counts, reasons, and charged units
  • ACCEPTANCE_REPORT — findings, field coverage, and the contract that was used
  • ACCEPTANCE-REPORT.html — the same profile as a page

An accepted run writes a profile to the named store acceptance-gate-baselines (or the store you name). The profile keeps coverage, currency and country mix, unique-entity count, and lastKnownGoodDatasetId. A partial verdict, a rejection, or a crash does not replace that profile. If this accepted run has no dataset ID, the previous last-known-good dataset ID stays in the profile.

Confidence is medium on the first accepted run because there is no baseline yet. Later accepted runs that match a baseline can be high.

What the contract catches

  • Present values with the wrong type, and review ratings outside 0–5
  • Required fields below their coverage threshold
  • A critical field whose coverage falls below 80% of the baseline (coverage that was already under 50% is ignored)
  • Duplicate identities. Any duplicates make the verdict partial. Above the duplicate cap (default 2%) rejects
  • Currency or country values outside an allow-list
  • A new currency or country on more than 5% of rows, compared with the baseline
  • A dominant currency or country change inside the values the baseline already saw (partial, not rejected)
  • A dataset of status or error objects and no business rows
  • A quiet source, when you turn on Allow a legitimately empty result
  • Canary records that must appear, matched on the fields you list
  • A large drop in unique entities, or a jump in upstream cost per unique entity, when the source run reports usage

Cost per unique entity is a warning. This Actor cannot refund the upstream run that already finished.

Min and max row counts apply to business rows, after status objects are removed. The check window defaults to 50,000 rows and cannot exceed 200,000. A window that stops early is partial, and it does not move the baseline, because the unseen rows were not judged.

Presets

PresetIdentity, in orderRequired coverage
Productssku, then productId, then idname 95%, price 90%, currency 90%, url 80%, sku 90%
JobsjobId, then id, then urltitle 95%, company 90%, url 80%
PlacesplaceId, then id, then urlname 95%, address 80%, latitude and longitude 70%
ReviewsreviewId, then id, then urlrating 98% (and rating must be from 0 to 5), text 70%
CustomOnly what you typeOnly what you type

Alternate identity uses the first key that is filled on at least half of the business rows. Composite identity requires every key. Leave the override lists empty to keep the preset. Values you do set are merged on top.

The bundled sample is synthetic catalog data created for this Actor. It is not a scrape of a store. One product has no URL and one has no brand, which is still inside the products thresholds.

Pricing

Pay per event, for a check that finished and was written. A source that cannot be read is not charged. There is no per-dataset-item event.

EventWhen it is chargedPrice
Run assessedOne completed verdict$0.05
1,000 rows checkedOne event per full 1,000 rows read. The remainder is included in the run assessment$0.002
Platform startOnce per run, per GB of memory. This Actor defaults to 512 MB$0.005 per GB, about $0.0025 per run

A baseline dataset is not billed as a second assessment. The platform also charges the start event in the table. Those are the rates this Actor is priced at.

Trial. 20 assessments of 400 rows. 20 × $0.05 = $1.00. No thousand-row events. Start charges about 20 × $0.0025 = $0.05. About $1.05.

A production estate. 300 assessments of 50,000 rows. 300 × $0.05 = $15. 300 × 50 × $0.002 = $30. Start charges about $0.75. About $45.75.

A capped run. One 50,000-row check is $0.05 + $0.10 + about $0.0025, roughly $0.15, if the row cap stays at 50,000. If the task's maximum charge is lower, charging stops when the platform budget is exhausted, chargeLimitReached is true, and unbilled thousand-row units are not charged. The data verdict is still returned. Raising the check window to the 200,000-row maximum is $0.05 + $0.40, plus the start charge, for a single assessment.

Wire it into a schedule

  1. Let the upstream Actor finish.
  2. Call this Actor with that run's default dataset ID and a stable Baseline key, such as acme-products-daily. A new run ID on every schedule is fine. The key is what keeps the profile.
  3. Turn on Fail the run when rejected if the next step should not run.
  4. In the webhook, Make, or n8n branch, read the dataset and keep the row where recordType is verdict.
  5. Stop the downstream write when gateFailed is true or verdict is rejected. Treat partial as needs a person unless you have decided partial data is still loadable.
  6. For a quiet alert, turn on Dataset rows only when the profile changes and filter to materialChange true. The HTML and JSON reports are still saved on every run.

Sheets: download the dataset as CSV. The columns are already flat.

A rejected gate does not undo charges from the Actor that produced the dataset. Put this gate before the system that spends money on the rows, not after a customer has already been billed for them.

Limits

  • Types are checked as stored. A price that arrives as text fails a number field. CSV input does parse numeric cells.
  • Country and currency comparison is literal after trimming and case-folding. UK and GB are different until you put both in the allow-list.
  • The baseline stores coverage and the dataset ID, not a second copy of the rows. The report names a short identity when a type check fails. It does not copy review text.
  • Two runs writing the same baseline key at the same time can race. Give each workflow its own key and one schedule.
  • The first accepted run creates the baseline. It cannot yet detect a currency shift.
  • Memory is 512 MB, with a 1,024 MB ceiling. Very wide rows near the 200,000 cap can be tight. Lower the check window if a run runs out of memory.
  • This version does not start a fallback Actor.

Run it locally

Python 3.12.

pip install -r requirements.txt
python -m src

With no INPUT.json, the Actor uses the bundled product sample. Output is under storage/: the dataset, OUTPUT, and ACCEPTANCE-REPORT.html.

pip install pytest
pytest

FAQ

Why was a successful upstream run rejected? The upstream run can succeed and still return status objects, the wrong currency, or a field that collapsed. coverageStatus says how much was read. verdict says whether it met the contract.

A quiet day keeps failing. Turn on Allow a legitimately empty result, or set the minimum business rows to 0. An error status is still rejected. A row that only says the source was empty (emptyReason no_results, or status empty) is accepted when the allowance is on.

Partial and rejected feel different. Rejected means a hard contract failure: do not load the data. Partial means the rows are usable with a warning, such as a few duplicates or a dominant currency that changed inside the known set. Only an accepted verdict replaces the baseline.

Will a bad run erase yesterday's good dataset ID? No. The last accepted profile, including lastKnownGoodDatasetId, is left in place.

Does the default run call the internet? No. It reads samples/products.json inside the Actor.

Questions: plusonecares@gmail.com.