CRM Syncs
Push graph8 records to a connected CRM. Five providers are supported:
| Provider | URL |
|---|---|
hubspot | hubspot.com |
salesforce | salesforce.com |
pipedrive | pipedrive.com |
zoho | zoho.com (CRM) |
sugarcrm | sugarcrm.com |
You must connect the CRM in Settings -> Integrations first - the {provider} path parameter resolves to the existing Nango connection for the org. If no connection exists you’ll get 404 Not Found.
| Endpoint | Method | Description |
|---|---|---|
/crm-syncs | GET | List configured CRM integrations |
/crm-syncs/{provider}/contacts/push | POST | Push contacts |
/crm-syncs/{provider}/companies/push | POST | Push companies |
/crm-syncs/{provider}/lists/push | POST | Modify list memberships (add/remove) |
/crm-syncs/{provider}/fields | GET | Discover available fields for an entity type |
/crm-syncs/{provider}/status | GET | Check connection health |
For machine-readable schemas see the interactive API docs.
List CRM Syncs
GET /crm-syncs
Returns connected CRM providers for the org. Disconnected providers are not returned.
Example
curl "https://be.graph8.com/api/v1/crm-syncs" \ -H "Authorization: Bearer $API_KEY"Response
{ "data": [ { "provider": "hubspot", "status": "connected", "connection_id": "nango-conn-abc123", "last_sync_at": null, "connected_at": null } ]}Push Contacts
POST /crm-syncs/{provider}/contacts/push
Bulk-create contacts on the CRM. The endpoint iterates each record and reports per-record errors in the response - one bad record does not fail the whole batch.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
records | object[] | Yes | Contact payloads in the CRM’s native field format (e.g. email, firstname, lastname for HubSpot) |
provider_fields | object | No | Optional field mapping overrides |
Example
curl -X POST "https://be.graph8.com/api/v1/crm-syncs/hubspot/contacts/push" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "records": [ {"email": "jane@acme.com", "firstname": "Jane", "lastname": "Smith"}, {"email": "bob@acme.com", "firstname": "Bob", "lastname": "Jones"} ] }'Response
{ "data": { "pushed_count": 2, "failed_count": 0, "errors": [] }}When a record fails, errors[] contains { "record": "<email>", "error": "<message>" }.
Errors
| Status | Meaning |
|---|---|
404 | No active {provider} connection. Connect in Integrations first |
Push Companies
POST /crm-syncs/{provider}/companies/push
Same shape as contact push. Each record is a company payload (e.g. name, domain, industry for HubSpot).
Example
curl -X POST "https://be.graph8.com/api/v1/crm-syncs/salesforce/companies/push" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "records": [ {"Name": "Acme Inc", "Website": "acme.com", "Industry": "Technology"} ] }'Push List Memberships
POST /crm-syncs/{provider}/lists/push
Add or remove contacts from CRM lists. Each records[] entry targets one list.
Request Body
Each item in records[] should look like:
| Field | Type | Description |
|---|---|---|
list_id | string | CRM list identifier |
add_contact_ids | string[] | CRM contact IDs to add |
remove_contact_ids | string[] | CRM contact IDs to remove |
Example
curl -X POST "https://be.graph8.com/api/v1/crm-syncs/hubspot/lists/push" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "records": [{ "list_id": "42", "add_contact_ids": ["101", "102"], "remove_contact_ids": ["999"] }] }'Response
{ "data": { "added": 2, "removed": 1, "errors": [] }}Discover Fields
GET /crm-syncs/{provider}/fields
Return the schema (custom + standard fields) for an entity type on the CRM. Useful before a push so you can validate field names dynamically.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
entity_type | string | contacts | One of contacts, companies, leads, deals |
Example
curl "https://be.graph8.com/api/v1/crm-syncs/hubspot/fields?entity_type=contacts" \ -H "Authorization: Bearer $API_KEY"Response
{ "data": { "fields": [ { "name": "First Name", "slug": "firstname", "type": "string", "required": false, "label": "First Name" } ] }}Errors
| Status | Meaning |
|---|---|
502 | The CRM rejected the discovery request - check message for the upstream error |
Get Status
GET /crm-syncs/{provider}/status
Health-check the connection by issuing a small read. Returns connected: false (with a message) if the connection is broken or the OAuth token expired - the endpoint never raises 5xx for connection failures.
Example
curl "https://be.graph8.com/api/v1/crm-syncs/hubspot/status" \ -H "Authorization: Bearer $API_KEY"Response
{ "data": { "provider": "hubspot", "connected": true, "connection_id": "nango-conn-abc123", "crm_type": "hubspot", "message": "Connection is healthy", "last_sync_at": null }}Field Mapping Reference
Every provider has a canonical map from graph8 fields to the provider’s native field names. Use GET /crm-syncs/{provider}/fields to introspect at runtime; the tables below are the maintained reference.
Upsert keys (per provider)
| Provider | Contact upsert key | Company upsert key |
|---|---|---|
| HubSpot | email | domain |
| Salesforce | Email | Website (used as domain) |
| Pipedrive | email + name | name (no domain field) |
| Zoho | Email | Website |
| SugarCRM | email | website |
Contact field map (canonical → provider)
| graph8 | HubSpot | Salesforce | Pipedrive | Zoho | SugarCRM |
|---|---|---|---|---|---|
work_email | email | Email | email[] | Email | email1 |
first_name | firstname | FirstName | name (derived) | First_Name | first_name |
last_name | lastname | LastName (default -) | name (derived) | Last_Name (fallback first_name) | last_name |
job_title | jobtitle | Title | org.title | Title | title |
seniority_level | seniority | — | — | — | — |
direct_phone | phone | Phone | phone[] | Phone | phone_work |
mobile_phone | mobilephone | MobilePhone | phone[] | Mobile | phone_mobile |
linkedin_url | hs_linkedin_url | Twitter (custom) | (custom) | (custom) | linkedin_c |
city | city | MailingCity | (custom) | City | primary_address_city |
country | country | MailingCountry | (custom) | Country | primary_address_country |
source | hs_lead_source | LeadSource | (custom) | Lead_Source | lead_source |
Notes:
- Salesforce requires
LastName— graph8 defaults to"-"if missing. - Pipedrive uses array shapes for email and phone (
[{ value, primary }]). - SugarCRM custom fields are suffixed
_c(e.g.linkedin_c); HubSpot auto-discovers custom fields via the Properties API.
Company field map (canonical → provider)
| graph8 | HubSpot | Salesforce | Pipedrive | Zoho | SugarCRM |
|---|---|---|---|---|---|
name | name | Name | name | Account_Name | name |
domain | domain | Website | (custom) | Website | website |
industry | industry | Industry | (custom) | Industry | industry |
employee_count | numberofemployees (int) | NumberOfEmployees | (custom) | Employees | employees |
revenue | annualrevenue | AnnualRevenue | (custom) | Annual_Revenue | annual_revenue |
country | country | BillingCountry | (custom) | Billing_Country | billing_address_country |
founded_year | founded_year | (custom) | (custom) | (custom) | (custom) |
Direction support
| Provider | Push (g8 → CRM) | Pull (CRM → g8) | Deals sync | Lifecycle / status owned by |
|---|---|---|---|---|
| HubSpot | ✅ full | ✅ full | ✅ | CRM (lifecyclestage, hs_lead_status overwrite g8 on pull) |
| Salesforce | ✅ contacts/companies | ⚠️ minimal (email + first/last only by default) | — | CRM |
| Pipedrive | ✅ contacts/companies (push-only) | — | ⚠️ partial | CRM |
| Zoho | ✅ contacts/companies | ✅ partial | ⚠️ partial | CRM |
| SugarCRM | ✅ full | ✅ full | ✅ | CRM (SugarHint enrichment fields pulled inbound) |
Custom field handling
- HubSpot: auto-discovers all custom properties via the Properties API. Pass them as bare property names in
records[].properties. - Salesforce: pass-through. Custom fields use
__csuffix natively. - Pipedrive: custom fields are prefixed
org.for company customs,person.for contact customs. - Zoho: pass-through. Pre-sanitized (integers and currency parsed before push).
- SugarCRM: custom fields use
_csuffix (e.g.lead_score_c).
Type coercion + quirks
| Provider | Quirk |
|---|---|
| All | email, phone, domain are always strings. ZIP / postal codes are also strings (Salesforce + Zoho will 400 on integers). |
| Salesforce | Requires LastName — graph8 defaults to "-". |
| Zoho | Requires Last_Name — graph8 falls back to First_Name if missing. |
| Pipedrive | Employee count ranges (50-200) are converted to a midpoint integer. |
| HubSpot | numberofemployees is integer, NOT the bucket string used elsewhere. |
| HubSpot ambiguous fields | phone, address, city, state, country, zip, mobilephone are personal contact fields. Don’t include them on contact-→-company migration mappings (graph8 auto-filters). |
Rate limits + quirks
- HubSpot: ~10 rps per integration; bulk endpoints accept 100 records per request.
- Salesforce: API request limits depend on the org’s edition (typically 15k-1M / 24h). The connector uses bulk where available.
- Pipedrive: ~10 rps; no bulk upsert — graph8 paginates internally.
- Zoho: ~10 rps; require token refresh every hour.
- SugarCRM: instance-specific; check with your SugarCRM admin.
GET /crm-syncs/{provider}/status exposes the current rate-limit budget and last-sync timestamp.