Send an agent reply
Deprecated — use POST /v1/agents/events/ingest (AgentEvent protocol).
This route stays live for old @novu/framework and existing OpenAPI sendReply clients.
Do not use it for new integrations.
Message actions
reply— markdown, interactive card, or tool-approval card (optionalfiles)edit— update a previously delivered message in placedeleteMessages— remove rendered platform messages (history is kept)addReactions— add emoji reactions to existing messages
Turn control
typing—{ status?: string }to set status, or"stop"to clearresolve— mark the conversation resolved (optionally with a final reply)error: true— report a customer-runtime failure (cannot combine with other actions)
Signals & tools
signals— metadata set/delete/clear, or trigger a Novu workflowtoolResults— persist tool outputs into conversation historytoolApprovalRequest— ledger a gated tool call (pair with an approval card reply)
Returns { data: { messageId, platformThreadId } } when a reply or edit is delivered;
otherwise { data: null }.
package main
import(
"context"
"github.com/novuhq/novu-go/v3"
"github.com/novuhq/novu-go/v3/models/components"
"log"
)
func main() {
ctx := context.Background()
s := v3.New(
v3.WithSecurity("YOUR_SECRET_KEY_HERE"),
)
res, err := s.Agents.SendReply(ctx, "support-agent", components.AgentReplyPayloadDto{
ConversationID: "64f5a1c2e8b7a3d9f0c1b2a3",
IntegrationIdentifier: "slack-support",
Reply: v3.Pointer(components.CreateReplyMarkdownReplyContentDto(
components.MarkdownReplyContentDto{
Markdown: "**Report ready.** Your weekly summary is attached.",
},
)),
}, nil)
if err != nil {
log.Fatal(err)
}
if res.Object != nil {
// handle response
}
}import { Novu } from "@novu/api";
const novu = new Novu({
secretKey: "YOUR_SECRET_KEY_HERE",
});
async function run() {
const result = await novu.agents.sendReply({
conversationId: "64f5a1c2e8b7a3d9f0c1b2a3",
integrationIdentifier: "slack-support",
reply: {
markdown: "**Report ready.** Your weekly summary is attached.",
},
}, "support-agent");
console.log(result);
}
run();using Novu;
using Novu.Models.Components;
var sdk = new NovuSDK(secretKey: "YOUR_SECRET_KEY_HERE");
var res = await sdk.Agents.SendReplyAsync(
agentId: "support-agent",
agentReplyPayloadDto: new AgentReplyPayloadDto() {
ConversationId = "64f5a1c2e8b7a3d9f0c1b2a3",
IntegrationIdentifier = "slack-support",
Reply = Reply.CreateMarkdownReplyContentDto(
new MarkdownReplyContentDto() {
Markdown = "**Report ready.** Your weekly summary is attached.",
}
),
}
);
// handle responsedeclare(strict_types=1);
require 'vendor/autoload.php';
use novu;
use novu\Models\Components;
$sdk = novu\Novu::builder()
->setSecurity(
'YOUR_SECRET_KEY_HERE'
)
->build();
$agentReplyPayloadDto = new Components\AgentReplyPayloadDto(
conversationId: '64f5a1c2e8b7a3d9f0c1b2a3',
integrationIdentifier: 'slack-support',
reply: new Components\MarkdownReplyContentDto(
markdown: '**Report ready.** Your weekly summary is attached.',
),
);
$response = $sdk->agents->sendReply(
agentId: 'support-agent',
agentReplyPayloadDto: $agentReplyPayloadDto
);
if ($response->object !== null) {
// handle response
}import novu_py
from novu_py import Novu
with Novu(
secret_key="YOUR_SECRET_KEY_HERE",
) as novu:
res = novu.agents.send_reply(agent_id="support-agent", agent_reply_payload_dto=novu_py.AgentReplyPayloadDto(
conversation_id="64f5a1c2e8b7a3d9f0c1b2a3",
integration_identifier="slack-support",
reply=novu_py.MarkdownReplyContentDto(
markdown="**Report ready.** Your weekly summary is attached.",
),
))
# Handle response
print(res)curl --request POST \
--url https://api.novu.co/v1/agents/{agentId}/reply \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"conversationId": "64f5a1c2e8b7a3d9f0c1b2a3",
"integrationIdentifier": "slack-support",
"reply": {
"markdown": "**Report ready.** Your weekly summary is attached."
}
}
'const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
conversationId: '64f5a1c2e8b7a3d9f0c1b2a3',
integrationIdentifier: 'slack-support',
reply: {markdown: '**Report ready.** Your weekly summary is attached.'}
})
};
fetch('https://api.novu.co/v1/agents/{agentId}/reply', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));HttpResponse<String> response = Unirest.post("https://api.novu.co/v1/agents/{agentId}/reply")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"conversationId\": \"64f5a1c2e8b7a3d9f0c1b2a3\",\n \"integrationIdentifier\": \"slack-support\",\n \"reply\": {\n \"markdown\": \"**Report ready.** Your weekly summary is attached.\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.novu.co/v1/agents/{agentId}/reply")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"conversationId\": \"64f5a1c2e8b7a3d9f0c1b2a3\",\n \"integrationIdentifier\": \"slack-support\",\n \"reply\": {\n \"markdown\": \"**Report ready.** Your weekly summary is attached.\"\n }\n}"
response = http.request(request)
puts response.read_body{
"messageId": "1712345678.123456",
"platformThreadId": "C0123456789"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"errors": {
"fieldName1": {
"messages": [
"Field is required",
"Must be a valid email address"
],
"value": "invalidEmail"
},
"fieldName2": {
"messages": [
"Must be at least 18 years old"
],
"value": 17
},
"fieldName3": {
"messages": [
"Must be a boolean value"
],
"value": true
},
"fieldName4": {
"messages": [
"Must be a valid object"
],
"value": {
"key": "value"
}
},
"fieldName5": {
"messages": [
"Field is missing"
],
"value": null
},
"fieldName6": {
"messages": [
"Undefined value"
]
}
},
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}"API rate limit exceeded"{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}"Please wait some time, then try again."@novu/framework on the bridge (for example with the Python, Go, PHP, .NET, or Java SDKs), or when another backend service needs to post into a live conversation.
The request body is a bag of optional actions. Provide at least one of reply, edit, resolve, signals, toolResults, toolApprovalRequest, addReactions, deleteMessages, typing, or error. Named examples in the OpenAPI spec cover the common shapes.
When a reply or edit is delivered, the response data contains messageId and platformThreadId. Side-effect-only requests return data: null.
For the TypeScript bridge API (ctx.reply, cards, signals), see Reply.Authorizations
API key authentication. Allowed headers-- "Authorization: ApiKey <novu_secret_key>".
Headers
A header for idempotency purposes
Path Parameters
Agent identifier (slug) for the agent that owns the conversation.
Body
Reply payload. Provide at least one action: reply, edit, resolve, signals, toolResults, toolApprovalRequest, addReactions, deleteMessages, typing, or error. See named examples for common shapes used by server-side SDKs.
Conversation id to reply into. Obtained from the inbound agent event / bridge payload.
"64f5a1c2e8b7a3d9f0c1b2a3"
Channel integration identifier linked to the agent for this conversation (e.g. slack-support).
"slack-support"
Outbound message content. Exactly one of markdown, card, or toolApprovalCard. Optional files attach to the message. Cannot be combined with edit.
- Option 1
- Option 2
- Option 3
Show child attributes
Show child attributes
Tool-lifecycle ledger row for a gated tool call. Pair with reply.toolApprovalCard (or another reply shape) to deliver the approval UI.
Show child attributes
Show child attributes
In-place edit of a previously posted agent message. Cannot be combined with reply, resolve, signals, toolResults, toolApprovalRequest, addReactions, or deleteMessages.
Show child attributes
Show child attributes
Mark the conversation resolved. May be combined with a final reply.
Show child attributes
Show child attributes
Side-effect signals executed during this turn: conversation metadata mutations, Novu workflow triggers, or human-in-the-loop interactions.
- Option 1
- Option 2
- Option 3
- Option 4
- Option 5
Show child attributes
Show child attributes
Tool-call outcomes to persist in conversation history (typically before the assistant reply).
Show child attributes
Show child attributes
Emoji reactions to add to existing platform messages.
Show child attributes
Show child attributes
Delete previously posted platform messages. Removes the rendered message only — history is preserved.
Show child attributes
Show child attributes
Per-turn typing/status control. Pass { status?: string } to set/update the status (omit status for "Thinking…"), or "stop" to clear it. Best-effort per platform.
stop { "status": "Looking up your order…" }
Bridge reports that the customer runtime failed this turn. Cannot be combined with other actions. Novu delivers generic user-facing error copy.
true
Response
OK. When a reply or edit is delivered, data contains the platform message identifiers. Side-effect-only requests (typing, reactions, deletes, signals without an outbound message) return data: null.
Was this page helpful?
package main
import(
"context"
"github.com/novuhq/novu-go/v3"
"github.com/novuhq/novu-go/v3/models/components"
"log"
)
func main() {
ctx := context.Background()
s := v3.New(
v3.WithSecurity("YOUR_SECRET_KEY_HERE"),
)
res, err := s.Agents.SendReply(ctx, "support-agent", components.AgentReplyPayloadDto{
ConversationID: "64f5a1c2e8b7a3d9f0c1b2a3",
IntegrationIdentifier: "slack-support",
Reply: v3.Pointer(components.CreateReplyMarkdownReplyContentDto(
components.MarkdownReplyContentDto{
Markdown: "**Report ready.** Your weekly summary is attached.",
},
)),
}, nil)
if err != nil {
log.Fatal(err)
}
if res.Object != nil {
// handle response
}
}import { Novu } from "@novu/api";
const novu = new Novu({
secretKey: "YOUR_SECRET_KEY_HERE",
});
async function run() {
const result = await novu.agents.sendReply({
conversationId: "64f5a1c2e8b7a3d9f0c1b2a3",
integrationIdentifier: "slack-support",
reply: {
markdown: "**Report ready.** Your weekly summary is attached.",
},
}, "support-agent");
console.log(result);
}
run();using Novu;
using Novu.Models.Components;
var sdk = new NovuSDK(secretKey: "YOUR_SECRET_KEY_HERE");
var res = await sdk.Agents.SendReplyAsync(
agentId: "support-agent",
agentReplyPayloadDto: new AgentReplyPayloadDto() {
ConversationId = "64f5a1c2e8b7a3d9f0c1b2a3",
IntegrationIdentifier = "slack-support",
Reply = Reply.CreateMarkdownReplyContentDto(
new MarkdownReplyContentDto() {
Markdown = "**Report ready.** Your weekly summary is attached.",
}
),
}
);
// handle responsedeclare(strict_types=1);
require 'vendor/autoload.php';
use novu;
use novu\Models\Components;
$sdk = novu\Novu::builder()
->setSecurity(
'YOUR_SECRET_KEY_HERE'
)
->build();
$agentReplyPayloadDto = new Components\AgentReplyPayloadDto(
conversationId: '64f5a1c2e8b7a3d9f0c1b2a3',
integrationIdentifier: 'slack-support',
reply: new Components\MarkdownReplyContentDto(
markdown: '**Report ready.** Your weekly summary is attached.',
),
);
$response = $sdk->agents->sendReply(
agentId: 'support-agent',
agentReplyPayloadDto: $agentReplyPayloadDto
);
if ($response->object !== null) {
// handle response
}import novu_py
from novu_py import Novu
with Novu(
secret_key="YOUR_SECRET_KEY_HERE",
) as novu:
res = novu.agents.send_reply(agent_id="support-agent", agent_reply_payload_dto=novu_py.AgentReplyPayloadDto(
conversation_id="64f5a1c2e8b7a3d9f0c1b2a3",
integration_identifier="slack-support",
reply=novu_py.MarkdownReplyContentDto(
markdown="**Report ready.** Your weekly summary is attached.",
),
))
# Handle response
print(res)curl --request POST \
--url https://api.novu.co/v1/agents/{agentId}/reply \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"conversationId": "64f5a1c2e8b7a3d9f0c1b2a3",
"integrationIdentifier": "slack-support",
"reply": {
"markdown": "**Report ready.** Your weekly summary is attached."
}
}
'const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
conversationId: '64f5a1c2e8b7a3d9f0c1b2a3',
integrationIdentifier: 'slack-support',
reply: {markdown: '**Report ready.** Your weekly summary is attached.'}
})
};
fetch('https://api.novu.co/v1/agents/{agentId}/reply', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));HttpResponse<String> response = Unirest.post("https://api.novu.co/v1/agents/{agentId}/reply")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"conversationId\": \"64f5a1c2e8b7a3d9f0c1b2a3\",\n \"integrationIdentifier\": \"slack-support\",\n \"reply\": {\n \"markdown\": \"**Report ready.** Your weekly summary is attached.\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.novu.co/v1/agents/{agentId}/reply")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"conversationId\": \"64f5a1c2e8b7a3d9f0c1b2a3\",\n \"integrationIdentifier\": \"slack-support\",\n \"reply\": {\n \"markdown\": \"**Report ready.** Your weekly summary is attached.\"\n }\n}"
response = http.request(request)
puts response.read_body{
"messageId": "1712345678.123456",
"platformThreadId": "C0123456789"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"errors": {
"fieldName1": {
"messages": [
"Field is required",
"Must be a valid email address"
],
"value": "invalidEmail"
},
"fieldName2": {
"messages": [
"Must be at least 18 years old"
],
"value": 17
},
"fieldName3": {
"messages": [
"Must be a boolean value"
],
"value": true
},
"fieldName4": {
"messages": [
"Must be a valid object"
],
"value": {
"key": "value"
}
},
"fieldName5": {
"messages": [
"Field is missing"
],
"value": null
},
"fieldName6": {
"messages": [
"Undefined value"
]
}
},
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}"API rate limit exceeded"{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}"Please wait some time, then try again."