On this page

The REST API v1 is the same interface the Zapier app is built on, eleven endpoints under https://crm.vgraple.co.in/api/v1, authenticated with a Bearer API key, that any language capable of an HTTP request can call directly. This quickstart takes you from an empty terminal to a created contact, a sent template, a read of your conversation history and a first broadcast, with the exact request and response shapes.
REST API quickstart: first 5 of 8 steps
- 1Test authentication
- 2Create or update a contact
- 3List your approved templates
- 4Send an approved template
- 5List recent messages
Before you start
- You need an API key. See creating and rotating API keys; only the organisation owner or a super admin can generate one, from Settings > API Keys. If your integration only reads, create it as a Read-only key: every example in this guide except the sends and creates works with one.
- You need at least one approved WhatsApp template if you plan to send a message, since the API only sends templates, never free-form text.
curlis used in every example here because it needs no setup, but the same requests work from any HTTP client, Postman, a Nodefetchcall, Python'srequests, or a Zapier-adjacent tool like Make's HTTP module.
Steps
- Test authentication. Confirm your key works and see which organisation it belongs to.

curl https://crm.vgraple.co.in/api/v1/me \
-H "Authorization: Bearer vgk_your_key_here"
A working key returns your organisation's name and key metadata; a wrong or missing key returns 401 with an error message. This exact call is also what Zapier uses as its connection test.
- Create or update a contact.
POST /api/v1/contactsupserts by phone number, so calling it twice with the same number never creates a duplicate.
curl -X POST https://crm.vgraple.co.in/api/v1/contacts \
-H "Authorization: Bearer vgk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"phone": "+919876543210", "name": "Priya Sharma", "email": "[email protected]"}'
A first call returns 201 with {"contact": {...}, "created": true}. Calling it again with the same phone number and a different name returns 200 with {"contact": {...}, "created": false}, updating the name on the existing record.
- List your approved templates. You need an exact template name to send one, so fetch the list first if you don't already have it.
curl "https://crm.vgraple.co.in/api/v1/templates" \
-H "Authorization: Bearer vgk_your_key_here"
{
"templates": [
{ "name": "order_confirmation", "language": "en", "category": "UTILITY", "variable_count": 2 }
]
}
- Send an approved template. Only template sends are accepted; there is no way to send free-form text through the API.
curl -X POST https://crm.vgraple.co.in/api/v1/messages \
-H "Authorization: Bearer vgk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"to": "+919876543210", "template": "order_confirmation", "variables": ["Priya", "#4521"]}'
{
"message_id": "msg_01H8X...",
"contact_id": "con_01H8W...",
"conversation_id": "conv_01H8V..."
}
The variables array fills {{1}}, {{2}}, and so on in the template body, in order. This goes through the same tracked send path automation rules and owner alerts use, so it lands in the inbox and respects opt-out status.
- List recent messages. Useful for polling if you are not using webhooks for real-time delivery, and it is also the export endpoint: it returns every message in your inbox on every channel, imported WhatsApp history included.
curl "https://crm.vgraple.co.in/api/v1/messages?direction=inbound&limit=20" \
-H "Authorization: Bearer vgk_your_key_here"
{
"messages": [
{
"id": "msg_01H8X...",
"wa_message_id": "wamid.HBgM...",
"body": "Do you deliver to Rotterdam?",
"type": "text",
"direction": "inbound",
"status": "delivered",
"template_name": null,
"error": null,
"is_forwarded": false,
"media": null,
"reply_to": null,
"wa_timestamp": "2026-07-13T10:00:00.000Z",
"created_at": "2026-07-13T10:00:01.482Z",
"updated_at": "2026-07-13T10:00:01.482Z",
"channel": "whatsapp",
"contact": { "id": "con_01H8W...", "name": "Asha Patel", "phone": "919876543210", "wa_id": "919876543210" },
"conversation_id": "conv_01H8V..."
}
],
"next_cursor": "msg_01H8X..."
}
Paginate with cursor: pass the previous response next_cursor value as ?cursor=... to fetch the next page; next_cursor is null on the last page. Narrow the set with direction, type, status, conversation_id, contact_id, and a since/until window; flip to oldest-first with order=asc.
A message with an attachment carries a media object instead of null, whose url is an authenticated download link:
curl -L "https://crm.vgraple.co.in/api/v1/media/msg_01H8X..." \
-H "Authorization: Bearer vgk_your_key_here" -o attachment.webp
- List conversations. The thread each message belongs to, with its contact, channel and lifecycle timestamps.
curl "https://crm.vgraple.co.in/api/v1/conversations?status=open&limit=20" \
-H "Authorization: Bearer vgk_your_key_here"
Each row has id, channel, status, priority, label, unread_count, last_message_at, first_response_at, waiting_since, closed_at, csat_score and the contact. To read one thread, pass its id back as GET /api/v1/messages?conversation_id=<id>&order=asc. Pulling everything into your own database is covered in exporting your message history.
- Create a lead.
POST /api/v1/leadsaccepts a name, phone, email, an optionalpipeline_id(defaults to your default pipeline) and notes.
curl -X POST https://crm.vgraple.co.in/api/v1/leads \
-H "Authorization: Bearer vgk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name": "Asha Patel", "phone": "+919876543210", "notes": "Asked about the premium package"}'
- Create a broadcast, carefully.
POST /api/v1/broadcastsaccepts an audience mode, a template, variable mappings and an optionalsend_at. Omitsend_at, or set it in the future, while testing; setting it to"now"sends immediately to the real audience through the exact same guards (opt-in, daily limit, quiet hours) as the composer.
What you will see
Every successful response is JSON with a resource key matching what you created or fetched (contact, lead, message_id, templates, leads). List endpoints wrap results in a plural key alongside next_cursor for pagination. Errors are a JSON object with an error string describing what went wrong, at a matching HTTP status code.
Settings and options
| Setting or field | What it does | Default |
|---|---|---|
| Base URL | https://crm.vgraple.co.in/api/v1 | Fixed |
| Auth header | Authorization: Bearer vgk_... | Required on every request |
| Rate limit | Requests allowed per key per hour | 600, rolling window |
| Broadcast creation limit | Extra cap on POST /api/v1/broadcasts | 20 campaigns/hour per organisation |
| Pagination | limit (max 100) and cursor on list endpoints | limit=50 |
| Ordering | order=asc or order=desc on list endpoints, by creation time | desc (newest first) |
| Date window | since and until, ISO 8601, both inclusive, on creation time | Unbounded |
| Key access level | Full access, or read-only (403 on every write) | Full access |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Every request returns 401 | Missing, malformed, rotated or revoked API key | Recheck the Authorization: Bearer header and confirm the key still exists in Settings > API Keys |
POST /api/v1/messages returns "No approved template named..." | The template name is misspelled, not yet approved, or disabled | Call GET /api/v1/templates first and use the exact name field it returns |
| A create call returns 400 with a field-specific message | The request body failed validation (for example an invalid phone number or a missing required field) | Read the error message directly; it names the offending field |
| 429 "Rate limit exceeded" | More than 600 requests in the last rolling hour on this key | Slow down or batch calls; give a second integration its own key rather than sharing one |
| 403 "This API key is read-only" | The key was created with read-only access and the call writes | Use a full-access key for writes; a key access level cannot be changed after creation |
A list call returns 400 naming since, order or limit | A parameter was malformed, for example a non-ISO date or an inverted since/until window | Read the error message; it names the parameter and the accepted form |
POST /api/v1/broadcasts sent to the wrong audience unexpectedly | send_at was set to "now" or a past-due time against a broader audience than intended | Always test with a small tagged audience first; review the audience mode and template mapping before setting a live send time |
| A phone number in a response looks different from what you sent | Numbers are normalised to E.164 on save | Expected; store and compare using the normalised value the API returns, not your original input string |
Where does this fit next to webhooks and Zapier?
The REST API is the foundation both the Zapier app and outbound webhooks are built on. If you want VGraple CRM to notify your system the moment something happens, use webhooks instead of polling this API in a loop; if you need to push data in, like creating a contact or sending a template from your own system, this API (or Zapier as a no-code layer over it) is the right tool. GET/POST /api/v1/hooks and DELETE /api/v1/hooks/{id} let you manage REST hook subscriptions directly with your API key, the same mechanism Zapier uses when a Zap turns on, including subscribing to any of the platform's full event catalog, even the few not yet exposed as checkboxes in Settings > Webhooks.