# MJX CRM REST API reference (v1)

> Every MJX CRM API endpoint — me, leads, one lead, add, update, notes and tags — with parameters, example requests, responses and errors.

Source: https://pathshala.mjxtechnologies.com/docs/developers/crm-api-reference/

The MJX CRM REST API v1 has seven endpoints under `/api/v1/crm`. All of them need an [API key](developers/authentication-and-api-keys); the write endpoints need a key with `write`.

## Base address

```
https://<your-code>.pathshala.mjxtechnologies.com/api/v1/crm
```

Copy the exact address from **For your developer** on the Integrations page.

> [!PATH] Apps › MJX CRM › CRM settings › Advanced: API & webhooks › For your developer

![Integrations screen with the For your developer section marked](media/developers/crm-api-reference-1.jpg "Apps › MJX CRM › CRM settings › Advanced: API & webhooks › For your developer")

Requests with a body use `Content-Type: application/json`. Every answer is JSON in the same envelope:

```json
{ "success": true, "data": { … } }
{ "success": false, "error": { "message": "A sentence you can show or log." } }
```

## Endpoints

| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | `/me` | read | Which institute and key, the statuses and webhook events |
| GET | `/leads` | read | Search and list leads |
| GET | `/leads/{id}` | read | One lead |
| POST | `/leads` | write | Add a lead (or get the existing one) |
| PATCH | `/leads/{id}` | write | Change status, tags or next follow-up |
| POST | `/leads/{id}/notes` | write | Add a note |
| GET | `/tags` | read | The tags in use, with counts |

Any other path under `/api/v1/crm` answers `404` with "No such endpoint. See the Integrations page for the list."

## The lead object

Every endpoint that returns a lead uses this shape. It is also the `lead` inside a [webhook](developers/lead-webhook) body.

| Field | Type | Meaning |
|---|---|---|
| `id` | string (uuid) | The lead's id |
| `name` | string | The person who enquired |
| `phone` | string or null | `+91XXXXXXXXXX` for an Indian mobile; `+<digits>` for a foreign number |
| `email` | string or null | Email address |
| `student` | string or null | Who the enquiry is for (a child, for example) |
| `class` | string or null | The class column, when set in the CRM |
| `status` | string | One of the five statuses below |
| `source` | string or null | Where it came from, e.g. `website`, `api`, `web_hook`, `indiamart`, `meta_ads` |
| `message` | string or null | What they wrote |
| `tags` | array of strings | Tags, oldest first |
| `assigned_to` | string or null | Name of the staff member who looks after it |
| `next_followup_at` | string (ISO date) or null | Next follow-up |
| `extra` | object | Extra details (shown as "Additional details" on the lead) |
| `details` | object | Your vertical's own fields, e.g. `target_exam`, `course`, `service`, `company`, `city`, `reference`, `from`, `to` |
| `interest` | string or null | One line summing up what they want |
| `created_at`, `updated_at` | string (ISO date) | Times |

### Statuses

| API value | Default on-screen name |
|---|---|
| `new` | New |
| `in_conversation` | Talking |
| `follow_up_due` | Call back |
| `converted` | Admitted (school, college) · Joined (coaching, tuition, institute) · Won (business) |
| `lost` | Not interested |

---

## GET /me

Checks your key and tells you who it belongs to.

```bash
curl "$MJX_BASE/me" -H "Authorization: Bearer $MJX_KEY"
```

```json
{
  "success": true,
  "data": {
    "institute": { "code": "sunrise", "name": "Sunrise Coaching" },
    "key": { "name": "Old ERP", "scopes": ["read", "write"] },
    "statuses": ["new", "in_conversation", "follow_up_due", "converted", "lost"],
    "webhook_events": ["lead.created", "lead.status_changed"]
  }
}
```

---

## GET /leads

Lists leads, newest activity first (by `updated_at`, or `created_at` when never updated). Deleted leads are never returned.

### Query parameters

| Name | Type | Default | Meaning |
|---|---|---|---|
| `status` | string | — | Only this status. Must be one of the five values. |
| `tag` | string | — | Only leads with this tag (any case). Up to 30 characters. |
| `q` | string | — | Part of a name, phone number, email or student name. Up to 80 characters. |
| `updated_since` | ISO date | — | Only leads created or updated at or after this time. |
| `followups_due` | any value | — | Present = only open leads whose follow-up is today or earlier (India time), oldest first. Leave it out otherwise. |
| `limit` | integer | 50 | 1 to 100 |
| `offset` | integer | 0 | Skip this many |

```bash
curl "$MJX_BASE/leads?status=new&tag=NEET&updated_since=2026-10-01T00:00:00Z&limit=50&offset=0" \
  -H "Authorization: Bearer $MJX_KEY"
```

```json
{
  "success": true,
  "data": {
    "leads": [
      {
        "id": "3f2b9c1e-6a4d-4f8e-9b7a-2c1d0e5f6a7b",
        "name": "Sunita Sharma",
        "phone": "+9198XXXXXX10",
        "email": null,
        "student": "Aarav",
        "class": null,
        "status": "new",
        "source": "website",
        "message": "Wants a weekend batch",
        "tags": ["NEET"],
        "assigned_to": "Priya",
        "next_followup_at": null,
        "extra": {},
        "created_at": "2026-10-28T05:30:00.000Z",
        "updated_at": "2026-10-28T05:30:00.000Z",
        "details": { "target_exam": "NEET 2027" },
        "interest": "NEET 2027"
      }
    ],
    "total": 1,
    "limit": 50,
    "offset": 0
  }
}
```

### Errors

| Status | Message |
|---|---|
| 400 | `status must be one of: new, in_conversation, follow_up_due, converted, lost.` |
| 400 | `updated_since must be a date, e.g. 2026-09-01T00:00:00Z.` |

---

## GET /leads/{id}

```bash
curl "$MJX_BASE/leads/3f2b9c1e-6a4d-4f8e-9b7a-2c1d0e5f6a7b" -H "Authorization: Bearer $MJX_KEY"
```

```json
{ "success": true, "data": { "lead": { "id": "3f2b9c1e-…", "name": "Sunita Sharma", "…": "…" } } }
```

| Status | Message |
|---|---|
| 404 | `No lead with that id.` (also for an id that is not a uuid, or a lead of another institute) |

---

## POST /leads

Adds a lead. Needs a `write` key.

### Body

A phone number **or** an email is required. Everything else is optional. Field names do not have to match ours: `full_name`, `mobile`, `whatsapp`, `email_address`, `requirement` and many more are recognised.

| Field | Type | Meaning |
|---|---|---|
| `name` | string | The person. If left out, it is taken from `company`, then the email, then "Caller <phone>". |
| `phone` | string | An Indian mobile, with or without +91, or a foreign number with its country code |
| `email` | string | Email address |
| `student` | string | Who the enquiry is for |
| `class` | string | Class, course, exam, grade or batch — stored in your vertical's own field |
| `message` | string | What they wrote (up to 5,000 characters) |
| `company`, `service`, `budget`, `timeline`, `city` | string | Stored under `details` |
| `reference`, `type`, `from`, `to` | string | A booking's reference, type and dates — stored under `details` |
| `tags` | array or comma-separated string | Tags |
| `source` | string | Your own word for where it came from, e.g. `website`. Default `api`. |
| `extra` | object | Extra details; also any field we do not recognise. Up to 30 kept. |

Fields named `cf_<something>` go into the CRM's custom fields.

```bash
curl -X POST "$MJX_BASE/leads" \
  -H "Authorization: Bearer $MJX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Ravi Mehta","phone":"98XXXXXX21","email":"ravi@example.com","class":"NEET 2027","city":"Bhopal","tags":["Walk-in"],"source":"erp"}'
```

### Responses

| Status | When | `data` |
|---|---|---|
| 201 | A new lead was added | `{ "lead": {…}, "duplicate": false }` |
| 200 | The phone (or, with no phone, the email) already belongs to a lead | `{ "lead": {…existing…}, "duplicate": true }` — nothing is written |
| 400 | No usable phone or email | `A phone number or an email is required.` or `phone "…" is not a phone number, and there is no email.` |
| 403 | Read-only key | `This key is read-only. Make a key with "write" to add or change leads.` |

> [!IMPORTANT]
> A lead added through the API gets **no WhatsApp message** and no follow-up date. It does start the institute's [Auto actions](crm/automation), ring the new-lead bell, and go to every webhook that listens for `lead.created`.

---

## PATCH /leads/{id}

Changes a lead. Needs a `write` key. Send only the fields you want to change.

| Field | Type | Meaning |
|---|---|---|
| `status` | string | One of the five statuses |
| `tags` | array of strings | **Replaces** all the lead's tags |
| `next_followup_at` | ISO date or `null` | Sets the next follow-up; `null` clears it |

```bash
curl -X PATCH "$MJX_BASE/leads/3f2b9c1e-6a4d-4f8e-9b7a-2c1d0e5f6a7b" \
  -H "Authorization: Bearer $MJX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"converted","tags":["NEET"],"next_followup_at":"2026-11-01T10:00:00+05:30"}'
```

```json
{ "success": true, "data": { "lead": { "id": "3f2b9c1e-…", "status": "converted", "…": "…" } } }
```

| Status | Message |
|---|---|
| 400 | `status must be one of: new, in_conversation, follow_up_due, converted, lost.` |
| 400 | `next_followup_at must be a date, or null to clear it.` |
| 404 | `No lead with that id.` |

> [!NOTE]
> Setting `next_followup_at` works like setting it in the CRM: the automatic follow-up can then message that person. A real change of status sends `lead.status_changed` to your webhooks.

---

## POST /leads/{id}/notes

Adds a note that staff see on the lead. Needs a `write` key. The note is saved with your key's name in front, e.g. `[Old ERP] Called from our ERP`.

```bash
curl -X POST "$MJX_BASE/leads/3f2b9c1e-6a4d-4f8e-9b7a-2c1d0e5f6a7b/notes" \
  -H "Authorization: Bearer $MJX_KEY" \
  -H "Content-Type: application/json" \
  -d '{"note":"Called from our ERP"}'
```

`201 Created`:

```json
{ "success": true, "data": { "note": { "id": "b7c1…", "note": "Called from our ERP", "created_at": "2026-10-28T06:10:00.000Z" } } }
```

| Status | Message |
|---|---|
| 400 | `note is required.` |
| 404 | `No lead with that id.` |

The saved note (with the key's name) is cut at 2,000 characters.

---

## GET /tags

The tags in use on live leads, most used first (up to 200).

```bash
curl "$MJX_BASE/tags" -H "Authorization: Bearer $MJX_KEY"
```

```json
{ "success": true, "data": { "tags": [ { "tag": "NEET", "count": 42 }, { "tag": "Walk-in", "count": 7 } ] } }
```

## Errors on every endpoint

| Status | Message |
|---|---|
| 401 | `Send your API key: "Authorization: Bearer mjx_live_…".` |
| 401 | `That API key is not valid, or it was revoked.` |
| 403 | `MJX CRM is not switched on for this institute.` |
| 403 | `The API comes with MJX CRM Pro.` |
| 429 | `Too many requests for this key — at most 120 a minute. Slow down and try again.` |
| 500 | `Something went wrong on our side. Try again.` |
| 503 | `Could not check that key just now. Try again.` |

See [Rate limits and errors](developers/rate-limits-and-errors) for how to handle them.

## Questions people ask

### Can I delete a lead through the API?

No. The API can read, add and update leads and add notes. Deleting is done by people in the CRM.

### Can I assign a lead to a staff member through the API?

No. The lead's owner is returned as assigned_to (a name) but cannot be set through the API.

### How do I page through all leads?

Use limit (up to 100) and offset. The answer includes total, so stop when offset reaches total.

### How do I fetch only leads that changed since my last sync?

Pass updated_since with an ISO date, e.g. 2026-10-01T00:00:00Z.
