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

The Sports Angel Team
10 Min Read

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
Share This Article
The Sports Angel Team is a group of technology specialists, software consultants and digital business advisors with combined experience across hundreds of implementations for small businesses, agencies and startups across the United States. Every tool we review is tested firsthand before we recommend it.
Leave a Comment

Leave a Reply

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