# Airtable Schema Diff - Compare & Monitor Bases (`mediocre_interest/airtable-schema-drift`) Actor

Compare two Airtable bases field by field, or snapshot one base and see exactly what changed since last run. Renames are reported as renames, not as a delete plus an add. Read-only token, schema.bases:read only - no record access.

- **URL**: https://apify.com/mediocre\_interest/airtable-schema-drift.md
- **Developed by:** [Mediocre\_Interest](https://apify.com/mediocre_interest) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.05 / base diffed

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Airtable Schema Diff - Compare & Monitor Bases

**Find out exactly what changed in your Airtable base — which field was renamed, which formula broke, which link now points somewhere else.** Compare two bases against each other to see how far copies of a template have drifted apart, or snapshot one base and diff it against its own previous run. Leave the token empty and press Start to see a worked example on two bundled sample bases; nothing to install and no credentials to try it.

- **Renames are renames.** A renamed field is one row, not a delete plus an add, because the diff matches on Airtable's stable field IDs rather than on names.
- **Broken formulas are found.** Airtable marks a computed field invalid in its schema and surfaces it nowhere in the UI. Every run catches the moment a formula, rollup, lookup or count stops evaluating.
- **Read-only, one scope.** `schema.bases:read` and nothing else. The Actor never reads a single record.
- **Deterministic.** No AI model, no scoring heuristic. The same two schemas always produce the same rows.

### What does Airtable Schema Diff do?

Airtable has no schema history. There is no audit log of structural change on any plan below Enterprise, no way to ask what a base looked like last Tuesday, and no warning when someone deletes a field that three Make scenarios and a Zapier zap are reading by name. A base that started as a clean template accumulates a `Notes 2` here and a retyped currency field there until the copies an agency runs for eight clients no longer share a shape — and the first anyone hears of it is an integration failing in production.

This Actor reads your base structure through Airtable's metadata API and compares two versions of it. In **Compare two bases** mode, every base you list is checked against one template base, which is how you find the client copy that has drifted. In **Track one base over time** mode, each base's structure is stored in a named key-value store and compared against the snapshot from the previous run, which is how you get a daily changelog. Snapshots are kept indefinitely and are not tied to a single run.

Each difference becomes one flat row in the dataset: what changed, where, the value before and after, how serious it is, and what to do about it. Alongside the rows you get one summary row per base and a self-contained web page per base you can send to whoever owns it — it needs no Airtable access and no Apify account to read. Everything runs on Apify, so you can schedule it daily, call it from the API, pipe it into Make, n8n, Zapier or Slack, and export the results as JSON, CSV or Excel.

#### Which schema changes does it detect?

Severity is about what breaks downstream, not how surprising the change is. Rows are written worst first.

| Severity   | Change                                               | What it catches                                                                                       |
| ---------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `critical` | `fieldBroke`                                         | A formula, rollup, lookup or count has stopped evaluating. Airtable reports this nowhere else.        |
| `critical` | `fieldRemoved`                                       | A field is gone, so everything reading it now fails.                                                  |
| `critical` | `tableRemoved`                                       | A whole table is gone.                                                                                |
| `critical` | `linkTargetChanged`                                  | A link field now points at a different table — joins that used to resolve now resolve somewhere else. |
| `critical` | `primaryFieldChanged`                                | What every linked record displays as has changed, in every table that links here.                     |
| `high`     | `fieldRenamed`                                       | A field was renamed. Field IDs are stable, so this is never confused with a delete plus an add.       |
| `high`     | `fieldTypeChanged`                                   | The field's JSON type in the API response has changed.                                                |
| `high`     | `selectOptionRemoved`                                | A dropdown option was deleted, so records holding it lose their value.                                |
| `high`     | `linkInverseChanged`                                 | A two-way link became one-way, or the reverse.                                                        |
| `medium`   | `fieldAdded`, `tableAdded`, `selectOptionAdded`      | New structure appeared — a table, a field, or a dropdown option.                                      |
| `medium`   | `fieldRepaired`                                      | A previously broken computed field evaluates again.                                                   |
| `medium`   | `tableRenamed`, `selectOptionRenamed`                | A rename that keeps its ID, so records keep their values.                                             |
| `medium`   | `fieldOptionsChanged`, `formulaChanged`              | Precision, currency symbol, date format, time zone, or the formula text itself.                       |
| `medium`   | `viewAdded`, `viewRemoved`                           | A view appeared or disappeared.                                                                       |
| `low`      | `fieldDescriptionChanged`, `tableDescriptionChanged` | Documentation edits.                                                                                  |
| `low`      | `viewRenamed`, `viewFieldsChanged`                   | Cosmetic view changes.                                                                                |
| `low`      | `fieldOrderChanged`, `viewOrderChanged`              | Someone dragged a column or a view tab.                                                               |

The five kinds of churn that happen without anyone deciding anything — description edits, hidden columns, field order, view order and dropdown colours — are switched off by default, so a run on a schedule stays worth reading. Turn any of them back on in **Changes to ignore**.

### Why use Airtable Schema Diff?

- **It finds the break Airtable hides.** A rollup that silently stopped evaluating looks normal in the grid. It is a `critical` row here, on the run it happens.
- **Nothing to install, no credentials to start.** Press Start with the token empty and the Actor compares two bundled sample bases: 12 changes across 4 tables and 17 fields, in about a second.
- **Safe by construction.** One read-only scope, `schema.bases:read`, on a personal access token you grant base by base. No record data is read, so no record data can reach the dataset.
- **Built for agencies and ops teams.** Point it at a template and eight client copies and it tells you which copy drifted and how far, in one run.
- **Deterministic and auditable.** No AI model and no weighted drift score — a dozen description edits can never outrank one broken formula. The dataset carries the counts; you decide what matters.
- **Runs on the Apify platform.** Schedule it daily, trigger it from the API, connect it to Make, n8n, Zapier or Slack, and export to JSON, CSV or Excel.

### How to compare two Airtable bases

To see what the output looks like with nothing set up:

1. Leave **Airtable personal access token** empty.
2. Press **Start**.
3. Open the **Changes** tab. You are looking at 12 differences between two sample bases, worst first, each marked `source: "sample"`.

To run it against your own bases:

1. Create a personal access token at [airtable.com/create/tokens](https://airtable.com/create/tokens).
2. Give it the **`schema.bases:read`** scope. Nothing else is needed.
3. Under **Access**, add each base you want compared. A personal access token lists its bases individually — a base you do not add here is invisible to the token.
4. Paste the token into **Airtable personal access token**.
5. Choose **Compare two bases** and list your bases, template first — or choose **Track one base over time** and list the bases you want a changelog for.
6. Press **Start**, then open the **Changes** tab for the differences and **Bases compared** for the per-base totals.

In **Track one base over time**, the first run of any base stores its snapshot and reports no changes, because there is nothing yet to compare against. The second run is the one that produces a changelog.

### Input

| Field                              | What it does                                                                                                                                            |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Airtable personal access token** | A read-only token with the `schema.bases:read` scope. Leave it empty to compare two bundled sample bases instead of your own.                           |
| **What to compare**                | *Compare two bases* checks every base against one template. *Track one base over time* compares each base against its own previous run.                 |
| **Bases**                          | Base IDs, one per line. Paste whole base URLs if that is easier — the ID is taken out of them. Leave empty to use every base the token can see.         |
| **Template base**                  | The base everything else is compared against. Leave it empty to use the first base listed, so you can paste the template first and its copies after it. |
| **Changes to ignore**              | Kinds of change that will not be reported. The default hides the churn a base produces on its own.                                                      |
| **Minimum severity**               | Drop changes milder than this.                                                                                                                          |
| **Snapshot store name**            | The named key-value store holding one snapshot per base, used when tracking over time. Change it only to keep two schedules from sharing snapshots.     |
| **Write a shareable report**       | Produce a self-contained web page per base, linked from that base's summary row.                                                                        |
| **Maximum bases**                  | Stop after this many bases.                                                                                                                             |

See what the output looks like, with no Airtable account involved:

```json
{
    "airtableToken": "",
    "mode": "comparative"
}
```

Check three client bases against a template, and report only what breaks something:

```json
{
    "airtableToken": "<your Airtable personal access token>",
    "mode": "comparative",
    "compareToBaseId": "appTEMPLATE000000",
    "baseIds": ["appCLIENTONE00000", "appCLIENTTWO00000", "appCLIENTTHREE000"],
    "minSeverity": "high"
}
```

Keep a daily changelog of one base, including the description edits that are hidden by default:

```json
{
    "airtableToken": "<your Airtable personal access token>",
    "mode": "temporal",
    "baseIds": ["https://airtable.com/appCLIENTONE00000/tblPjlsmyaLlNAekw"],
    "ignore": ["viewFieldVisibility", "fieldOrder", "viewOrder", "selectColors"]
}
```

### Output

Every run writes one row per change to the default dataset, one row per base to the **Bases compared** dataset, and — unless you switch reports off — one shareable web page per base plus a `summary-run` record to the key-value store. Export any of it as JSON, CSV or Excel, or read it from the API.

These two rows are from the first input example above, the one that needs no token. The broken rollup:

```json
{
    "source": "sample",
    "runAt": "2026-09-30T06:52:15.590Z",
    "mode": "comparative",
    "baseId": "appDRIFTEDCLONE01",
    "baseName": "Northwind Ltd (drifted copy)",
    "comparedToBaseId": "appm2rdjG6AkpyytA",
    "comparedToBaseName": "Client Portal Template (demo)",
    "comparedToAt": "2026-08-01T09:00:00.000Z",
    "changeType": "fieldBroke",
    "severity": "critical",
    "scope": "field",
    "tableId": "tblYf7TsieTYspz0b",
    "tableName": "Projects",
    "fieldId": "fldZQHqO9S9ooAZum",
    "fieldName": "Hours Logged",
    "fieldType": "rollup",
    "viewId": "",
    "viewName": "",
    "path": "Projects.Hours Logged.isValid",
    "before": "true",
    "after": "false",
    "message": "Field \"Hours Logged\" (rollup) in \"Projects\" is broken: Airtable reports it as no longer valid, so it has stopped evaluating.",
    "remediation": "Open the field in Airtable and fix its configuration. A computed field usually breaks because a field or link it referenced was deleted or retyped."
}
```

And the rename, reported as one row rather than as a removal and an addition:

```json
{
    "changeType": "fieldRenamed",
    "severity": "high",
    "scope": "field",
    "tableName": "Clients",
    "fieldName": "Primary Email",
    "fieldType": "email",
    "path": "Clients.Primary Email",
    "before": "Contact Email",
    "after": "Primary Email",
    "message": "Field \"Contact Email\" was renamed to \"Primary Email\" in \"Clients\".",
    "remediation": "Update anything that addresses the field by name. Most no-code tools do - Airtable field IDs are stable across a rename, but names are what integrations usually store."
}
```

#### What does each change row contain?

| Columns                                                                        | What they mean                                                                                                                          |
| ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `changeType`, `severity`, `scope`                                              | What kind of change it is, how much it breaks, and whether it is about a table, a field or a view.                                      |
| `path`, `tableName`, `fieldName`, `viewName`, `fieldType`                      | Where in the base, in names you recognise — `Projects.Hours Logged.isValid`.                                                            |
| `before`, `after`                                                              | The old and new values, as text. Empty on the side where the thing did not exist.                                                       |
| `message`, `remediation`                                                       | The change in one sentence, and what to check or fix. `remediation` is empty where there is nothing to do.                              |
| `baseId`, `baseName`, `comparedToBaseId`, `comparedToBaseName`, `comparedToAt` | Which base, and what it was compared against — the template, or the previous run and when that snapshot was taken.                      |
| `mode`, `source`, `runAt`                                                      | How the comparison was made, whether the row came from your bases (`live`) or the bundled samples (`sample`), and when the run started. |
| `tableId`, `fieldId`, `viewId`                                                 | Airtable's own IDs, which survive renames. Address these rather than names if you build on the output.                                  |

#### What is in the "Bases compared" table?

One row per base, worst first: `changesCritical`, `changesHigh`, `changesMedium`, `changesLow` and `changesTotal`, the base's current `tableCount` and `fieldCount`, what it was compared against, and a link to its report. A row with `isBaseline` set is the first time this Actor saw that base, so it stored a snapshot and had nothing to compare against yet.

There is deliberately no single drift score. Any weighted sum lets a dozen description edits outrank one broken formula, so the table gives you the counts by severity and sorts on those instead.

#### What is in a drift report?

One self-contained web page per base, listing every change worst first, with the same before-and-after values and remediation text as the rows. It is a single HTML file with no external assets, so you can attach it to a ticket or send it to whoever owns the base — they need no Airtable access and no Apify account to read it. The link appears in the `reportUrl` column of that base's summary row.

### How much does it cost to compare Airtable bases?

The Actor is **pay per event**, at the prices shown on this page.

| Event         | Charged                | When                                                    |
| ------------- | ---------------------- | ------------------------------------------------------- |
| `base-diffed` | Once per base compared | After that base's rows have been written to the dataset |

A run that compares one template against eight client copies charges eight events — the template itself is not charged, because it is the thing being compared against. A run in **Track one base over time** charges once per base each run, including the first, because the base's structure is read and stored either way.

Nothing is charged for a base that could not be read: a base the token was not granted, a base ID that does not exist, or a base beyond the **Maximum bases** cut-off costs nothing. A run with no token compares the bundled samples and is free. A failed run charges nothing at all.

The Actor respects the **maximum cost per run** you set on the run. If your budget covers four bases and you listed nine, it compares four, writes their rows, and says so in the run's status message rather than quietly delivering work you have not paid for. If the budget does not cover a single base, the run fails and tells you why.

### How to run an Airtable schema diff from the API or from Make, n8n and Zapier

Call it from anywhere with one request, and get the rows back in the response:

```bash
curl -X POST "https://api.apify.com/v2/acts/mediocre_interest~airtable-schema-drift/run-sync-get-dataset-items?token=<your Apify API token>" \
  -H "Content-Type: application/json" \
  -d '{
        "airtableToken": "<your Airtable personal access token>",
        "mode": "comparative",
        "compareToBaseId": "appTEMPLATE000000",
        "baseIds": ["appCLIENTONE00000", "appCLIENTTWO00000"],
        "minSeverity": "high"
      }'
```

That returns the change rows as a JSON array once the run finishes. Add `&format=csv` for a spreadsheet, or `&fields=severity,changeType,path,message` to keep the response small enough to drop straight into a Slack message.

From there, the Apify integrations do the rest: the **Apify app for Make**, the **Apify node for n8n** and the **Apify Zapier integration** can all start this Actor and read its dataset, so a `critical` row can open a ticket or post to a channel the moment it appears. Apify's own **Schedules** run it daily without any of that — pair a daily schedule with **Track one base over time** and you have a changelog for every base you own.

### Tips for better results

- **Put the template first.** In *Compare two bases*, leaving **Template base** empty makes the first base in the list the template. Pasting the template and then its copies is the fastest way to set up an agency check.
- **Start at `high` severity, then loosen.** `critical` and `high` are the rows that break integrations. Once those are clean, drop to `low` to see the full picture.
- **Turn descriptions back on if you use them as documentation.** `tableDescriptions` is reported by default but `fieldDescriptions` is not, because most bases churn them. If yours are load-bearing, remove `fieldDescriptions` from **Changes to ignore**.
- **Give each schedule its own snapshot store.** Two schedules tracking the same base with the same **Snapshot store name** overwrite each other's snapshots, and each run then diffs against the other's. One store name per schedule keeps them independent.
- **Grant the token every base you list.** A base missing from the token's **Access** list is refused by Airtable with the same error as a base that does not exist, and the run reports it as skipped.

### FAQ

#### Is my Airtable token safe?

The token is a secret input: Apify encrypts it, the Console masks it, and it is never written to the dataset, the reports or the run log. The Actor only ever sends it to `api.airtable.com`. Give it the `schema.bases:read` scope alone and it cannot read a record even if it wanted to.

#### Does this change anything in my Airtable base?

No. Both endpoints it calls are read-only, and the only write scope it could use is one it never asks for. It reads your base structure and writes rows to Apify.

#### Does it use an AI model?

No. The diff is a deterministic comparison of two schemas: the same two schemas always produce the same rows, in the same order. Severity comes from a fixed table you can read in full above, not from a model's judgement.

#### Why did my run report no changes?

Three common reasons. In *Track one base over time*, the first run of a base only stores its snapshot — run it again after a change. In *Compare two bases*, the bases genuinely match. Or the changes are all in the categories switched off by default: description edits, hidden columns, field order, view order and dropdown colours. Clear **Changes to ignore** to see everything.

#### Why is a base reported as skipped?

Airtable answers with the same error for three different causes, and will not say which: the base ID is wrong, the token was not granted that base under **Access**, or the token is missing the `schema.bases:read` scope. The run names the base and lists all three, and carries on with the bases it could read. Nothing is charged for a base that was skipped.

#### Does it detect changes to view filters, sorts or grouping?

No, and nothing here claims otherwise. Airtable's metadata API returns only a view's ID, name, type and which fields are visible in it — there is no filter, sort, grouping or row-height information in the response at all. View *configuration* drift is not visible to any Airtable API client.

#### Does it need a paid Airtable plan?

No. `schema.bases:read` is available on every Airtable plan, including Free, and a base read-only role is enough. Airtable's own structural audit log is Enterprise-only, which is most of the reason this Actor exists.

#### How many bases can it handle in one run?

As many as your token can see, up to the **Maximum bases** limit, which defaults to 200. Bases are read ten at a time, so a fleet of 50 is a matter of seconds. A single base at Airtable's own ceiling — 1,000 tables of 500 fields — arrives as one large response, which is why the Actor's default memory is set high enough to parse it.

### What other Actors work with this one?

- [Airtable Base Schema Auditor & Link Checker](https://apify.com/mediocre_interest/airtable-schema-auditor) — audits a single base for broken links, orphaned tables and configuration problems. It tells you what is wrong with a base right now; this Actor tells you what changed. Run the auditor when a `critical` row appears here and you go straight from *what changed* to *what it broke*.
- [Airtable Base Documentation & ER Diagram Generator](https://apify.com/mediocre_interest/airtable-base-documentation) — turns a base into readable documentation and an entity-relationship diagram, which is the thing to regenerate once a diff shows the structure has moved.
- [Make.com Scenario Backup & GitOps - Blueprints](https://apify.com/mediocre_interest/make-scenario-backup) — versions the Make scenarios that read these bases, so a field rename found here can be traced to the scenarios that address it by name.

### Support

Report a problem on the **Issues** tab of this Actor. Include the run ID and the smallest input that reproduces it — for a diff question, the two base IDs and the mode are usually enough. Custom work and changes to the output shape are available on request through the same tab.

# Actor input Schema

## `airtableToken` (type: `string`):

A read-only Airtable personal access token. Only the <code>schema.bases:read</code> scope is needed — this Actor never reads a single record. Create one at <a href='https://airtable.com/create/tokens' target='_blank'>airtable.com/create/tokens</a> and add each base you want under <b>Access</b>.<br><br><b>Leave this empty to see a worked example.</b> The Actor then compares two bundled sample bases instead of your own, and every row it writes is marked <code>source="sample"</code>. That run is free.

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

<b>Compare two bases</b> checks every base below against one template base, so you can see how far copies of the same template have drifted apart.<br><br><b>Track one base over time</b> stores a snapshot of each base and compares it against the snapshot from the previous run. The first run of any base stores its snapshot and reports no changes, because there is nothing yet to compare it against.

## `baseIds` (type: `array`):

Airtable base IDs, one per line. Paste the whole base URL if that is easier — the ID is taken out of it. Leave empty to use every base the token can see.

## `compareToBaseId` (type: `string`):

The base everything else is compared against. Only used when comparing two bases. Leave it empty to use the first base listed above, which lets you paste the template first and its copies after it.

## `ignore` (type: `array`):

Kinds of change that will not be reported. The defaults hide the churn that every base produces on its own — someone hiding a column, dragging a field, recolouring a dropdown — so the output stays worth reading on a daily schedule.

## `minSeverity` (type: `string`):

Drop changes milder than this. Leave it at <b>Low</b> to see everything; the output is sorted worst first either way.

## `snapshotStoreName` (type: `string`):

The named key-value store that holds one snapshot per base, used when tracking a base over time. Snapshots are overwritten each run, one record per base. Change this only to keep two schedules from sharing snapshots.

## `includeHtmlReport` (type: `boolean`):

Produce a self-contained web page per base listing every change, worst first, ready to send to whoever owns the base. The link appears on that base's summary row.

## `maxBases` (type: `integer`):

Stop after this many bases.

## Actor input object example

```json
{
  "mode": "comparative",
  "baseIds": [],
  "ignore": [
    "fieldDescriptions",
    "viewFieldVisibility",
    "fieldOrder",
    "viewOrder",
    "selectColors"
  ],
  "minSeverity": "low",
  "snapshotStoreName": "airtable-schema-snapshots",
  "includeHtmlReport": true,
  "maxBases": 200
}
```

# Actor output Schema

## `changes` (type: `string`):

No description

## `summaries` (type: `string`):

No description

## `reports` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "baseIds": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("mediocre_interest/airtable-schema-drift").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 = { "baseIds": [] }

# Run the Actor and wait for it to finish
run = client.actor("mediocre_interest/airtable-schema-drift").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 '{
  "baseIds": []
}' |
apify call mediocre_interest/airtable-schema-drift --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mediocre_interest/airtable-schema-drift"
        }
    }
}
```

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/9wNe4z0qk7k517QtW/builds/JQuGE4Kq59IlBg0fu/openapi.json
