The order
Load records before the things that point at them, so every link resolves on first pass.1
Members
Invite the people who own records in the old CRM. Owners are matched by email (
owner_email); an owner who is not a member leaves the record unowned, and a note or task keeps the author’s name as text.2
Structure
Pipelines and stages with
POST /api/v1/pipelines (stage kind decides open, won or lost, with a probability), custom fields with POST /api/v1/crm/fields. GET /api/v1/crm/schema shows what exists. The imports can also create text fields on the fly with create_missing_fields: true.3
Companies
POST /api/v1/companies/import, 1,000 rows per call, with domain and the source id as external_id.4
Contacts
POST /api/v1/contacts/import, 1,000 rows per call. company (the name) links each contact to the company imported above; created_at keeps the original creation date.5
Deals
POST /api/v1/deals/import, 1,000 rows per call, naming the pipeline and stage by name, the company by company_external_id and the contacts by contact_external_ids. A closed deal keeps its closed_at.6
History
POST /api/v1/activities/import, 500 rows per call: notes, emails, calls, meetings and tasks with their original occurred_at, attached by contact_external_id, company_external_id and deal_external_id.7
Do-not-contact
POST /api/v1/sequences/unsubscribes with up to 1,000 emails per call for everyone who opted out or bounced in the old system, so nobody is emailed by mistake.Ids and dates
Sendexternal_source once per call (for example "hubspot") and each row’s external_id. The id is stored with the record, and from then on any call can name the record by it: company_external_id on a deal, deal_external_id on an activity, contact_external_id anywhere a contact is linked. A row whose id is already known updates the record (companies also match on domain), so a migration can stop, resume and re-run safely; duplicate_mode: "skip" keeps what is already there.
created_at on contacts, companies, deals, notes and tasks, occurred_at on activities, completed_at on tasks, and closed_at and stage_entered_at on deals keep the original timestamps, so timelines and reports read the way they did before. stage_entered_at is when a new deal entered its current stage: time in stage and the deal’s stage history start there (without it, at closed_at for a closed deal, else created_at). A row that matches a record Orbit already had (a form lead, a company created from an email domain) moves its created_at back to the row’s when the row’s is earlier, never forward.
errors it left out, warnings for rows that imported with something missing (a contact that was never imported, say), and records: the Orbit id of each row.
Limits
A
429 carries Retry-After in seconds. An approved OAuth app has higher request limits (see rate limits).
Webhooks, apps and workflows during a migration
Nothing you import fires anything by default:- Workflows never start from an import unless the person importing picks one in the import dialog (“Contact Added” trigger). The API’s bulk endpoints and CRM migrations start none.
- Webhooks and connected apps do not receive the records an import writes. The workspace’s owner can turn that on under Settings → Integrations → Connected apps → Send imported records to integrations; it is an explicit, recorded acceptance, applies to every integration in the workspace, and only covers imports that run after it. Deliveries for imported records then carry
"source": "import"so a receiver can tell them apart. See Imports.
What does not carry over
Form definitions, email templates, sequences and workflows are built in Orbit rather than imported; files can be attached withPOST /api/v1/files/import from a signed URL in the old system. Migrating from HubSpot maps every HubSpot object and engagement field to its Orbit counterpart.