AskHandle Blog
How to Connect AI to Your CRM Using Structured JSON
- JSON
- CRM
- AI

How to Connect AI to Your CRM Using Structured JSON
AI becomes useful in CRM workflows when it stops producing vague prose and starts producing structured output your systems can act on.
That is the practical bridge between AI and a CRM like Salesforce:
AI reads unstructured input → returns structured JSON → your app validates it → your integration writes it into Salesforce.
The same pattern works in reverse too:
User asks a question → AI generates a structured query/request → your app fetches Salesforce data → AI explains the result.
Salesforce’s REST API supports creating records through its sObject resources and retrieving data through query endpoints that use SOQL. Salesforce also documents composite record creation for batch-style inserts.
Why structured JSON matters
A CRM does not want “Daniel sounded interested and might want a demo.” It wants fields like:
1{
2 "FirstName": "Daniel",
3 "LastName": "Reed",
4 "Company": "Apex Corp",
5 "Status": "Working - Contacted",
6 "Description": "Interested in enterprise plan and requested a demo next week"
7}That is why the most important implementation detail is not “using AI,” but controlling the output format.
How AI generates structured JSON
The simplest way is to give the model:
- the source text
- the exact schema you want
- strict instructions not to add extra text
- a rule not to invent missing fields
Sample prompt: extract a Salesforce Lead
1You are a CRM data extraction assistant.
2
3Extract only facts explicitly present in the conversation.
4Do not guess or infer missing values.
5Return ONLY valid JSON.
6
7JSON schema:
8{
9 "object": "Lead",
10 "fields": {
11 "FirstName": "",
12 "LastName": "",
13 "Company": "",
14 "Email": "",
15 "Phone": "",
16 "Status": "",
17 "Description": ""
18 }
19}
20
21Conversation:
22“We spoke with Daniel Reed from Apex Corp. He wants a demo next week for the enterprise plan. His email is daniel@apex.example.”Example AI output
1{
2 "object": "Lead",
3 "fields": {
4 "FirstName": "Daniel",
5 "LastName": "Reed",
6 "Company": "Apex Corp",
7 "Email": "daniel@apex.example",
8 "Phone": "",
9 "Status": "Working - Contacted",
10 "Description": "Requested a demo next week for the enterprise plan"
11 }
12}That output is now usable by code.
Practical prompt patterns people actually use
1. Extract a new lead from a call note
1Extract Salesforce Lead fields from the text below.
2
3Rules:
4- Return ONLY JSON
5- Use empty string for unknown values
6- Do not invent values
7- Keep Description concise
8
9Schema:
10{
11 "object": "Lead",
12 "fields": {
13 "FirstName": "",
14 "LastName": "",
15 "Company": "",
16 "Email": "",
17 "Phone": "",
18 "LeadSource": "",
19 "Status": "",
20 "Description": ""
21 }
22}
23
24Text:
25{{sales_call_summary}}2. Update an opportunity after a meeting
1Convert this meeting summary into a Salesforce Opportunity update.
2
3Return ONLY JSON:
4{
5 "object": "Opportunity",
6 "lookup": {
7 "Name": ""
8 },
9 "fields": {
10 "StageName": "",
11 "Amount": null,
12 "CloseDate": "",
13 "Description": "",
14 "NextStep": ""
15 }
16}
17
18Only include values explicitly supported by the text.
19Meeting summary:
20{{meeting_notes}}3. Turn a user request into a Salesforce fetch request
1You translate user requests into a structured CRM query plan.
2
3Return ONLY JSON:
4{
5 "action": "",
6 "object": "",
7 "filters": {},
8 "fields": [],
9 "limit": 10
10}
11
12User request:
13"Show me open opportunities for Apex Corp closing this quarter."Example output
1{
2 "action": "query",
3 "object": "Opportunity",
4 "filters": {
5 "Account.Name": "Apex Corp",
6 "IsClosed": false,
7 "CloseDate": "THIS_QUARTER"
8 },
9 "fields": ["Id", "Name", "StageName", "Amount", "CloseDate"],
10 "limit": 10
11}Sending AI-generated JSON into Salesforce
Salesforce REST API lets you create records by posting field values to an sObject endpoint. In practice, that usually looks like a POST to an endpoint like /services/data/vXX.X/sobjects/Lead/.
Python example: create a Lead in Salesforce
1import os
2import requests
3
4SALESFORCE_INSTANCE_URL = os.environ["SALESFORCE_INSTANCE_URL"]
5SALESFORCE_ACCESS_TOKEN = os.environ["SALESFORCE_ACCESS_TOKEN"]
6API_VERSION = "v61.0"
7
8ai_output = {
9 "object": "Lead",
10 "fields": {
11 "FirstName": "Daniel",
12 "LastName": "Reed",
13 "Company": "Apex Corp",
14 "Email": "daniel@apex.example",
15 "Status": "Working - Contacted",
16 "Description": "Requested a demo next week for the enterprise plan"
17 }
18}
19
20def create_salesforce_record(object_name: str, fields: dict) -> dict:
21 url = f"{SALESFORCE_INSTANCE_URL}/services/data/{API_VERSION}/sobjects/{object_name}/"
22 headers = {
23 "Authorization": f"Bearer {SALESFORCE_ACCESS_TOKEN}",
24 "Content-Type": "application/json"
25 }
26
27 response = requests.post(url, headers=headers, json=fields, timeout=30)
28 response.raise_for_status()
29 return response.json()
30
31result = create_salesforce_record(ai_output["object"], ai_output["fields"])
32print(result)Salesforce’s record-creation response includes the new record ID and a success flag when the request succeeds.
Safer production pattern: validate before writing
You usually do not want to pass raw model output straight into Salesforce.
A better flow is:
- AI returns JSON
- Your app validates required fields
- Your app checks for duplicates
- Your app maps allowed fields only
- Your app writes to Salesforce
That matters because the AI may omit fields, misread names, or produce values your org does not allow.
Python example: validate AI output before send
1REQUIRED_LEAD_FIELDS = ["LastName", "Company"]
2
3def validate_lead_payload(fields: dict) -> tuple[bool, list[str]]:
4 errors = []
5 for field in REQUIRED_LEAD_FIELDS:
6 if not fields.get(field):
7 errors.append(f"Missing required field: {field}")
8 return (len(errors) == 0, errors)
9
10is_valid, errors = validate_lead_payload(ai_output["fields"])
11
12if not is_valid:
13 print({"ok": False, "errors": errors})
14else:
15 result = create_salesforce_record(ai_output["object"], ai_output["fields"])
16 print({"ok": True, "salesforce_result": result})Updating Salesforce records from AI output
For updates, the pattern is usually:
- AI identifies the object and fields to update
- your app finds the record ID first
- your app sends a PATCH request to that specific record
Example AI output for an Opportunity update
1{
2 "object": "Opportunity",
3 "lookup": {
4 "Name": "Apex Corp - Enterprise Expansion"
5 },
6 "fields": {
7 "StageName": "Proposal/Price Quote",
8 "Amount": 25000,
9 "NextStep": "Send pricing deck and security documentation",
10 "Description": "Customer asked for enterprise pricing and security review materials"
11 }
12}Python example: update an Opportunity
1import requests
2from urllib.parse import quote
3
4def query_salesforce(soql: str) -> dict:
5 url = f"{SALESFORCE_INSTANCE_URL}/services/data/{API_VERSION}/query"
6 headers = {"Authorization": f"Bearer {SALESFORCE_ACCESS_TOKEN}"}
7 response = requests.get(url, headers=headers, params={"q": soql}, timeout=30)
8 response.raise_for_status()
9 return response.json()
10
11def update_salesforce_record(object_name: str, record_id: str, fields: dict) -> None:
12 url = f"{SALESFORCE_INSTANCE_URL}/services/data/{API_VERSION}/sobjects/{object_name}/{record_id}"
13 headers = {
14 "Authorization": f"Bearer {SALESFORCE_ACCESS_TOKEN}",
15 "Content-Type": "application/json"
16 }
17 response = requests.patch(url, headers=headers, json=fields, timeout=30)
18 response.raise_for_status()
19
20opp_name = "Apex Corp - Enterprise Expansion"
21soql = f"SELECT Id, Name FROM Opportunity WHERE Name = '{opp_name.replace(\"'\", \"\\\\'\")}' LIMIT 1"
22query_result = query_salesforce(soql)
23
24records = query_result.get("records", [])
25if not records:
26 raise ValueError("Opportunity not found")
27
28opp_id = records[0]["Id"]
29
30update_fields = {
31 "StageName": "Proposal/Price Quote",
32 "Amount": 25000,
33 "NextStep": "Send pricing deck and security documentation",
34 "Description": "Customer asked for enterprise pricing and security review materials"
35}
36
37update_salesforce_record("Opportunity", opp_id, update_fields)
38print("Opportunity updated")Salesforce documents the Query resource for executing SOQL through the REST API, and notes that synchronous query responses can return up to 2,000 records at a time before pagination/query locators come into play.
Using AI to fetch data from Salesforce
This is the other half that teams care about.
Instead of hard-coding every possible question, you let AI translate a user’s request into a structured query plan. Your backend then converts that plan into SOQL, fetches data from Salesforce, and optionally sends the result back through AI for summarization.
Example prompt: turn a question into a query plan
1Translate the user request into a structured Salesforce query plan.
2
3Return ONLY valid JSON:
4{
5 "object": "",
6 "fields": [],
7 "filters": {},
8 "sort": "",
9 "limit": 10
10}
11
12User request:
13"Find my open deals over $20,000 that are likely closing this month."Example AI output
1{
2 "object": "Opportunity",
3 "fields": ["Id", "Name", "StageName", "Amount", "CloseDate", "Owner.Name"],
4 "filters": {
5 "IsClosed": false,
6 "Amount_gt": 20000,
7 "CloseDate": "THIS_MONTH"
8 },
9 "sort": "CloseDate ASC",
10 "limit": 10
11}Your app then translates that into SOQL.
Example SOQL
1SELECT Id, Name, StageName, Amount, CloseDate, Owner.Name
2FROM Opportunity
3WHERE IsClosed = false
4AND Amount > 20000
5AND CloseDate = THIS_MONTH
6ORDER BY CloseDate ASC
7LIMIT 10Salesforce uses SOQL for record queries in the REST API query resource.
Python example: fetch data from Salesforce
1def fetch_open_opportunities():
2 soql = """
3 SELECT Id, Name, StageName, Amount, CloseDate, Owner.Name
4 FROM Opportunity
5 WHERE IsClosed = false
6 AND Amount > 20000
7 AND CloseDate = THIS_MONTH
8 ORDER BY CloseDate ASC
9 LIMIT 10
10 """.strip()
11
12 result = query_salesforce(soql)
13 return result.get("records", [])
14
15records = fetch_open_opportunities()
16for r in records:
17 print({
18 "Id": r.get("Id"),
19 "Name": r.get("Name"),
20 "StageName": r.get("StageName"),
21 "Amount": r.get("Amount"),
22 "CloseDate": r.get("CloseDate"),
23 "OwnerName": (r.get("Owner") or {}).get("Name")
24 })Let AI explain the Salesforce data after fetch
Once you retrieve data, you can pass it back to AI with a second prompt:
1You are a sales assistant.
2Summarize these Salesforce opportunities for an account executive.
3Focus on stage, amount, close timing, and what deserves attention.
4
5Data:
6{{json_from_salesforce}}That gives users a natural-language answer while keeping the actual data retrieval deterministic.
Batch creation example
If you want AI-extracted data from a call or import process to create many records at once, Salesforce documents composite sObject tree creation and says a single request can create up to 200 records.
Example AI output for multiple leads
1{
2 "records": [
3 {
4 "attributes": { "type": "Lead", "referenceId": "lead1" },
5 "FirstName": "Daniel",
6 "LastName": "Reed",
7 "Company": "Apex Corp"
8 },
9 {
10 "attributes": { "type": "Lead", "referenceId": "lead2" },
11 "FirstName": "Maya",
12 "LastName": "Chen",
13 "Company": "Northstar Labs"
14 }
15 ]
16}Python example: create multiple records
1def create_multiple_records(records_payload: dict) -> dict:
2 url = f"{SALESFORCE_INSTANCE_URL}/services/data/{API_VERSION}/composite/tree/Lead"
3 headers = {
4 "Authorization": f"Bearer {SALESFORCE_ACCESS_TOKEN}",
5 "Content-Type": "application/json"
6 }
7 response = requests.post(url, headers=headers, json=records_payload, timeout=30)
8 response.raise_for_status()
9 return response.json()Authentication note
To call the Salesforce REST API, your app needs OAuth-based access through a connected app. Salesforce’s docs describe OAuth authorization with connected apps, and their REST quick start covers the setup path for basic API use.
What this looks like in a real workflow
A practical production workflow often looks like this:
Write path
- Sales call transcript arrives
- AI extracts JSON for
Lead,Contact, orOpportunity - Backend validates the payload
- Backend checks Salesforce for duplicates
- Backend creates or updates the record
- AI writes a short activity summary into
Descriptionor as a note/task
Read path
- User asks, “What’s the status of Apex?”
- AI converts the question into a structured query plan
- Backend executes SOQL through Salesforce REST API
- AI summarizes the results in plain English
Best practices
The teams that get this right usually follow a few rules:
- Keep AI output narrow and schema-based
- Never let the model invent CRM fields or IDs
- Do not give the model direct unrestricted write access
- Validate all payloads before they hit Salesforce
- Search for existing records before creating new ones
- Keep an audit trail of source text, AI JSON, and API payload
- Start with low-risk writes like notes, summaries, and task drafts
The real pattern is not “AI magically updates Salesforce.”
It is:
AI produces structured JSON. Your application decides what is safe. Salesforce API handles the record write or query.
That is how you turn AI from a chatbot into a working CRM layer.