Convoly API
Read your Instagram conversations, send DMs and check your automations from your own application, over a plain REST API.
Introduction
The Convoly API is organised around REST. It uses predictable, resource-oriented URLs, returns JSON for every response including errors, and relies on standard HTTP status codes.
https://www.convoly.in/api/v1What you can do
- Read the contacts who have messaged your Instagram account
- Export the phone numbers people shared as leads, and keep a CRM in sync with them
- Read full conversation history, including shared reels and posts
- Send a DM, with Instagram’s 24-hour window enforced for you
- Inspect your automations and their run statistics
- Track how many messages you have sent this month
Authentication
Authenticate with an API key, sent in the Authorization header of every request. Create keys in Settings → API keys inside Convoly.
Authorization: Bearer cvy_live_YOUR_KEYA bare Authorization: cvy_live_… without the Bearer prefix is also accepted.
curl https://www.convoly.in/api/v1/account \
-H "Authorization: Bearer cvy_live_YOUR_KEY"Errors & status codes
Every error returns the same envelope: a stable machine-readable error you can branch on, and a message written for a person.
{
"error": "send_not_allowed",
"message": "More than 24 hours have passed since this person's last message, so an automated reply cannot be sent."
}| Status | Error | Meaning |
|---|---|---|
400 | invalid_request | A required field is missing or malformed. The message says which. |
400 | invalid_json | The request body is not valid JSON. |
400 | invalid_cursor | The cursor was not issued by this API. Pass next_cursor back exactly as received. |
401 | missing_api_key | No Authorization header was sent. |
401 | invalid_api_key | The key is wrong, or it has been revoked. |
403 | account_inactive | The Instagram account behind this key is disconnected. Reconnect it in Convoly. |
404 | contact_not_found | No contact with that identifier belongs to this account. |
404 | lead_not_found | No lead with that id belongs to this account. |
422 | send_not_allowed | Instagram's messaging window forbids this send. Check reason and messaging_window. |
500 | internal_error | Something went wrong on our side. Safe to retry with backoff. |
Get account
GET https://www.convoly.in/api/v1/accountReturns the Instagram account this API key belongs to. Useful as a health check and to confirm which account a key acts on.
curl https://www.convoly.in/api/v1/account \
-H "Authorization: Bearer cvy_live_YOUR_KEY"{
"account": {
"id": "3f0c…",
"instagram_user_id": "17841444482386124",
"username": "languageiquanta",
"display_name": "Language With iQuanta",
"account_type": "BUSINESS",
"followers_count": 222,
"status": "active"
}
}List contacts
GET https://www.convoly.in/api/v1/contactsPeople who have messaged your account. Each contact carries its current messaging_window, so you can tell whether a send would be permitted without reimplementing Instagram's rules.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | How many to return. Default 50, maximum 200. |
curl "https://www.convoly.in/api/v1/contacts?limit=2" \
-H "Authorization: Bearer cvy_live_YOUR_KEY"{
"contacts": [
{
"id": "b71e…",
"instagram_user_id": "1099883142544299",
"name": "Lakshay Panwar",
"tags": ["lead"],
"opted_out": false,
"last_inbound_at": "2026-08-30T10:20:11.000Z",
"messaging_window": "open"
}
],
"has_more": true
}- messaging_window is one of: open (you may reply freely), human_agent_only (past 24h — only a human-typed reply is lawful), closed (past 7 days), no_conversation (they have never messaged you).
List leads
GET https://www.convoly.in/api/v1/leadsPhone numbers people shared with your account — typed in a DM, or collected by an automation — together with who shared them and which automation captured them. Leads come oldest change first, so you can import everything once and then fetch only what changed.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Leads per page. Default 100, maximum 500. |
cursor | string | No | next_cursor from the previous page, passed back unchanged. Omit for the first page. |
updated_since | ISO 8601 | No | Only leads created or changed at or after this instant, e.g. 2026-10-16T00:00:00Z. |
status | string | No | new, contacted, converted or not_interested. |
source | string | No | automation (collected by an automation step) or chat (typed in a conversation). |
curl "https://www.convoly.in/api/v1/leads?limit=200" \
-H "Authorization: Bearer cvy_live_YOUR_KEY"curl "https://www.convoly.in/api/v1/leads?updated_since=2026-10-16T08:42:34.123Z" \
-H "Authorization: Bearer cvy_live_YOUR_KEY"{
"leads": [
{
"id": "a41e…",
"phone": "+919876543210",
"status": "new",
"source": "automation",
"automation": { "id": "5d2f…", "name": "GUIDE comment flow" },
"contact": {
"id": "8b1c…",
"instagram_user_id": "1784…",
"username": "priya.sharma",
"name": "Priya Sharma",
"profile_picture_url": "https://…"
},
"first_shared_at": "2026-10-14T11:02:51.120Z",
"last_shared_at": "2026-10-15T09:30:12.004Z",
"updated_at": "2026-10-15T09:30:12.004Z"
}
],
"has_more": true,
"next_cursor": "eyJ0IjoiMjAyNi0xMC0xNSAwOTozMDoxMi4wMDQ…"
}- Moving leads into a CRM: call with no parameters, then keep requesting with cursor=next_cursor until has_more is false. Store the updated_at of the last lead you received.
- Keeping the CRM current: on each later run, pass that stored value as updated_since and page through the same way. Any lead created, re-shared or changed in status since then comes back; nothing else does. A lead can appear in two runs, so upsert on id.
- phone is in E.164 format (+ and country code). One person who shares two numbers is two leads with the same contact.id.
- automation is null when the number was typed in an ordinary conversation rather than collected by an automation step.
Update a lead's status
PATCH https://www.convoly.in/api/v1/leads/:lead_idWrites a follow-up outcome back to Convoly, so the Lead Manager shows the same status as your CRM. This changes updated_at, so the lead also appears in your next updated_since sync.
| Parameter | Type | Required | Description |
|---|---|---|---|
lead_id | string | Yes | The lead's id, from List leads. |
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | Yes | new, contacted, converted or not_interested. |
curl -X PATCH https://www.convoly.in/api/v1/leads/a41e… \
-H "Authorization: Bearer cvy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "converted"}'{ "lead": { "id": "a41e…", "status": "converted" } }{ "error": "lead_not_found", "message": "No lead with that id on this account." }List conversations
GET https://www.convoly.in/api/v1/conversationsEvery conversation with a message count and a preview of the most recent message.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | How many to return. Default 50, maximum 200. |
curl https://www.convoly.in/api/v1/conversations \
-H "Authorization: Bearer cvy_live_YOUR_KEY"{
"conversations": [
{
"contact_id": "b71e…",
"instagram_user_id": "1099883142544299",
"name": "Lakshay Panwar",
"message_count": 24,
"last_message": {
"direction": "in",
"text": "Hey",
"sent_at": "2026-08-30T10:20:11.000Z"
}
}
],
"has_more": false
}Get messages
GET https://www.convoly.in/api/v1/conversations/:contact_id/messagesThe message history for one conversation, oldest first. Shared reels and posts arrive as attachments with an instagram.com permalink and an empty text field.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | Yes | The contact's Convoly id. |
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Most recent N messages. Default 50, maximum 200. |
curl https://www.convoly.in/api/v1/conversations/b71e.../messages \
-H "Authorization: Bearer cvy_live_YOUR_KEY"{
"contact_id": "b71e…",
"messages": [
{ "id": "…", "direction": "in", "text": "Hey", "attachments": [],
"source": "dm", "sent_at": "2026-08-30T10:20:11.000Z" },
{ "id": "…", "direction": "in", "text": "", "source": "dm",
"attachments": [
{ "kind": "share", "media": "reel", "title": null,
"url": "https://www.instagram.com/reel/DWVusbwDQba/" }
],
"sent_at": "2026-08-30T10:21:02.000Z" }
]
}{
"error": "conversation_not_found",
"message": "No conversation with that contact id."
}Send a DM
POST https://www.convoly.in/api/v1/messagesSends a direct message. This runs the same 24-hour window check as the rest of Convoly, so a send that Instagram would reject is refused here first, with a reason you can act on.
| Parameter | Type | Required | Description |
|---|---|---|---|
text | string | Yes | The message. 1–1000 characters. |
contact_id | string | No | The contact's Convoly id. Provide this or instagram_user_id. |
instagram_user_id | string | No | The contact's Instagram-scoped id. Provide this or contact_id. |
curl -X POST https://www.convoly.in/api/v1/messages \
-H "Authorization: Bearer cvy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "b71e…",
"text": "Thanks for reaching out — here is the link you asked for."
}'{
"message": {
"id": "…",
"mid": "aWdfZAG1faXRlbTo…",
"contact_id": "b71e…",
"text": "Thanks for reaching out …",
"status": "sent"
}
}{
"error": "send_not_allowed",
"message": "More than 24 hours have passed since this person's last message, so an automated reply cannot be sent.",
"reason": "outside_24h_window_and_not_human_agent",
"messaging_window": "human_agent_only"
}{
"error": "contact_not_found",
"message": "No contact matches that identifier for this account. A contact exists only after they message you first."
}- Instagram has no cold outreach. A contact can only be messaged after they message you first — otherwise you get reason: user_never_messaged_first.
- The HUMAN_AGENT tag, which extends the window to 7 days, is intentionally not available over the API. Meta permits it only for text a person actually typed, and an API call cannot attest to that. Use the Live Chat inbox for those replies.
- A 422 is a normal outcome, not a fault. Handle it rather than retrying — retrying will not change the window.
List automations
GET https://www.convoly.in/api/v1/flowsYour automations, their trigger configuration and their run statistics.
curl https://www.convoly.in/api/v1/flows \
-H "Authorization: Bearer cvy_live_YOUR_KEY"{
"flows": [
{
"id": "…",
"name": "Auto-DM links from comments",
"status": "live",
"trigger": { "type": "comment", "match": "contains", "keywords": ["link"] },
"stats": { "runs": 42, "sent": 39, "clicks": 12 }
}
]
}Get usage
GET https://www.convoly.in/api/v1/usageSends in the current calendar month, broken down by outcome — including the ones the window check stopped before they cost you anything.
curl https://www.convoly.in/api/v1/usage \
-H "Authorization: Bearer cvy_live_YOUR_KEY"{
"period": "current_calendar_month",
"sent": 142,
"blocked_by_window": 9,
"blocked_opted_out": 0,
"failed": 1,
"total_attempted": 152
}