All Posts

How to Connect Your CRM to AI Tools via REST API

Your CRM holds the richest context about your prospects and customers: deal stages, engagement history, company details, custom fields painstakingly maintained by your reps. Meanwhile, your AI tools need exactly that context to generate anything worth sending.

How to Connect Your CRM to AI Tools via REST API

Published on
February 26, 2026

Overview

Your CRM holds the richest context about your prospects and customers: deal stages, engagement history, company details, custom fields painstakingly maintained by your reps. Meanwhile, your AI tools need exactly that context to generate anything worth sending. The gap between those two systems is a REST API call, but bridging it reliably is where most GTM engineering projects stall.

This guide walks through the practical mechanics of connecting CRMs like HubSpot and Salesforce to AI tools via their REST APIs. We cover authentication patterns, data model differences, building read and write operations, handling rate limits, and the error handling patterns that separate a weekend prototype from a production pipeline. If you're building workflows that coordinate CRM, enrichment, and sequencer tools, this is the plumbing layer underneath all of it.

CRM API Authentication Patterns

Before you can read or write anything, you need to authenticate. HubSpot and Salesforce take meaningfully different approaches, and the pattern you choose affects how your integration handles token refreshes, permission scoping, and multi-user deployments.

HubSpot: Private Apps and OAuth

HubSpot offers two primary authentication mechanisms. For internal integrations where you're connecting your own CRM instance to AI tools, Private App tokens are the fastest path. You create a Private App in your HubSpot settings, select the scopes you need (like crm.objects.contacts.read and crm.objects.contacts.write), and get a bearer token.

GET https://api.hubapi.com/crm/v3/objects/contacts
Authorization: Bearer pat-na1-xxxxxxxx
Content-Type: application/json

Private App tokens are long-lived, which simplifies your integration code but means you need to store them securely. For production deployments, store tokens in a secrets manager, not in environment variables baked into container images.

If you're building a product that connects to multiple HubSpot instances (less common for GTM engineering, more common for tool builders), you'll need OAuth 2.0 with a full authorization code flow. The key difference: OAuth tokens expire every 30 minutes and require a refresh token rotation.

Salesforce: Connected Apps and the OAuth Dance

Salesforce authentication is more involved. You create a Connected App in Setup, which gives you a Consumer Key and Consumer Secret. From there, the most common pattern for server-to-server integrations is the JWT Bearer Flow, which eliminates the need for interactive login.

POST https://login.salesforce.com/services/oauth2/token
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=eyJhbGciOiJSUzI1NiJ9...

For simpler setups (especially during development), the Username-Password Flow works but requires appending a security token to the password. It's convenient for prototyping but has security implications in production.

The critical detail with Salesforce: your access token response includes an instance_url field. All subsequent API calls must go to that specific instance URL, not a generic endpoint. Ignoring this is a common source of "invalid session" errors that seem to appear randomly.

Authentication Tip

Build a token management layer from day one. Whether you're using HubSpot's long-lived tokens or Salesforce's expiring ones, abstract authentication into a single module that handles refresh logic, retry on 401 responses, and token storage. You'll thank yourself when your 3 AM batch job doesn't fail because a token expired mid-run.

Choosing Scopes and Permissions

Both platforms use permission scoping, and the principle of least privilege matters here. For AI-powered workflows, you'll typically need:

  • Read access to contacts, companies, and deals for building AI context
  • Write access to contacts and deals for pushing AI-generated updates back
  • Read access to custom objects if you're using them for enrichment or qualification data
  • Read access to engagement history (emails, calls, meetings) for deeper personalization context

Resist the temptation to request all scopes upfront. Narrow permissions make security reviews easier and reduce the blast radius if a token is compromised. Teams working on compliance-safe qualification workflows should be especially mindful of which data their integrations can access.

Common Data Models and Field Types

CRMs are not databases, even though they store structured data. Understanding their data models prevents a class of bugs that only surface when you try to write back AI-generated values.

HubSpot's Object Model

HubSpot organizes data into standard objects (Contacts, Companies, Deals, Tickets) and custom objects. Each object has properties, and each property has a type: string, number, date, datetime, enumeration (dropdown), or boolean.

The important nuance: HubSpot's API returns all property values as strings, regardless of their configured type. A number field comes back as "42", not 42. A date field returns as a Unix timestamp string like "1708905600000". Your parsing layer needs to handle type coercion based on the property definition, not the raw response.

// HubSpot contact response (simplified)
{
  "id": "12345",
  "properties": {
    "firstname": "Sarah",
    "lastname": "Chen",
    "company": "Acme Corp",
    "lifecyclestage": "salesqualifiedlead",
    "num_associated_deals": "3",
    "hs_lead_status": "OPEN",
    "createdate": "1708905600000"
  }
}

Salesforce's Object Model

Salesforce uses sObjects (standard objects like Lead, Contact, Account, Opportunity) with typed fields. Unlike HubSpot, Salesforce returns values in their native types through the REST API. Numbers come back as numbers, dates as ISO 8601 strings.

The complexity with Salesforce is in its relationship model. A Contact has an AccountId field that links to an Account, but querying relationships requires SOQL (Salesforce Object Query Language) with relationship traversal syntax:

SELECT Id, FirstName, LastName, Account.Name, Account.Industry
FROM Contact
WHERE Account.Industry = 'Technology'
LIMIT 100

For AI workflows that need account-level context when processing contacts, this relationship traversal is essential. Without it, you're making two API calls (one for the contact, one for the account) when one would do.

Field Types That Trip Up AI Integrations

Field TypeHubSpot BehaviorSalesforce BehaviorAI Integration Concern
Picklist/EnumerationReturns internal value (e.g., "salesqualifiedlead")Returns display valueAI needs the display value for context; map internal values to labels
Multi-selectSemicolon-separated stringSemicolon-separated stringParse into arrays before passing to AI prompts
Date/DateTimeUnix timestamp as stringISO 8601 stringNormalize to a consistent format for AI context
CurrencyString with no currency symbolNumber with currency ISO codeInclude currency context when relevant to AI output
Rich TextHTML stringHTML stringStrip HTML before using as AI context; preserve for write-back

If you've worked through field mapping between CRM, sequencer, and analytics systems, these type mismatches will feel familiar. The same principles apply: build a normalization layer that translates CRM-specific formats into a clean data structure your AI tools can consume.

Building Read Operations for AI Context

The goal of read operations is to assemble a rich context packet that gives your AI tool enough information to generate something useful. A personalized email needs different context than a qualification score, but the API patterns are similar.

Fetching Individual Records

Both HubSpot and Salesforce support fetching individual records by ID. The key optimization: request only the properties you need.

// HubSpot: Fetch specific contact properties
GET /crm/v3/objects/contacts/12345?properties=firstname,lastname,company,jobtitle,lifecyclestage,hs_lead_status,notes_last_updated

// Salesforce: Fetch specific fields
GET /services/data/v61.0/sobjects/Contact/003xx000004TMfG?fields=FirstName,LastName,Account.Name,Title,LeadSource

In HubSpot, you can also request associations in the same call to pull related objects:

GET /crm/v3/objects/contacts/12345?associations=companies,deals&properties=firstname,lastname,jobtitle

This returns the contact plus IDs of associated companies and deals. You'll still need follow-up calls to get the details of those associated objects, but at least you have the IDs without a separate associations API call.

Building Search Queries

When your AI workflow needs to process a batch of records (for example, all contacts at a target account, or all deals in a specific stage), you need search or query endpoints.

HubSpot's search API uses a JSON filter syntax:

POST /crm/v3/objects/contacts/search
{
  "filterGroups": [{
    "filters": [{
      "propertyName": "lifecyclestage",
      "operator": "EQ",
      "value": "salesqualifiedlead"
    }, {
      "propertyName": "hs_lead_status",
      "operator": "EQ",
      "value": "OPEN"
    }]
  }],
  "properties": ["firstname", "lastname", "company", "jobtitle"],
  "limit": 100,
  "after": 0
}

Salesforce uses SOQL, which is more powerful but requires you to construct query strings:

GET /services/data/v61.0/query?q=SELECT+Id,FirstName,LastName,Title,Account.Name+FROM+Contact+WHERE+Account.Id='001xx000003GY3g'+AND+LeadSource='Web'

Assembling Context Packets for AI

Raw CRM data is not good AI context. You need to transform it into a structure that gives the AI model the right information in a consumable format. A typical context assembly function might:

  1. Fetch the target contact with key properties
  2. Fetch the associated company with firmographic data
  3. Pull recent engagement history (last 5 emails, last call notes)
  4. Retrieve the current deal stage and amount if an opportunity exists
  5. Assemble everything into a structured context object
// Assembled context for AI consumption
{
  "person": {
    "name": "Sarah Chen",
    "title": "VP of Revenue Operations",
    "tenure": "2 years"
  },
  "company": {
    "name": "Acme Corp",
    "industry": "SaaS",
    "employee_count": 450,
    "annual_revenue": "$50M"
  },
  "relationship": {
    "lifecycle_stage": "Sales Qualified Lead",
    "lead_status": "Open",
    "last_activity": "Demo call on 2026-02-10",
    "open_deal": "$45,000 - Negotiation stage"
  },
  "recent_engagement": [
    "Opened pricing email on Feb 15",
    "Attended webinar: 'Scaling RevOps' on Feb 8",
    "Downloaded whitepaper: 'CRM Integration Guide' on Jan 30"
  ]
}

This structured context gives your AI tool what it needs to generate concept-centric personalization rather than surface-level first-line tricks. The richer and better-organized the context, the more relevant the AI output.

Write Operations for AI-Generated Updates

Reading CRM data to feed AI tools is only half the equation. The other half is writing AI-generated outputs back to the CRM so your team can actually use them. This includes qualification scores, enrichment data, personalized messaging drafts, and next-step recommendations.

Updating Existing Records

The standard pattern is a PATCH request with the properties you want to update:

// HubSpot: Update contact properties
PATCH /crm/v3/objects/contacts/12345
{
  "properties": {
    "ai_qualification_score": "85",
    "ai_qualification_rationale": "Strong ICP fit: SaaS company, 450 employees, VP-level contact in RevOps. Recent engagement signals high intent.",
    "hs_lead_status": "IN_PROGRESS"
  }
}

// Salesforce: Update contact fields
PATCH /services/data/v61.0/sobjects/Contact/003xx000004TMfG
{
  "AI_Qualification_Score__c": 85,
  "AI_Qualification_Rationale__c": "Strong ICP fit: SaaS company, 450 employees...",
  "LeadSource": "AI Qualified"
}

Note the Salesforce convention: custom fields end with __c. If you're creating custom fields to store AI outputs, you'll need to create them in Salesforce Setup first. HubSpot custom properties need to be created via the API or UI before you can write to them.

Creating New Records

Some AI workflows need to create records. For example, an enrichment pipeline that discovers new contacts at target accounts might need to create contact records in the CRM:

// HubSpot: Create a new contact
POST /crm/v3/objects/contacts
{
  "properties": {
    "firstname": "Marcus",
    "lastname": "Rivera",
    "email": "marcus@acmecorp.com",
    "jobtitle": "Director of Sales Enablement",
    "company": "Acme Corp",
    "ai_source": "Enrichment pipeline - Feb 2026"
  }
}

// Salesforce: Create a new contact
POST /services/data/v61.0/sobjects/Contact
{
  "FirstName": "Marcus",
  "LastName": "Rivera",
  "Email": "marcus@acmecorp.com",
  "Title": "Director of Sales Enablement",
  "AccountId": "001xx000003GY3g"
}

Managing Associations and Relationships

Creating a contact is one thing; associating it with the right company is another. In HubSpot, associations are managed through a dedicated API:

PUT /crm/v4/objects/contacts/12345/associations/companies/67890
[{
  "associationCategory": "HUBSPOT_DEFINED",
  "associationTypeId": 1
}]

In Salesforce, you set the AccountId field on the Contact directly. The relationship is a field value, not a separate API call.

Write-Back Best Practices

Always include a source identifier. Tag every AI-generated update with a field like ai_source or AI_Updated_By__c that records which pipeline, agent, or workflow produced the data. When something looks wrong in the CRM six months from now, you'll need to trace it back to its source. Teams syncing Clay data to CRM will recognize this pattern.

Handling Custom Fields for AI Outputs

Before your integration can write AI-generated data, the destination fields need to exist. This is a common oversight that breaks deploys. Consider creating a standard set of custom fields for AI outputs:

Field NameTypePurpose
AI Qualification ScoreNumber (0-100)ICP fit score from qualification agent
AI Qualification RationaleLong TextNatural-language explanation of the score
AI Recommended PlaybookPicklistBest messaging approach for this contact
AI Last UpdatedDateTimeTimestamp of last AI-generated update
AI Source PipelineTextWhich workflow produced this data

This field structure supports the combined scoring approaches that modern GTM teams use, giving reps both the score and the reasoning behind it.

Rate Limiting and Batch Operations

The single fastest way to break a CRM integration is to ignore rate limits. Both HubSpot and Salesforce enforce them aggressively, and getting throttled or blocked can halt your entire AI pipeline.

Understanding the Limits

PlatformRate LimitBurst LimitDaily Limit
HubSpot (Private App)200 requests/second per appN/A500,000/day (varies by plan)
Salesforce (REST)Per-user concurrent limit of 25N/A100,000+/day (varies by org edition)

These numbers look generous until you're running a qualification pipeline across 10,000 contacts. If each contact requires 3-4 API calls (fetch contact, fetch company, fetch engagement history, write back results), you're looking at 30,000-40,000 calls for a single batch run. Teams familiar with managing Clay rate limits know this math well.

Batch Endpoints

Both platforms offer batch APIs that dramatically reduce call counts:

// HubSpot: Batch read contacts (up to 100 per call)
POST /crm/v3/objects/contacts/batch/read
{
  "inputs": [
    {"id": "12345"},
    {"id": "12346"},
    {"id": "12347"}
  ],
  "properties": ["firstname", "lastname", "company", "jobtitle"]
}

// HubSpot: Batch update contacts (up to 100 per call)
POST /crm/v3/objects/contacts/batch/update
{
  "inputs": [
    {
      "id": "12345",
      "properties": {"ai_qualification_score": "85"}
    },
    {
      "id": "12346",
      "properties": {"ai_qualification_score": "42"}
    }
  ]
}

Salesforce offers Composite API for combining multiple operations into a single request, and Bulk API 2.0 for large data operations:

// Salesforce: Composite request (up to 25 subrequests)
POST /services/data/v61.0/composite
{
  "compositeRequest": [
    {
      "method": "PATCH",
      "url": "/services/data/v61.0/sobjects/Contact/003xx1",
      "referenceId": "contact1",
      "body": {"AI_Qualification_Score__c": 85}
    },
    {
      "method": "PATCH",
      "url": "/services/data/v61.0/sobjects/Contact/003xx2",
      "referenceId": "contact2",
      "body": {"AI_Qualification_Score__c": 42}
    }
  ]
}

Implementing Throttling

Even with batch endpoints, you need throttling logic. A production-grade approach includes:

  • Token bucket rate limiter that respects the platform's limits with a safety margin (aim for 80% of the published limit)
  • Response header monitoring: HubSpot returns X-HubSpot-RateLimit-Daily-Remaining; Salesforce returns Sforce-Limit-Info
  • Exponential backoff on 429 (Too Many Requests) responses, starting at 1 second and doubling up to 60 seconds
  • Queue-based processing for large batches: enqueue operations and process them at a controlled rate rather than firing everything at once

Error Handling Best Practices

CRM APIs fail in interesting ways. Field validation errors, duplicate record detection, permission changes, and API version deprecations all surface at runtime. Robust error handling is what separates a pipeline that runs reliably from one that silently drops data.

Common Error Categories

HTTP StatusMeaningRecovery Strategy
400 Bad RequestMalformed request or invalid field valueLog the full request and response; check field types and value constraints
401 UnauthorizedToken expired or revokedRefresh the token and retry once; alert if refresh also fails
403 ForbiddenMissing permissions for the requested operationCheck scopes; this usually requires manual intervention to update app permissions
404 Not FoundRecord deleted or invalid IDRemove from processing queue; log for data reconciliation
409 ConflictDuplicate record (HubSpot) or version conflictFetch existing record, merge data, and retry
429 Too Many RequestsRate limit exceededBackoff and retry using the Retry-After header value
500/502/503Server-side errorRetry with exponential backoff; up to 3 retries before moving to dead-letter queue

Idempotency and Retry Safety

Not all operations are safe to retry. GET and PATCH requests are generally idempotent, meaning retrying them produces the same result. But POST requests that create records are not idempotent: retrying a failed create can produce duplicates.

To handle this safely:

  • For creates: Check if the record already exists (using email or a unique identifier) before creating. HubSpot will return a 409 on duplicate email, which you can catch and convert to an update.
  • For updates: Include a timestamp or version field so you can detect whether your update was already applied.
  • For batch operations: Track each individual item's status. A batch write to HubSpot can partially succeed, with some items updated and others rejected. Process the response item by item.

Dead-Letter Queues

When a record fails repeatedly, you need somewhere to put it that isn't "silently dropped." A dead-letter queue (DLQ) pattern works well:

  1. Attempt the operation
  2. On failure, retry with backoff (up to 3 times)
  3. If still failing, write the failed operation to a DLQ (a database table, file, or queue)
  4. Alert the team
  5. Process DLQ items manually or on a delayed schedule

This pattern is especially important for write operations. A failed qualification score write means a rep doesn't see the data they need. A failed contact create means your deduplication logic might break downstream.

Logging and Observability

Log every API call with enough context to debug failures after the fact:

  • Request URL, method, and body (redact sensitive fields)
  • Response status code and body
  • Processing time
  • Record IDs being operated on
  • The pipeline or workflow that triggered the call

When your AI qualification pipeline suddenly starts producing 403 errors at 2 AM because someone changed the Private App scopes, these logs are what get you to resolution in minutes instead of hours.

Putting It Together: Common Integration Patterns

With the building blocks in place, here are three integration patterns that GTM teams commonly implement.

Pattern 1: Real-Time Context Fetch for AI Generation

Trigger: An AI agent needs to generate a personalized email for a specific contact.

1
Fetch contact from CRM with key properties (title, lifecycle stage, engagement history)
2
Fetch associated company with firmographic data (industry, size, revenue)
3
Assemble context packet with normalized, typed data
4
Send to AI tool for generation
5
Write AI output back to a custom field on the contact record

This pattern works well for generating targeted sequences for specific personas where real-time accuracy matters more than throughput.

Pattern 2: Batch Qualification Pipeline

Trigger: New leads enter the CRM from a marketing campaign or import.

1
Query CRM for all contacts in "New" lifecycle stage created in the last 24 hours
2
Batch read contact and company data (100 at a time)
3
Run AI qualification on each record, respecting rate limits
4
Batch write scores and rationale back to CRM
5
Update lifecycle stage for high-scoring leads to trigger sales notification

This is the backbone of automated MQL-to-SQL qualification, and it scales well with batch endpoints and queue-based processing.

Pattern 3: Continuous CRM Enrichment

Trigger: Scheduled job runs daily or on CRM webhook events.

1
Identify stale records by querying for contacts where ai_last_updated is older than 30 days
2
Enrich with external data from Clay, Clearbit, or other enrichment providers
3
Re-run AI qualification with updated context
4
Write updated scores and data back to CRM
5
Flag significant changes (e.g., score changed by more than 20 points) for rep review

This keeps your CRM context fresh for AI tools, preventing the data decay problem where AI-generated outputs degrade as CRM data ages.

Beyond Individual API Connections

Everything above works for connecting one CRM to one AI tool. But GTM stacks don't have one integration; they have dozens. Your CRM connects to your enrichment platform, which feeds your AI qualification engine, which informs your sequence generation, which writes back to your CRM, which updates your analytics. Each connection is its own authentication setup, its own data model mapping, its own error handling logic, its own rate limiting implementation.

The maintenance cost compounds. When HubSpot deprecates an API version or Salesforce changes a field type, you're updating code in multiple places. When your team adds a new custom field to the CRM, every downstream integration needs to know about it. When someone changes a picklist value, qualification scores start coming back wrong because the mapping table hasn't been updated.

What starts as a clean CRM-to-AI pipeline becomes a web of point-to-point integrations that requires constant tending. Teams that have built hands-off outbound pipelines know this pain intimately: the "hands-off" part breaks the moment any upstream system changes.

The underlying problem isn't any individual API connection. It's that every tool in your stack maintains its own partial copy of your GTM context, and keeping those copies synchronized is a full-time job. What you need is a layer that owns the context centrally and serves it to whatever tool needs it.

This is what platforms like Octave are built to solve. Instead of building and maintaining direct integrations between your CRM, enrichment tools, and AI agents, Octave acts as the unified context layer. Your CRM data, enrichment signals, ICP definitions, and engagement history all feed into Octave's context graph. When an AI agent needs to qualify a lead or generate a personalized sequence, it pulls from a single, normalized source of truth rather than making ad-hoc CRM API calls. When your CRM data changes, Octave propagates that context to every downstream consumer automatically. The result is fewer integrations to maintain, consistent context across tools, and AI outputs that stay accurate as your GTM data evolves.

FAQ

Should I use HubSpot's v3 or v4 API?

Use v3 for most object operations (contacts, companies, deals). The v4 API currently covers associations and some newer features. HubSpot is gradually migrating functionality to v4, so check their changelog for your specific endpoints. For new integrations, start with v3 for objects and v4 for associations.

How do I handle Salesforce sandbox vs. production environments?

Salesforce sandboxes use test.salesforce.com for authentication instead of login.salesforce.com. Make the login URL configurable in your integration so you can switch between environments without code changes. Always test new field mappings and write operations in sandbox first.

What's the best way to handle CRM API versioning?

Salesforce versions its API explicitly (v61.0, v62.0, etc.) and supports multiple versions simultaneously. Pin your integration to a specific version and test against newer versions quarterly. HubSpot versions less formally but deprecates endpoints with advance notice. Subscribe to both platforms' developer changelogs.

How do I avoid creating duplicate records when writing AI outputs back?

Use "upsert" patterns. HubSpot supports search-before-create using email as a unique key. Salesforce offers an explicit upsert endpoint that checks an external ID field. Always implement deduplication logic rather than relying on the CRM to catch duplicates after the fact.

Is it better to use webhooks or polling for detecting CRM changes?

Webhooks are preferred for real-time responsiveness (HubSpot supports workflow-based webhooks; Salesforce has Platform Events and Change Data Capture). Polling is simpler to implement and debug. For most AI integration use cases, a polling interval of 5-15 minutes provides sufficient freshness without API overhead.

How much CRM context should I pass to AI tools?

Enough to be useful, not so much that you blow context windows or add noise. For email personalization, focus on: person title/role, company industry and size, current deal stage, and 2-3 recent engagement signals. For qualification, add ICP-relevant fields like tech stack, funding stage, and hiring signals. Test with your specific AI model to find the signal-to-noise sweet spot.

Conclusion

Connecting your CRM to AI tools via REST API is foundational GTM engineering work. The mechanics are straightforward once you understand each platform's authentication model, data types, and batch patterns. The hard part isn't making the first API call; it's building integrations that handle edge cases gracefully, scale to thousands of records, and stay reliable as your CRM schema evolves.

Start with read operations to feed your AI tools rich context. Add write operations to push AI-generated insights back where reps can actually use them. Implement rate limiting and error handling from the beginning, not as an afterthought. And as your integration count grows, think seriously about whether point-to-point connections will continue to serve you, or whether a unified context layer makes more sense for your architecture.

The teams that get this right don't just have AI tools that work. They have AI tools that work with the full picture of their go-to-market data, producing outputs that reps trust and prospects respond to.

FAQ

Frequently Asked Questions

Still have questions? Get connected to our support team.