Zoho CRM API: How to Create a Lead Using a Simple cURL Command

Zoho CRM API: How to Create a Lead Using a Simple cURL Command

The Problem

Every sales team eventually needs to push leads from an external source — a landing page, a support ticket, a third-party form — directly into Zoho CRM without manual data entry. The Zoho API makes this possible, but getting your first successful request through is a wall of friction: OAuth 2.0 token generation, data center region mismatches, scope configuration, and a JSON body structure that Zoho silently rejects if formatted incorrectly. Most developers burn hours on a INVALID_TOKEN or INVALID_DATA error with no clear explanation. This tutorial cuts through that. You will generate a working OAuth access token, construct a correctly shaped cURL request, and create a real lead record in Zoho CRM — all from the terminal — with every gotcha documented so you do not hit them.

Tech Stack & Prerequisites

  • Zoho CRM account — Free tier works (sign up at zoho.com/crm)
  • Zoho Developer Console access — to register an OAuth client
  • cURL 7.68+ — pre-installed on macOS/Linux; Windows users use WSL or Git Bash
  • A REST client — Postman (optional, for Step 4 verification)
  • A text editor — to store tokens and IDs temporarily
  • Your Zoho data center region.com / .eu / .in / .com.au / .jp (critical — covered in Step 1)

Step-by-Step Implementation

Step 1: Register Your OAuth Client in Zoho

Zoho uses OAuth 2.0 for all API calls. You need a Client ID and Client Secret before making any request.

1.1 — Go to the Zoho API Console

Navigate to: https://api-console.zoho.com

Log in with the same account that owns your CRM.

1.2 — Create a new client

  • Click “Add Client”
  • Choose “Self Client” (best for server-to-server / terminal use)
  • Click “Create”

You will immediately see your:

  • Client ID — looks like 1000.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  • Client Secret — looks like abc123...

Save both in a scratch file. Do not share these.

1.3 — Generate a Grant Token

Still in the Self Client screen:

  • Click the “Generate Code” tab
  • In the Scope field, paste exactly:
ZohoCRM.modules.leads.CREATE,ZohoCRM.modules.leads.READ
  • Set Time Duration to 10 minutes
  • Home Domain: select your data center (e.g., zoho.com for US accounts)
  • Click “Create” — copy the one-time Grant Token immediately

⚠️ The grant token expires in 10 minutes and can only be exchanged once.

Step 2: Exchange the Grant Token for Access & Refresh Tokens

This is a one-time step. You exchange the short-lived grant token for a long-lived refresh token and a short-lived access token (valid 60 minutes).

Replace the placeholders and run this cURL command in your terminal:

bash
curl -X POST "https://accounts.zoho.com/oauth/v2/token" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=https://www.zoho.com" \
  -d "code=YOUR_GRANT_TOKEN"

EU accounts: replace accounts.zoho.com with accounts.zoho.eu IN accounts: use accounts.zoho.in — mismatching regions is the #1 cause of INVALID_CODE

Expected response:

json
{
  "access_token": "1000.abc123...xyz",
  "refresh_token": "1000.def456...uvw",
  "token_type": "Bearer",
  "expires_in": 3600
}

Store both tokens securely. Save them to a local .env file:

.env

env
ZOHO_ACCESS_TOKEN=1000.abc123...xyz
ZOHO_REFRESH_TOKEN=1000.def456...uvw
ZOHO_CLIENT_ID=1000.XXXXXXXXXXXXXXXXXXXXXX
ZOHO_CLIENT_SECRET=your_client_secret_here
ZOHO_REGION=com

Never commit this file. Add it to .gitignore immediately:

bash
echo ".env" >> .gitignore

Step 3: Create a Lead via cURL

With a valid access token, you can now POST to the Leads module.

3a — The Core cURL Command

bash
curl -X POST "https://www.zohoapis.com/crm/v6/Leads" \
  -H "Authorization: Zoho-oauthtoken YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      {
        "Last_Name": "Reyes",
        "First_Name": "Marco",
        "Email": "marco.reyes@example.com",
        "Phone": "+1-415-555-0192",
        "Company": "Stackline Inc.",
        "Lead_Source": "Web Site",
        "Description": "Submitted via API integration test"
      }
    ]
  }'

EU accounts: replace zohoapis.com with zohoapis.eu

Expected success response:

json
{
  "data": [
    {
      "code": "SUCCESS",
      "details": {
        "Created_Time": "2025-06-15T10:22:41+00:00",
        "Modified_Time": "2025-06-15T10:22:41+00:00",
        "id": "5545974000000478001"
      },
      "message": "record added",
      "status": "success"
    }
  ]
}

Save the returned id — it is your new lead’s record ID in Zoho CRM.

3b — Refresh Your Access Token (when it expires after 60 min)

Access tokens expire. Use your refresh token to get a new one without re-authenticating:

bash
curl -X POST "https://accounts.zoho.com/oauth/v2/token" \
  -d "grant_type=refresh_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "refresh_token=YOUR_REFRESH_TOKEN"

Response:

json
{
  "access_token": "1000.newtoken...abc",
  "token_type": "Bearer",
  "expires_in": 3600
}

Update your .env with the new access_token and re-run Step 3a.

Step 4: Verify the Lead Was Created

4a — Fetch the lead by ID via cURL

bash
curl -X GET "https://www.zohoapis.com/crm/v6/Leads/YOUR_LEAD_ID" \
  -H "Authorization: Zoho-oauthtoken YOUR_ACCESS_TOKEN"

Expected response (trimmed):

json
{
  "data": [
    {
      "id": "5545974000000478001",
      "Last_Name": "Reyes",
      "First_Name": "Marco",
      "Email": "marco.reyes@example.com",
      "Company": "Stackline Inc.",
      "Lead_Source": "Web Site"
    }
  ]
}

4b — Verify inside Zoho CRM UI

  1. Log in to Zoho CRM → click Leads in the top nav
  2. Sort by Created Time (Descending)
  3. You should see Marco Reyes at the top of the list

4c — Test in Postman (optional)

  • Method: POST
  • URL: https://www.zohoapis.com/crm/v6/Leads
  • Headers:
    • Authorization: Zoho-oauthtoken YOUR_ACCESS_TOKEN
    • Content-Type: application/json
  • Body: raw JSON — paste the data payload from Step 3a
  • Send → confirm "status": "success" in the response panel

Common Errors & Troubleshooting

Gotcha 1 — "code": "INVALID_TOKEN" on every request

This means your access token is expired, malformed, or from the wrong region.

Fix:

  • Confirm the token starts with 1000. — if not, it was not generated correctly
  • Check that your API endpoint region matches your account region. A US token hitting zohoapis.eu will always fail
  • Re-run the token refresh command from Step 3b and replace the token in your request

Gotcha 2 — "code": "MANDATORY_NOT_FOUND" with status "error"

Zoho requires Last_Name for every lead. No exceptions. This error fires if it is missing, empty, or the key is spelled incorrectly (it is case-sensitive).

Fix:

bash
# ❌ Wrong — missing Last_Name or wrong casing
-d '{ "data": [{ "Lastname": "Reyes" }] }'

# ✅ Correct — exact field name required
-d '{ "data": [{ "Last_Name": "Reyes" }] }'

Also check: Lead_Source must match a value from your CRM picklist exactly (e.g., "Web Site" not "Website").

Gotcha 3 — "code": "INVALID_DATA" but the JSON looks correct

This usually means the data key is missing its array wrapper, or the JSON body has a trailing comma that makes it invalid.

Fix: Validate your JSON before sending:

bash
# Pipe your JSON through Python's JSON validator
echo '{
  "data": [
    { "Last_Name": "Reyes", "Company": "Stackline Inc." }
  ]
}' | python3 -m json.tool

If it prints the formatted JSON, it is valid. If it throws a JSONDecodeError, fix the syntax first. Also confirm Content-Type: application/json is set — without it, Zoho may parse the body as form data and reject it.

Security Checklist

  • Never hardcode tokens in scripts — load them from .env using os.getenv() (Python) or process.env (Node.js)
  • Add .env to .gitignore before the first commit — a leaked Client Secret requires full OAuth client rotation
  • Use minimum required scopes — only grant ZohoCRM.modules.leads.CREATE if you do not need read access
  • Rotate access tokens on a schedule — automate the refresh flow; do not store long-lived access tokens in plain text
  • Restrict your Self Client by IP if Zoho adds that option in your console — limits token misuse if a secret leaks
  • Audit API usage in Zoho CRM under Setup → Developer Space → API Usage — watch for unexpected call spikes
  • Store the refresh token in a secrets manager (AWS Secrets Manager, HashiCorp Vault, or GitHub Secrets for CI/CD) — never in a .env file on a shared or production server

Leave a Comment

Your email address will not be published. Required fields are marked *