Skip to content
Docs menu

Migrating from Klara

Klara doesn't expose a public REST API, so a migration is mostly a one-time CSV export plus a bulk import script.

1. Export your data

In Klara, head to Settings → Data export and grab CSVs for: contacts, conversations, appointments, and forms. Klara's exports are well-formed but use Klara's own column names — we map them in step 3.

2. Stand up a service account

Create one in Settings → Service accounts with role ADMIN(you'll downgrade once the migration is done). Save the token as HEARTHNOTE_TOKEN in your shell.

3. Map the columns

Klara columnContactFollowUp field
First NamefirstName
Last NamelastName
Email Addressemail
Cell Phonephone
DOBdob (ISO 8601)
Statuslifecycle (PATIENT for active)
Insurance Planinsurance.payerName (separate POST)
Member IDinsurance.memberId
Notesnotes

4. Bulk import

Loop the CSV with whatever tool you like and POST to /api/contacts. We'd recommend a 100-record batch with 250 ms of jitter between batches, to be a polite client.

bulk_import.py
import csv, os, time, requests

TOKEN = os.environ["HEARTHNOTE_TOKEN"]
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}

with open("klara_contacts.csv", newline="") as f:
    reader = csv.DictReader(f)
    for row in reader:
        body = {
            "firstName": row["First Name"],
            "lastName":  row["Last Name"],
            "email":     row["Email Address"] or None,
            "phone":     row["Cell Phone"] or None,
            "lifecycle": "PATIENT" if row["Status"] == "Active" else "INACTIVE",
            "notes":     row.get("Notes") or None,
        }
        r = requests.post("https://app.contactfollowup.com/api/contacts", json=body, headers=HEADERS, timeout=30)
        if not r.ok:
            print("FAIL", row["Email Address"], r.status_code, r.text)
        time.sleep(0.25)

5. Forward inbound messages during the cutover

Klara's web inbox can forward each conversation as email. Point that forward at the ContactFollowUp inbox webhook (POST /api/inbox/webhook) and set the shared-secret header. See /docs/webhooks for the auth contract.

6. Downgrade your migration account

Once you've verified counts and spot-checked records, drop the migration service account's role to READONLY (or revoke the token outright). The principle is the same as any rotation: the least-privileged token wins.