Skip to main content
Orbit’s API is built for a migration of any size: every object has a bulk import that takes up to 1,000 rows per call, rows carry the ids and dates they had in the source system, and a second run updates instead of duplicating. A 50,000-contact HubSpot portal with its companies, deals and history is a few hundred calls and well under an hour.
Coming from HubSpot? Orbit can pull everything itself from Settings → Integrations → HubSpot → Import from HubSpot, with no API work at all. This page is for scripts and agents that drive the move through the API.

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

Send external_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 and closed_at on deals keep the original timestamps, so timelines and reports read the way they did before.
Every import answers with counts, the row-level 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).

What does not carry over

Form definitions, email templates, sequences and workflows are built in Orbit rather than imported; files can be attached with POST /api/v1/files/import from a signed URL in the old system. Migrate from HubSpot with an agent maps every HubSpot object and engagement field to its Orbit counterpart.