Replying and triage
Every action here is a POST (or DELETE for removing a tag) on one conversation, requires the write:chat scope (or * for full access), and returns the conversation after the change:
{ "chat": { "id": "cmq5chat0alk34567890", "status": "CLOSED", "handler": "HUMAN" } }Each action does exactly what the same button does in the app, so both always agree.
Acting as a rep
An API key belongs to the workspace, not to a person. When an action is done in someone's name, you say who with one of these fields in the JSON body:
| Field | Description |
|---|---|
asUserId | A workspace user ID |
asUserEmail | That user's login email |
The user must be an active member of the key's workspace. A user from another workspace, or one who has been deactivated, is refused with 400.
| Action | Actor |
|---|---|
| Reply, take over, hand back | Required. The visitor sees this person's name |
| Close, reopen, assign | Optional. Defaults to the workspace owner |
| Tags, spam | Not used |
Reply
POST /api/v1/chats/{id}/reply
Sends a message to the visitor. They see it in the chat widget straight away.
| Field | Required | Description |
|---|---|---|
body | Yes | The message, up to 2,000 characters. Leading and trailing spaces are trimmed |
asUserId or asUserEmail | Yes | The rep replying |
curl -s -X POST https://agentlyleads.com/api/v1/chats/cmq5chat0alk34567890/reply \
-H "Authorization: Bearer $AL_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "Yes, we ship to Canada. Most orders arrive in 3 to 5 business days.",
"asUserEmail": "kevin@example.com"
}'Returns 201 with the new message and the conversation:
{
"message": {
"id": "cmq6msg02alk34567890",
"role": "USER",
"userId": "cmq1user0alk34567890",
"userName": "Kevin Rep",
"body": "Yes, we ship to Canada. Most orders arrive in 3 to 5 business days.",
"createdAt": "2026-09-27T14:03:10.000Z"
},
"chat": { "id": "cmq5chat0alk34567890", "handler": "HUMAN" }
}Only the rep handling a conversation can write into it. If the rep you name is not already handling it, the reply takes it over first, as described next. A body that is empty or only spaces is refused before anything changes hands.
Take over
POST /api/v1/chats/{id}/take-over
A rep picks the conversation up from the AI agent or the waiting queue. It becomes HUMAN, is assigned to that rep, and the visitor sees "Kevin joined the conversation". From then on the AI agent does not answer.
curl -s -X POST https://agentlyleads.com/api/v1/chats/cmq5chat0alk34567890/take-over \
-H "Authorization: Bearer $AL_KEY" \
-H "Content-Type: application/json" \
-d '{ "asUserId": "cmq1user0alk34567890" }'You rarely need this before replying, since a reply takes over on its own. Use it to claim a conversation before you have something to say.
Hand back
POST /api/v1/chats/{id}/hand-back
The rep steps out. The conversation is unassigned and the visitor sees "Kevin left the conversation". Where it goes next depends on the chat site's settings:
- Inside the site's team hours it goes back to
WAITINGand the team is notified. - Outside them the AI agent picks it up and answers the visitor.
Only the rep handling the conversation, or a workspace owner, can hand it back:
{ "error": "Only the person handling this chat can hand it back." }Close and reopen
POST /api/v1/chats/{id}/close and POST /api/v1/chats/{id}/reopen
Closing marks the conversation handled for the whole team. If the visitor is linked to a contact or lead, the transcript is saved onto that record as a chat communication. Closing again later, after a reopen, updates that same entry instead of adding a second copy.
Both take an optional asUserId or asUserEmail for who did it. The body can be left out entirely:
curl -s -X POST https://agentlyleads.com/api/v1/chats/cmq5chat0alk34567890/close \
-H "Authorization: Bearer $AL_KEY"Closing does not change handler. A visitor who writes again after a conversation is closed starts a new conversation.
Assign
POST /api/v1/chats/{id}/assign
Sets the conversation's owner in the team queue, or clears it.
| Field | Required | Description |
|---|---|---|
assigneeId | Yes | An active workspace user, or null to unassign |
asUserId or asUserEmail | No | Who made the change. Defaults to the workspace owner |
curl -s -X POST https://agentlyleads.com/api/v1/chats/cmq5chat0alk34567890/assign \
-H "Authorization: Bearer $AL_KEY" \
-H "Content-Type: application/json" \
-d '{ "assigneeId": "cmq1user1alk34567890" }'The new owner is notified, the same as when a teammate assigns them a conversation in the app. Assigning changes ownership only: it does not change who is typing to the visitor. To do both, take over as that person instead.
Tags
POST /api/v1/chats/{id}/tags adds one tag. DELETE /api/v1/chats/{id}/tags removes one.
To add, send tagId for an existing workspace tag, or name to find the tag by name and create it if your workspace has not used it yet:
curl -s -X POST https://agentlyleads.com/api/v1/chats/cmq5chat0alk34567890/tags \
-H "Authorization: Bearer $AL_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Pricing" }'To remove, pass tagId or name as a query parameter. Names match case-insensitively. Removing a tag from a conversation never deletes the tag itself.
curl -s -X DELETE "https://agentlyleads.com/api/v1/chats/cmq5chat0alk34567890/tags?name=pricing" \
-H "Authorization: Bearer $AL_KEY"Tags are shared with the rest of the CRM, so "Pricing" on a chat is the same tag as "Pricing" on a deal.
Spam
POST /api/v1/chats/{id}/spam
| Field | Required | Description |
|---|---|---|
spam | Yes | true to mark as spam, false to undo |
Marking a conversation as spam moves it to the Spam folder and blocks the visitor on that chat site for 30 days, so the same browser cannot start a new conversation. spam: false takes it back out of Spam and lifts the block straight away.
curl -s -X POST https://agentlyleads.com/api/v1/chats/cmq5chat0alk34567890/spam \
-H "Authorization: Bearer $AL_KEY" \
-H "Content-Type: application/json" \
-d '{ "spam": true }'The conversation's visitor.blocked and visitor.blockedUntil show the block.
The block is per browser and per site. It stops a visitor from reopening the chat after you mark them as spam, but someone who clears their browser storage can start again, so treat it as a way to keep junk out of the queue rather than as a security control.