# CSV Contract Preflight — CSV Validator (`satisfied_laptop/csv-contract-preflight`) Actor

CSV validator for AI agent handoffs: check required values, integers, header order and duplicate keys. Get a JSON verdict for $0.01/report. Up to 20,000 UTF-8 CSV bytes and 500 records.

- **URL**: https://apify.com/satisfied_laptop/csv-contract-preflight.md
- **Developed by:** [11](https://apify.com/satisfied_laptop) (community)
- **Categories:** Developer tools
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.01 / completed csv report

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## CSV Contract Preflight

Check a small CSV before handing it to a customer or importing it into a workflow. Find missing required values, invalid integers, rows with the wrong number of columns, and duplicate single or composite keys. String identifiers keep their original spelling: `0017` and `17` remain distinct.

**Price: $0.01 per completed report, including reports with findings.** Each completed report is available as a downloadable JSON file and one row in the run's default dataset.

**Fits small, repeatable handoffs:** up to **20,000 UTF-8 CSV bytes and 500 data records**, with agreed columns and string/integer rules. Paste CSV text and a JSON contract; file uploads and URL inputs are not supported.

Using an AI agent? See the [agent quickstart](#use-with-ai-agents) for the exact input, spending limit and report retrieval steps.

### Try the prefilled example

1. Open the Actor's input form. Both fields already contain the fictional supplier/SKU example below.
2. Check the displayed run price, then start the Actor. A valid input with findings completes successfully and produces a report.
3. On the run's **Output** tab, open **Download the JSON report**. You can also inspect or export the same report from **Storage > Dataset**.

When ready, replace **both** input fields with your CSV and its contract. Use data you have permission to process on Apify.

### Worked example: exact IDs, duplicate keys and integer quantities

```csv
supplier,sku,units
north,0017,12
north,17,4
north,0017,1.5
```

Use this contract to require an integer quantity and a unique supplier/SKU pair:

```json
{
  "fields": [
    {"name": "supplier", "type": "string"},
    {"name": "sku", "type": "string"},
    {"name": "units", "type": "integer", "constraints": {"required": true}}
  ],
  "primaryKey": ["supplier", "sku"]
}
```

**Expected result: 3 records, 2 findings.** Record 3 has a fractional quantity and repeats the key from record 1. Record 2 has a different key because `17` and `0017` are distinct strings. Record numbers start at 1 after the header.

The prefilled example produces this report:

```json
{
  "profile": "csv-contract-preflight-0.1",
  "verdict": "findings",
  "schema_sha256": "1d1decbbe248181a0e178ef7a54d93910f7ced856044ea54b9fc045071a7145c",
  "csv_sha256": "cd2331e899d3a74a60ab33c78e4e96747082f94a7071e1623936cb27f7ea1c67",
  "records": 3,
  "findings": [
    {"record": 3, "field": "units", "code": "invalid_integer"},
    {"record": 3, "first_record": 1, "code": "duplicate_primary_key"}
  ]
}
```

To see a passing example, change the last data record to `south,0017,8`. Its supplier/SKU pair is different and its quantity is an integer. The report will have `"verdict": "pass"` and an empty `findings` list.

### Use with AI agents

Agents can discover this Actor and run it through the [Apify MCP server](https://docs.apify.com/mcp). Use your own eligible, funded Apify account and authenticate the connection once. The agent can then run this tool and retrieve its report without task-specific human fulfilment. This is a paid Actor, priced at $0.01 per completed report, including reports with findings.

For MCP clients that accept a `mcpServers` configuration, add:

```json
{
  "mcpServers": {
    "csv-contract-preflight": {
      "url": "https://mcp.apify.com/?tools=search-actors,fetch-actor-details,call-actor,get-dataset-items"
    }
  }
}
```

Connect and sign in when your client prompts. Ask the agent to inspect `satisfied_laptop/csv-contract-preflight` with `fetch-actor-details`. Use this exact owner and Actor name when selecting the tool. For a small-data workflow, check that the CSV and declared contract fit the limits below before starting a run.

Call `call-actor` with the following JSON. The spending limit belongs in **`callOptions`**, alongside memory and timeout. The Actor input contains only `contractJson` and `csvText`.

```json
{
  "actor": "satisfied_laptop/csv-contract-preflight",
  "input": {
    "contractJson": "{\n  \"fields\": [\n    {\"name\": \"supplier\", \"type\": \"string\"},\n    {\"name\": \"sku\", \"type\": \"string\"},\n    {\"name\": \"units\", \"type\": \"integer\", \"constraints\": {\"required\": true}}\n  ],\n  \"primaryKey\": [\"supplier\", \"sku\"]\n}",
    "csvText": "supplier,sku,units\nnorth,0017,12\nnorth,17,4\nnorth,0017,1.5\n"
  },
  "waitSecs": 30,
  "callOptions": {
    "build": "0.1.6",
    "memory": 256,
    "timeout": 60,
    "maxTotalChargeUsd": 0.01
  }
}
```

This example pins the verified executable build `0.1.6`, requests 256 MB and a 60-second Actor timeout, and caps its completed-report charge at $0.01. `waitSecs: 30` is how long the MCP call waits for a response; it is not the Actor timeout. A response that says the run is still running is not permission to start another run.

The Actor produces the same three-record, two-finding report shown above. To retrieve it:

1. Save the run ID and default dataset ID returned by `call-actor`. If the run is still running, use `get-actor-run` with that run ID until it reaches a terminal state.
2. On `SUCCEEDED`, call `get-dataset-items` using the returned dataset ID and `limit: 2`. Expect exactly one report row. Read its `verdict` and `findings`; a successful run can contain data findings.
3. On `pass`, continue only under your workflow's own policy. On `findings`, hold or reject the handoff, or route it to an already authorized correction step. The Actor does not repair data or prove its truth.
4. For the downloadable JSON report, use `get-key-value-store-record` with the returned default key-value store ID and `recordKey: "OUTPUT"`. Apify automatically makes these run/storage tools available when `call-actor` is selected.

**Start once and retain the run identity.** Before dispatch, save a caller-side operation record and dispatch time. If the response is lost or times out, keep that operation in an unresolved-start state; a run may already exist.

- **Run ID returned:** use `get-actor-run` with that same ID, then read its storage. Do not start a replacement run while reconciling it.
- **No run ID returned:** reconcile the original start only through your own already-authorized run-history access, if configured. The automatically available result helpers do not include run-history lookup. If that access is unavailable or the original run cannot be identified, **stop or hold the operation as unresolved**. Do not automatically start another run.

Each separately started completed run is billable. The $0.01 limit applies to one run and does not limit how many new runs an agent starts. This Actor does not provide an exactly-once start guarantee across separate calls.

Apify stores the original input and output with the run. The report omits cell values but includes field names and record numbers. Check the platform/account retention settings; this service does not promise zero retention. Do not assume a refused or failed run has zero platform cost; review the charges applicable to the account.

### What the report means

- `verdict` is `pass` or `findings`. Both are completed, billable reports at the displayed price.
- Findings identify the data record and relevant field or the first record with the same primary key.
- `schema_sha256` and `csv_sha256` identify the submitted UTF-8 text. Pasting can change line endings, so compare hashes against the text submitted to this Actor.
- The report includes field names and record numbers and omits cell values. The run's input and output use Apify's default storage; the original input is still stored with the run.

A refused contract or unsupported input stops with an error explaining what to fix. It does not produce a completed report.

### Supported checks and limits

- UTF-8 CSV with an optional byte-order mark, a comma delimiter and a double-quote escape convention. The header must match the declared names and order.
- Up to 20,000 CSV bytes, 500 data records, 64 columns and 8,000 contract bytes. A header-only file yields a report with zero records.
- A JSON contract with `fields` and an optional `primaryKey`. Each field has a distinct `name`, an optional `type` of `string` (the default) or `integer`, and an optional boolean `constraints.required`.
- Empty cells count as missing. Integers accept ASCII digits with an optional sign. String key fields are required and compared literally, including case, whitespace and leading zeroes. A key can use one field or a list of distinct fields.
- Properties outside this profile are rejected before scanning the CSV. The field and key syntax comes from [Frictionless Table Schema v1](https://specs.frictionlessdata.io/table-schema/); this Actor supports only the subset described here.

### Common questions

**Does it fix or import my data?** No. It reports checks against your declared contract. It does not change the CSV, import it into another service, or certify that source values are true.

**Can I validate dates, decimals or email addresses?** This profile supports strings and integers. Specialized date, decimal and email validation is not included.

**Can my key contain numbers?** Declare key columns as strings to preserve their exact spelling. Primary keys can use one string column or a combination of string columns.

**Will a report with findings fail the Apify run?** No. Accepted input with data findings produces a successful run and a completed report. Review `verdict` and `findings` to decide whether the CSV is ready for your handoff.

# Actor input Schema

## `contractJson` (type: `string`):

Up to 8,000 UTF-8 bytes. Declare column order, string and integer types, required fields, and an optional literal primary key.

## `csvText` (type: `string`):

Paste up to 20,000 UTF-8 bytes and 500 data records. The first record is the header. Keep IDs in their original spelling.

## Actor input object example

```json
{
  "contractJson": "{\n  \"fields\": [\n    {\"name\": \"supplier\", \"type\": \"string\"},\n    {\"name\": \"sku\", \"type\": \"string\"},\n    {\"name\": \"units\", \"type\": \"integer\", \"constraints\": {\"required\": true}}\n  ],\n  \"primaryKey\": [\"supplier\", \"sku\"]\n}",
  "csvText": "supplier,sku,units\nnorth,0017,12\nnorth,17,4\nnorth,0017,1.5\n"
}
```

# Actor output Schema

## `reportUrl` (type: `string`):

Open the verdict, record count, input hashes, and row-level findings for this run.

# 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 = {
    "contractJson": `{
  "fields": [
    {"name": "supplier", "type": "string"},
    {"name": "sku", "type": "string"},
    {"name": "units", "type": "integer", "constraints": {"required": true}}
  ],
  "primaryKey": ["supplier", "sku"]
}`,
    "csvText": `supplier,sku,units
north,0017,12
north,17,4
north,0017,1.5`
};

// Run the Actor and wait for it to finish
const run = await client.actor("satisfied_laptop/csv-contract-preflight").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 = {
    "contractJson": """{
  \"fields\": [
    {\"name\": \"supplier\", \"type\": \"string\"},
    {\"name\": \"sku\", \"type\": \"string\"},
    {\"name\": \"units\", \"type\": \"integer\", \"constraints\": {\"required\": true}}
  ],
  \"primaryKey\": [\"supplier\", \"sku\"]
}""",
    "csvText": """supplier,sku,units
north,0017,12
north,17,4
north,0017,1.5
""",
}

# Run the Actor and wait for it to finish
run = client.actor("satisfied_laptop/csv-contract-preflight").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "contractJson": "{\\n  \\"fields\\": [\\n    {\\"name\\": \\"supplier\\", \\"type\\": \\"string\\"},\\n    {\\"name\\": \\"sku\\", \\"type\\": \\"string\\"},\\n    {\\"name\\": \\"units\\", \\"type\\": \\"integer\\", \\"constraints\\": {\\"required\\": true}}\\n  ],\\n  \\"primaryKey\\": [\\"supplier\\", \\"sku\\"]\\n}",
  "csvText": "supplier,sku,units\\nnorth,0017,12\\nnorth,17,4\\nnorth,0017,1.5\\n"
}' |
apify call satisfied_laptop/csv-contract-preflight --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,satisfied_laptop/csv-contract-preflight"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/kLghTtrolYoiD2Vnz/builds/NnIWBXzSHlTY28rmb/openapi.json
