← Learn

Migrate from Airtable (or Google Sheets) to Relm

Your Airtable base or Google Sheet already is a CRM: names, a Status column, a notes field, maybe a Kanban view. Moving it into Relm is one agent run - export the CSVs, hand your AI agent the files and an API key, and it maps, loads and verifies everything through the batch API.

This guide is for the spreadsheet-as-CRM crowd. You never bought a CRM; you built one out of a table, and it worked - which is exactly why you now have 2,000 rows, a Status single-select doing the job of a pipeline, and an AI agent that has to parse a grid to find out who to follow up with. The migration below turns that table into typed CRM objects an agent can operate directly - no hand-copying.

Why move off the spreadsheet

Airtable is a superb general-purpose database, and Google Sheets is the most flexible tool ever shipped. Nothing here is a knock on either. But a spreadsheet used as a CRM has structural limits that show up exactly when an AI agent becomes the main operator:

Step 1: export your data

In Airtable, exports are per table: open each table in a grid view and download it as CSV from the view menu. A base with Contacts, Companies and Deals tables produces three files. Three export behaviors matter for the mapping:

In Google Sheets, download each tab as CSV (File, then Download). One tab per file, same idea. Put everything in one folder - that folder plus an API key is the entire input.

Step 2: the mapping

Here you decide what your tables were pretending to be. The good news: almost every spreadsheet CRM converges on the same shapes.

Airtable / Sheets sourceRelm object / fieldNotes
Companies table, or a Company text columncompany: Name → name, Website → domainCreate these first; everything else links to them. Dedupe company rows in the CSV before loading - the API creates one company per call.
Contacts / Leads tablecontact: Full Name split into first_name + last_name, Email → email, Phone → phone, LinkedIn → linkedin_url, linked Company → company_idEmail is optional - blank cells are fine. Company names resolve to the cmp_ IDs created in the previous pass.
Status single-select (New, Contacted, Qualified, Won, Lost...)a pipeline whose stages are your options, 1:1; each row becomes a deal in its stageOne POST /v1/pipelines call. Your Kanban view grouped by Status becomes a real board.
Deal Value / Amount (currency field)deal: value_centsMultiply by 100; currency defaults to USD.
Notes long-text, call logs, Last Contacted datesactivity (note / call) with occurred_atBackdate to the original date so history stays history.
Custom columns: Source, Lead Score, Tier, tagscustom_fields on the matching objectRegister each once via POST /v1/fields, then send under custom_fields. Typed - a number field rejects "high".
Formula / rollup / lookup columnsusually drop; keep important ones as a custom_fields snapshotThe logic is replaced by automations and agent queries (see below).
Attachment columnsURLs into a text custom field or an activity noteExported URLs expire - re-host files you care about and store the durable link.

Step 3: rehearse in test mode

Do the first run with a relm_test_ key. Test mode writes to an isolated dataset that is free, unlimited, and invisible to live data and billing - a full-dress rehearsal with zero risk. When the numbers check out, swap in the relm_live_ key and run the identical pass.

Step 4: one-time setup

Two setup calls before any rows load - register the custom fields your columns map to, and create a pipeline whose stages are exactly your Status options. Re-running them is safe: field registration is idempotent on the key, and a duplicate pipeline create returns a 409 you can ignore:

# register custom fields once, then send them under custom_fields
curl -X POST https://api.relmcrm.com/v1/fields \
  -H "Authorization: Bearer relm_test_..." -H "Content-Type: application/json" \
  -d '{ "object": "contact", "key": "source", "data_type": "text" }'

curl -X POST https://api.relmcrm.com/v1/fields \
  -H "Authorization: Bearer relm_test_..." -H "Content-Type: application/json" \
  -d '{ "object": "contact", "key": "lead_score", "data_type": "number" }'

# a pipeline whose stages ARE your Status options, 1:1
curl -X POST https://api.relmcrm.com/v1/pipelines \
  -H "Authorization: Bearer relm_test_..." -H "Content-Type: application/json" \
  -d '{ "name": "Leads", "stages": ["new", "contacted", "qualified", "won", "lost"] }'

Step 5: hand it to your agent

Point any MCP client - Claude, Cursor, anything that speaks the protocol - at Relm's MCP server at https://api.relmcrm.com/mcp with your key in the Authorization header (config snippet in the docs), give it the export folder, and prompt:

Import my Airtable CRM into Relm from the CSVs in ./export.
1. Call relm_describe_schema first and read what exists.
2. Create a pipeline named "Leads" whose stages are exactly the
   options in my Status column: New, Contacted, Qualified, Won, Lost.
3. Register custom fields: contact.source (text), contact.lead_score (number).
4. Load in dependency order: companies first, then contacts (resolving
   the Company column to company_id), then one deal per row that has a
   Status, then Notes as activities with occurred_at = Last Contacted.
5. Use relm_batch, 100 operations at a time. Rows without an email
   are fine - import them anyway.
6. When done, report counts per object and every failed row.

The dependency order matters because IDs are server-assigned: companies → contacts → deals → activities. Within a single batch, operations cannot reference each other's new IDs, so the agent runs one pass per object type and carries the returned IDs forward. If it sends an unknown stage or field, the 422 comes back with valid_options and a did-you-mean suggestion, and it self-corrects - the same contract described in how to give your AI agent a CRM.

Under the hood: what the agent sends

POST /v1/batch takes up to 100 operations per call and returns a per-op result array - one bad row does not fail the batch. Here is a mid-import batch with spreadsheet-realistic values; the cmp_ and con_ IDs were returned by earlier passes:

curl -X POST https://api.relmcrm.com/v1/batch \
  -H "Authorization: Bearer relm_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "operations": [
    { "method": "create", "object": "contact",
      "data": { "first_name": "Maya", "last_name": "Chen",
                "email": "[email protected]",
                "company_id": "cmp_9k2f8x3mq1",
                "custom_fields": { "source": "Webinar", "lead_score": 82 } } },
    { "method": "create", "object": "deal",
      "data": { "title": "Brightloop - annual", "pipeline": "leads",
                "stage": "qualified", "value_cents": 480000,
                "primary_contact_id": "con_7d4h2n8pw5" } },
    { "method": "create", "object": "activity",
      "data": { "type": "note", "contact_id": "con_7d4h2n8pw5",
                "body": "Asked for annual pricing (Notes column)",
                "occurred_at": "2026-03-14T00:00:00Z" } }
  ]}'

{ "object": "batch", "count": 3, "succeeded": 3, "failed": 0, "results": [...] }

Two guardrails keep re-runs safe. Creating a contact whose email already exists returns a 409 conflict with the existing record in the response, so a second pass resolves to the same contact instead of duplicating it. For rows without a natural key - deals, activities, no-email contacts - send them as individual creates with an Idempotency-Key header per row: the header applies to POST /v1/<object> calls (not to operations inside a batch), and a retried write replays the original result. If the agent dies at row 1,400, re-run only the rows the batch results marked failed or never sent, not every batch from the top.

One honest note on metering: every batch operation counts against your monthly quota exactly like an individual call - batching saves round-trips, not quota. A typical spreadsheet CRM (500 contacts, 150 companies, 500 deals, 800 notes) is roughly 2,000 requests. Free covers 1,000 requests a month with no card; Pro is $29/mo for 100,000; Scale is $249/mo for 2,000,000.

Step 6: verify your data

Do not trust "done" - check it. Three cheap passes:

Once test mode passes all three, run the same import with the live key, spot-check again, and archive the spreadsheet.

What replaces formulas and views

The parts of Airtable you actually liked have direct equivalents, just active instead of passive. Views become queries: an agent with the API or MCP answers "who have we not touched in 30 days?" on demand, with no saved view to maintain. Formulas that computed state become automations (/v1/automations): event-triggered rules that move a deal's stage, set a field, log an activity or send an email - they act, rather than recompute. The Kanban view is now a first-class pipeline your agent moves deals through one call at a time. The machine-readable manifests for all of it live in the agent hub.

FAQ

How do I get my data out of Airtable or Google Sheets?

Airtable exports are per table: open each table in a grid view and download it as CSV. Formulas, rollups and lookups export as computed values; linked records export as names, not IDs. In Google Sheets, download each tab as CSV. Put all the files in one folder for the agent.

Will re-running the import create duplicates?

Not if you lean on the guardrails. A duplicate contact email returns a 409 conflict that includes the existing record, so re-runs resolve to the same contact. For everything else, send an Idempotency-Key header per record on the individual create endpoints - operations inside /v1/batch are not individually idempotent - and a retried write replays the original result instead of writing twice.

What happens to my Airtable formulas, rollups and views?

They export as static computed values. Keep the ones that matter as typed custom fields, then replace the logic: automations cover event-driven updates, agent queries with q= replace saved views, and a Kanban view grouped by Status becomes a real pipeline.

How many API requests does a migration use, and what does it cost?

Each batch operation is metered like an individual call. A 500-contact spreadsheet CRM with companies, deals and notes lands around 2,000 requests. Free covers 1,000 requests a month with no card; Pro is $29/mo for 100,000; Scale is $249/mo for 2,000,000. Test-mode rehearsals are free and never counted.

Can I rehearse the migration without touching live data?

Yes - run the whole thing first with a relm_test_ key. Test mode is free, unlimited and completely isolated from live data and billing. When it checks out, run the identical pass with the live key.

What about rows with no email address?

They import fine. Contacts are email-optional: name-only, phone-only, LinkedIn-only or company-only contacts are valid; only a completely empty one is rejected. Migrate everyone now, enrich later.

Retire the spreadsheet

Mint a free test key, point your agent at the CSVs, and rehearse the whole migration today. See pricing.

Start free →