Skip to content
Syncline

HubSpot API reference

<p>This page covers the Syncline endpoints for the HubSpot connector. They are Syncline routes on the Syncline API, authenticated with a Syncline key. They are not a restatement of HubSpot's CRM API and the paths below do not exist on HubSpot's side: read the vendor's own developer documentation when you need those.</p><p>One connection maps to one HubSpot portal, which is why a sandbox and a production portal can never be confused with each other here. Contact writes match on email by default, so a repeated call updates the existing record instead of creating a second one, and the response always names which record it hit.</p>
Base URL: https://api.syncline.topclb.com

Authentication

Send a Syncline secret key as a bearer token: Authorization: Bearer sk_live_... . The connection_id parameter selects the portal. Write routes require a key created with write scope, and the connector itself only holds the HubSpot scopes you approved during install: contacts, deals and forms unless you narrowed it further.

Endpoints

<article class="sy-endpoint"><h3><code>POST /v1/connectors/hubspot/contacts</code></h3><p>Create a contact, or update the existing one that matches the email.</p><div class="sy-table-wrap"><table><thead><tr><th>Param</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>connection_id</td><td>string</td><td>yes</td><td>The authorised HubSpot portal to write into.</td></tr><tr><td>email</td><td>string</td><td>yes</td><td>The match key. Trimmed and lowercased before comparison, so casing never creates a duplicate.</td></tr><tr><td>properties</td><td>object</td><td>yes</td><td>Internal property names to values, including any custom properties defined in your portal.</td></tr><tr><td>on_conflict</td><td>string</td><td>no</td><td>One of update or skip. Defaults to update, which is what deduplication wants.</td></tr><tr><td>associate_company</td><td>string</td><td>no</td><td>A company domain or ID to link the contact to. Created when create_company is true.</td></tr></tbody></table></div><pre><code>{ &quot;run_id&quot;: &quot;run_b4862049&quot;, &quot;connector&quot;: &quot;hubspot&quot;, &quot;status&quot;: &quot;ok&quot;, &quot;data&quot;: { &quot;contact_id&quot;: &quot;701842933&quot;, &quot;action&quot;: &quot;updated&quot;, &quot;email&quot;: &quot;lena@harbourline.example&quot;, &quot;properties_written&quot;: [&quot;firstname&quot;, &quot;lifecyclestage&quot;], &quot;portal_id&quot;: &quot;48120037&quot; } }</code></pre></article><article class="sy-endpoint"><h3><code>GET /v1/connectors/hubspot/contacts</code></h3><p>Look up contacts by email or by any indexed property value.</p><div class="sy-table-wrap"><table><thead><tr><th>Param</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>connection_id</td><td>string</td><td>yes</td><td>The authorised HubSpot portal to search.</td></tr><tr><td>email</td><td>string</td><td>no</td><td>Exact email match. Fastest lookup and the one most flows use.</td></tr><tr><td>property</td><td>string</td><td>no</td><td>An alternative property to match on when you have no email.</td></tr><tr><td>value</td><td>string</td><td>no</td><td>The value to match against the named property.</td></tr><tr><td>limit</td><td>integer</td><td>no</td><td>Results per page, 1 to 100. Defaults to 25.</td></tr></tbody></table></div><pre><code>{ &quot;run_id&quot;: &quot;run_c5973150&quot;, &quot;connector&quot;: &quot;hubspot&quot;, &quot;status&quot;: &quot;ok&quot;, &quot;data&quot;: [ { &quot;contact_id&quot;: &quot;701842933&quot;, &quot;email&quot;: &quot;lena@harbourline.example&quot;, &quot;firstname&quot;: &quot;Lena&quot;, &quot;lifecyclestage&quot;: &quot;customer&quot;, &quot;owner_email&quot;: &quot;marcus@syncline.topclb.com&quot; } ], &quot;next_cursor&quot;: null }</code></pre></article><article class="sy-endpoint"><h3><code>POST /v1/connectors/hubspot/deals</code></h3><p>Open a deal in a named pipeline and stage, linked to a contact.</p><div class="sy-table-wrap"><table><thead><tr><th>Param</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>connection_id</td><td>string</td><td>yes</td><td>The authorised HubSpot portal to write into.</td></tr><tr><td>dealname</td><td>string</td><td>yes</td><td>The deal title as it will read in the pipeline view.</td></tr><tr><td>pipeline</td><td>string</td><td>yes</td><td>Pipeline name or ID. Refused with 404 when the pipeline does not exist in this portal.</td></tr><tr><td>dealstage</td><td>string</td><td>yes</td><td>Stage name or ID inside that pipeline.</td></tr><tr><td>amount</td><td>number</td><td>no</td><td>Deal value as a decimal. Convert from minor units before sending if the source is a payments app.</td></tr><tr><td>contact_id</td><td>string</td><td>no</td><td>Contact to associate the deal with, usually the ID returned by the contacts route.</td></tr></tbody></table></div><pre><code>{ &quot;run_id&quot;: &quot;run_d6a84261&quot;, &quot;connector&quot;: &quot;hubspot&quot;, &quot;status&quot;: &quot;ok&quot;, &quot;data&quot;: { &quot;deal_id&quot;: &quot;20933471&quot;, &quot;dealname&quot;: &quot;Harbour Line Coffee, annual&quot;, &quot;pipeline&quot;: &quot;default&quot;, &quot;dealstage&quot;: &quot;contractsent&quot;, &quot;amount&quot;: 4900.00, &quot;associated_contacts&quot;: [&quot;701842933&quot;] } }</code></pre></article><article class="sy-endpoint"><h3><code>PATCH /v1/connectors/hubspot/deals/{deal_id}</code></h3><p>Move a deal forward, or write properties when the signal came from elsewhere.</p><div class="sy-table-wrap"><table><thead><tr><th>Param</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>connection_id</td><td>string</td><td>yes</td><td>The authorised HubSpot portal holding the deal.</td></tr><tr><td>dealstage</td><td>string</td><td>no</td><td>The stage to move to. Must exist in the deal's current pipeline.</td></tr><tr><td>properties</td><td>object</td><td>no</td><td>Any other deal properties to write. Only the keys you send are touched.</td></tr><tr><td>note</td><td>string</td><td>no</td><td>Text logged on the deal timeline alongside the change, so the history explains itself.</td></tr></tbody></table></div><pre><code>{ &quot;run_id&quot;: &quot;run_e7b95372&quot;, &quot;connector&quot;: &quot;hubspot&quot;, &quot;status&quot;: &quot;ok&quot;, &quot;data&quot;: { &quot;deal_id&quot;: &quot;20933471&quot;, &quot;dealstage_from&quot;: &quot;contractsent&quot;, &quot;dealstage_to&quot;: &quot;closedwon&quot;, &quot;note_logged&quot;: true, &quot;updated_at&quot;: &quot;2026-09-07T11:06:22Z&quot; } }</code></pre></article><article class="sy-endpoint"><h3><code>GET /v1/connectors/hubspot/properties</code></h3><p>List the properties available on an object type, including custom ones.</p><div class="sy-table-wrap"><table><thead><tr><th>Param</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>connection_id</td><td>string</td><td>yes</td><td>The authorised HubSpot portal to inspect.</td></tr><tr><td>object_type</td><td>string</td><td>yes</td><td>One of contacts, companies or deals.</td></tr><tr><td>custom_only</td><td>boolean</td><td>no</td><td>Return only properties your portal defined, hiding the standard set. Defaults to false.</td></tr></tbody></table></div><pre><code>{ &quot;run_id&quot;: &quot;run_f8ca6483&quot;, &quot;connector&quot;: &quot;hubspot&quot;, &quot;status&quot;: &quot;ok&quot;, &quot;data&quot;: [ { &quot;name&quot;: &quot;lifecyclestage&quot;, &quot;label&quot;: &quot;Lifecycle stage&quot;, &quot;type&quot;: &quot;enumeration&quot;, &quot;options&quot;: [&quot;subscriber&quot;, &quot;lead&quot;, &quot;opportunity&quot;, &quot;customer&quot;], &quot;custom&quot;: false } ] }</code></pre></article>

Errors

<div class="sy-table-wrap"><table><thead><tr><th>Code</th><th>Meaning</th><th>What to do</th></tr></thead><tbody><tr><td>401 connection_revoked</td><td>The Syncline app was uninstalled from the portal, or the install was revoked by an admin.</td><td>Reinstall from Connections and pick the same portal. Deals and contacts are untouched by the reconnect.</td></tr><tr><td>403 scope_missing</td><td>The install does not carry the HubSpot scope this call needs, for example deals when only contacts was approved.</td><td>Reauthorise the portal and approve the scope during install. Nothing else in the request needs changing.</td></tr><tr><td>404 pipeline_not_found</td><td>The named pipeline or stage does not exist in this portal, often after a rename.</td><td>Call the properties route or read the pipeline list in HubSpot, then send the current name or ID.</td></tr><tr><td>409 duplicate_contact</td><td>Two records already share the email, so the connector will not guess which one to update.</td><td>Merge the duplicates in HubSpot, then retry. Setting on_conflict to skip lets the run pass while you sort it out.</td></tr><tr><td>429 upstream_rate_limited</td><td>The portal hit its ten second request budget, usually during an import.</td><td>Batch the writes or lower the backfill rate. The retry_after value in the body tells you how long to wait.</td></tr></tbody></table></div>

Rate limits

Syncline allows 120 requests per minute per API key. HubSpot allows roughly 100 requests every ten seconds per portal, which is the tighter of the two during an import, so the connector groups writes and spreads them across the window. Every response carries X-Syncline-RateLimit-Remaining, and a queued call returns 202 rather than an error.

Code samples

<h4>curl</h4><pre><code>curl -X POST https://api.syncline.topclb.com/v1/connectors/hubspot/contacts \ -H &quot;Authorization: Bearer $SYNCLINE_KEY&quot; \ -H &quot;Content-Type: application/json&quot; \ -d '{ &quot;connection_id&quot;: &quot;con_hubspot_9k1&quot;, &quot;email&quot;: &quot;lena@harbourline.example&quot;, &quot;properties&quot;: { &quot;firstname&quot;: &quot;Lena&quot;, &quot;lifecyclestage&quot;: &quot;customer&quot; } }'</code></pre><h4>node</h4><pre><code>const res = await fetch( &quot;https://api.syncline.topclb.com/v1/connectors/hubspot/contacts&quot;, { method: &quot;POST&quot;, headers: { Authorization: `Bearer ${process.env.SYNCLINE_KEY}`, &quot;Content-Type&quot;: &quot;application/json&quot; }, body: JSON.stringify({ connection_id: &quot;con_hubspot_9k1&quot;, email: &quot;lena@harbourline.example&quot;, properties: { firstname: &quot;Lena&quot;, lifecyclestage: &quot;customer&quot; } }) } ); const { data } = await res.json(); console.log(data.action, data.contact_id);</code></pre><h4>python</h4><pre><code>import os import requests resp = requests.post( &quot;https://api.syncline.topclb.com/v1/connectors/hubspot/contacts&quot;, headers={&quot;Authorization&quot;: f&quot;Bearer {os.environ['SYNCLINE_KEY']}&quot;}, json={ &quot;connection_id&quot;: &quot;con_hubspot_9k1&quot;, &quot;email&quot;: &quot;lena@harbourline.example&quot;, &quot;properties&quot;: {&quot;firstname&quot;: &quot;Lena&quot;, &quot;lifecyclestage&quot;: &quot;customer&quot;}, }, timeout=30, ) resp.raise_for_status() print(resp.json()[&quot;data&quot;][&quot;action&quot;])</code></pre>