← Learn

Migrate from GoHighLevel to Relm in one agent run

Export your contacts and opportunities from GoHighLevel, hand the files to your AI agent along with a Relm API key, and the agent does the rest: recreates your pipeline, registers your custom fields, and batch-loads every record with its history intact. No manual re-entry, no mapping spreadsheet you fill in by hand.

Why move the CRM brain out of GoHighLevel

GoHighLevel is an agency all-in-one, and it is genuinely good at that job: funnels, websites, calendars, workflows, two-way SMS and email, white-label sub-accounts. The CRM inside it is designed for a human operator working those tools from a dashboard, and its API reflects that heritage - it exists and it works, but it assumes an integration developer, not a language model. Custom fields and pipeline stages are referenced by opaque internal IDs the caller has to resolve first, tokens are scoped per location, and errors report what went wrong without telling the model what would have been right.

None of that is a flaw when a person runs the CRM. It becomes friction the day your primary CRM operator is an AI agent - an SDR bot logging calls, an enrichment agent updating records, a support copilot looking up history. Relm inverts the design: the REST API and the MCP server are the product. The schema is self-describing (GET /v1/schema), IDs are prefixed and human-readable (con_, cmp_, deal_, act_), and every error is structured problem+JSON - with valid_options when a value is wrong - so the agent fixes its own call. The practical move for most teams is a split, not a switch: keep GoHighLevel for funnels, booking and campaigns, and move the structured record layer - the CRM brain - to Relm where agents can operate it directly. The full case for that architecture is in What is a CRM for LLMs?

What to export from GoHighLevel

You need two files per sub-account, and GoHighLevel gives you both without any paid add-on:

Notes, tasks and conversation history are not part of those CSVs. If you want them migrated too, pull them through GoHighLevel's v2 API (a Private Integration token on the sub-account is enough for read access), or have your agent do that pull as part of the run. If your account uses GoHighLevel's Businesses records, export or fetch those as well - they become Relm companies. Remember the scoping rule: one GoHighLevel sub-account equals one Relm workspace with its own API key, so agencies repeat the run per client and data never crosses.

How GoHighLevel maps to Relm

GoHighLevelRelmHow
Sub-account (location)WorkspaceOne workspace + one API key per sub-account. Strict isolation, same as locations.
Contact: First Name, Last Name, Email, Phonecontact: first_name, last_name, email, phoneDirect. Email is optional in Relm, so phone-only leads import cleanly.
Contact: Business Name / Businesses recordcompany: nameCreate the company first, link the contact via company_id.
Contact: Tagscustom_fields.tagsRegister a tags field once via POST /v1/fields, then send the joined tag list.
Contact: Source + custom fieldscustom_fields.*One POST /v1/fields per field (typed: text, number, date, ...), then send values under custom_fields.
Contact ID (for traceability)custom_fields.ghl_contact_idKeep the source ID so you can cross-check both systems later.
Opportunity: Name, Lead Valuedeal: title, value_centsName is direct. Lead Value is dollars and value_cents is integer cents - multiply by 100.
Pipeline + Stagedeal: pipeline + stageRecreate the pipeline 1:1 via POST /v1/pipelines with your exact stage keys.
Opportunity status (won / lost / abandoned)Terminal stagesAdd won and lost stages; map abandoned to lost or keep it in a custom field.
Notes, tasks, call logsactivityType note / call / email / meeting, with occurred_at set to the original date.

The agent run, step by step

Start in test mode. Mint a relm_test_ key in the dashboard - test data is free, unlimited, and invisible to live billing and the live dashboard. Rehearse the full migration there, check the result, then repeat with the relm_live_ key. Nothing about the calls changes except the key.

One-time setup - register the fields you are keeping and recreate your pipeline, stage for stage:

# register the GoHighLevel fields you want to keep
curl -X POST https://api.relmcrm.com/v1/fields \
  -H "Authorization: Bearer relm_test_..." -H "Content-Type: application/json" \
  -d '{"object":"contact","key":"ghl_contact_id","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_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":"tags","data_type":"text"}'

# recreate your GoHighLevel pipeline, stage for stage
curl -X POST https://api.relmcrm.com/v1/pipelines \
  -H "Authorization: Bearer relm_test_..." -H "Content-Type: application/json" \
  -d '{"name":"Sales Pipeline","stages":["new-lead","contacted","appointment-booked","proposal-sent","won","lost"]}'

Load in dependency order. Records get server-assigned IDs, so create companies first, then contacts (referencing company IDs), then deals (referencing contact IDs), then activities. Within a single batch, operations cannot reference each other's new IDs - so each object type is its own pass. POST /v1/batch takes up to 100 operations per call and returns a per-operation result array: one bad row does not fail the batch, and every operation counts against your quota exactly like an individual call. A contacts pass looks like this, with the GoHighLevel columns mapped straight in:

curl -X POST https://api.relmcrm.com/v1/batch \
  -H "Authorization: Bearer relm_test_..." -H "Content-Type: application/json" \
  -d '{"operations":[
    {"object":"contact","data":{"first_name":"Maria","last_name":"Santos",
      "email":"[email protected]","phone":"+15125550143","type":"lead",
      "custom_fields":{"ghl_contact_id":"hnB2cd4pQzWkQvKe0Ge6",
        "lead_source":"Facebook Ad","tags":"new-patient, fb-campaign-may"}}},
    {"object":"contact","data":{"first_name":"Devon","phone":"+15125550177","type":"lead",
      "custom_fields":{"ghl_contact_id":"aQ7fLp0XeR2mNv8sT1uW",
        "lead_source":"Google LSA","tags":"missed-call-textback"}}}
  ]}'

Note the second contact has no email - that is fine in Relm, so nobody gets dropped. Deals follow the same pattern in the next pass (title, value_cents - integer cents, so a $1,500 Lead Value is 150000 - pipeline, stage, primary_contact_id), then activities with occurred_at preserving each note's original date. Duplicate emails come back as a 409 that includes the existing record, so a re-run never duplicates a contact that has an email - and because the batch response reports every row's result individually, a partial failure means re-running just the failed rows, not the whole file.

Or skip the curl entirely. Point any MCP client - Claude, Cursor, anything that speaks the protocol - at https://api.relmcrm.com/mcp with your key, give it the export files, and say:

Migrate my GoHighLevel export (contacts.csv + opportunities.csv) into Relm.
Call relm_describe_schema first. Create a pipeline whose stages match my
GoHighLevel pipeline exactly, register custom fields for tags, lead_source
and ghl_contact_id, then load everything with relm_batch, 100 operations
at a time, in dependency order: companies, contacts, deals, activities.
Set occurred_at on notes to their original dates. Contacts without an
email are fine - import everyone, skip nothing.

The agent discovers the whole contract from the schema, and it self-corrects as it goes: an unknown stage or field comes back as a structured 422 with valid_options and the exact call needed to create it. No guessing, no babysitting. The broader setup is covered in how to give your AI agent a CRM and the agent hub.

Verify your data before you switch

Trust, then verify - with the same API:

Because the rehearsal ran in test mode, you get to do this whole check before a single live record exists - and the live run is the identical script with a different key.

What stays in GoHighLevel

Relm is deliberately not an all-in-one. Your funnels, booking calendars, SMS and email campaigns, and workflow automations keep running in GoHighLevel exactly as before - Relm does not replace them and does not try. What moves is the system of record your agents read and write: contacts, companies, deals in pipelines, and the activity history. If you later want the two connected, Relm's signed webhooks can notify your GoHighLevel workflows when a deal changes stage.

FAQ

Can my AI agent really migrate GoHighLevel to Relm on its own?

Yes - that is the designed path. The agent reads GET /v1/schema to learn the contract, recreates your pipeline, registers custom fields, then loads records with POST /v1/batch. Wrong stage or field? The 422 response includes valid_options, so it self-corrects. Run it on a relm_test_ key first.

Do I have to stop using GoHighLevel?

No. Keep it for funnels, calendars, SMS and email campaigns. Move only the structured record layer to Relm so your agents can operate it directly. The two coexist fine.

How do GoHighLevel sub-accounts map to Relm?

One sub-account (location) maps to one Relm workspace with its own API key. Data never crosses workspaces, preserving the per-client isolation agencies rely on. Repeat the run per sub-account.

What happens to duplicate contacts during the import?

A contact whose email already exists returns a 409 conflict that includes the existing record, so a re-run never duplicates a contact that has an email. The batch response reports each row's result individually, so if some rows fail you re-run just those - and the ghl_contact_id custom field lets you check what already made it across.

Will my GoHighLevel notes keep their original dates?

Yes. Create them as activities with occurred_at set to the original timestamp. It only defaults to the import time if you omit it.

How much does the migration cost in API requests?

Each batched record counts as one operation, so a 5,000-record sub-account is roughly 5,000 requests plus setup. 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 - see pricing.

Point your agent at Relm

Mint a free key, rehearse the import in test mode, go live when the counts match.

Start free →