Triggers, API keys and the run API
LiveStart an agent's work on a schedule, from a webhook, when something happens, or from your own code with a project API key.
Most work reaches an agent when someone types a message. You can also start work without anyone typing:
- Schedules run an agent at set times, such as every morning at 9.
- Webhooks run an agent when your website, online store, form tool or other software sends data to a web address.
- Events run an agent when something happens in your workspace, such as a lead being saved. See Start an agent when something happens.
- The run API lets your own code send an agent a message and read the reply, using a project API key.
A few rules apply to all of them:
- Only the published version runs. Draft changes are not used until you publish them. If an agent was never published, or is paused, nothing runs.
- Every run is a normal task. It shows up in Tasks with its steps, approvals and rupee cost. The channel shows as Schedule, Webhook, Event or API, and the task says which trigger started it.
- Your approval settings still apply. If replies need approval, a triggered reply waits in Tasks like any other.
- Platform limits still apply. If AI work is paused on the platform, or the daily spending limit is reached, new runs are refused and schedules are skipped. See Limits and costs.
Who can do what
| Action | Owner | Admin | Builder | Viewer |
|---|---|---|---|---|
| See an agent's triggers | Yes | Yes | Yes | Yes |
| Add, turn on or off, and delete triggers | Yes | Yes | Yes | No |
| Create a new webhook secret | Yes | Yes | Yes | No |
| See, create and revoke API keys | Yes | Yes | No | No |
Schedules
Add a schedule
- Open the agent. On the Build tab, scroll to Triggers.
- Select Add trigger, then Schedule.
- Give it a name, such as "Morning order summary".
- Choose how often it runs:
- Every hour, at a set number of minutes past the hour.
- Every day, at a set time.
- Every week, on the days you pick, at a set time.
- Every month, on a day from 1 to 31, at a set time. In shorter months, a schedule for the 29th, 30th or 31st runs on the last day of the month.
- Write what the agent should do each time, such as "Check today's new leads and email the team a short summary."
- Select Add schedule.
All times are Indian Standard Time (IST). The trigger shows its last run, how that run went, and when it runs next.
How schedules run
- Dhanur AI checks for due schedules every minute, so a run can start up to a minute after its time.
- Each run starts a new task with the message you wrote.
- If the previous run is still going when the next one is due, the next one is skipped. Runs never pile up.
- If a run can't start, it is marked Skipped with the reason, for example "This agent is paused." The schedule then moves on to its next time, and the skipped run is not made up later.
- If Dhanur AI itself was down at a scheduled time, the schedule runs once when it is back, then keeps to its times.
- Turn off stops a schedule without deleting it. When you turn it back on, it runs at its next time from then.
Webhooks
Add a webhook
- Open the agent. On the Build tab, scroll to Triggers.
- Select Add trigger, then Webhook.
- Under What sends it?, keep Your own website, form tool or app. For Razorpay, Shopify or WooCommerce, pick the store instead: see Payments and orders from your store.
- Give it a name, such as "New website order".
- Optionally, write the message the agent gets, using fields from your data. See Message templates.
- Select Create webhook.
You then see the webhook's web address and its secret.
The secret is shown only once. Dhanur AI keeps only a scrambled copy (a hash), so it can't show it again. (A store's webhook is the exception: the store signs each call with the secret, so we also keep it encrypted to check the signature. It is still never shown again.) Copy it into the system that will call the webhook. If you lose it, open the trigger's menu and choose New secret. The old secret stops working straight away.
Call a webhook
Send a POST request with a JSON body to the web address. Put the secret in the X-Dhanur-Secret header.
curl -X POST "https://engine.dhanurai.com/v1/hooks/trg_..." \
-H "X-Dhanur-Secret: $DHANUR_WEBHOOK_SECRET" \
-H "Content-Type: application/json" \
-d '{"customer": {"name": "Ravi", "phone": "98765 43210"}, "order": {"id": 1042}}'
If your tool can't add headers, add the secret to the address instead: ...?token=<secret>. This is less safe, because web addresses can be saved in logs along the way.
Dhanur AI answers 202 Accepted with the new task's id:
{"task_id": "tsk_...", "status": "queued", "duplicate": false}
The agent then works on the task in the background.
- Body size: up to 64 KB of JSON. An empty body counts as
{}. - Retries: if your system might send the same event twice, add an
Idempotency-Keyheader, such as your order id. A repeat with the same key returns the first task, with"duplicate": true, instead of starting another. - Rate limit: up to 30 calls a minute for each webhook. Calls with a wrong secret don't count.
- Following up: to check the result from your code, read the task with an API key. See Read a task.
Message templates
Without a template, the agent gets all the data you sent, as JSON, with a first line naming the webhook.
With a template, the agent gets your text, with each field in double curly brackets filled in from the data:
| In the template | Filled in with |
|---|---|
{{name}} |
The name field |
{{customer.phone}} |
The phone field inside customer |
{{items.0.sku}} |
The sku field of the first item in the items list |
For example, the template New order {{order.id}} from {{customer.name}}. Send them a thank-you note. with the body above gives the agent "New order 1042 from Ravi. Send them a thank-you note."
- A field that isn't in the data is left empty.
- Lists and objects are inserted as JSON;
trueandfalsestay as they are. - Values are inserted as plain text. Curly brackets inside your data are never filled in.
- Messages longer than 8,000 characters are cut short. If the finished message is empty, the call is refused.
Webhook errors
| Status | Meaning |
|---|---|
400 |
The body isn't JSON, or the template produced an empty message. |
401 |
The secret is missing or wrong, or the webhook doesn't exist. |
409 |
The webhook is turned off, or the agent isn't live or is paused. |
413 |
The body is larger than 64 KB. |
429 |
Too many calls this minute. Wait for the number of seconds in the Retry-After header. |
503 |
AI work is paused on the platform, or today's spending limit was reached. Try again later. |
When a call is refused because the agent can't take work, AI work is paused, or the message came out empty, the trigger shows Refused with the reason, so you can see what went wrong in the app.
API keys
An API key lets your own software use the run API. Each key belongs to one project and only reaches the agents in that project.
Create a key
You need the Owner or Admin role.
- Open Integrations in the sidebar.
- Under API keys, select Create key.
- Name it after the system that will use it, such as "Website order form".
- Select Create key, then copy the key.
A key is shown only once. Dhanur AI stores only a hash. Keys start with
dhk_, and the list shows the first few characters so you can tell them apart. If you lose a key, create a new one and revoke the old one.
Treat a key like a password:
- Keep it on your server, for example in an environment variable called
DHANUR_API_KEY. - Never put it in a web page, a mobile app or a chat message.
- Create a separate key for each system, so you can revoke one without affecting the others.
The list shows who created each key, when, and when it was last used. A project can have up to 20 active keys.
Revoke a key
Select Revoke next to the key and confirm. Requests with that key are refused straight away. Revoking can't be undone. Creating and revoking keys is recorded in the workspace's audit log.
The run API
The API uses JSON over HTTPS. The Build tab of each agent shows its address and ready-made examples. For dhanurai.com, the address starts with:
https://engine.dhanurai.com/v1/api
Send your key with every request:
Authorization: Bearer dhk_...
The API is meant to be called from your server. Browsers can't call it directly from your web pages.
Give an agent a task
POST /v1/api/agents/{agent_id}/tasks
| Field | Type | Meaning |
|---|---|---|
message |
text, up to 4,000 characters | What the agent should work on. Required, unless the message only carries files. |
wait |
true or false | Wait for the agent to finish, for up to 30 seconds. Default: false. |
task_id |
text | Continue a conversation that the API started earlier, instead of starting a new task. |
attachment_ids |
list of ids | Photos and PDFs to send with the message, up to 3. See Send photos and PDFs. |
You find the agent's id on its Build tab, under Use from your code.
- With
"wait": true, and when the agent finishes in time, the answer is200 OKwith the finished task. - Otherwise the answer is
202 Acceptedwith the task so far. Read it again later with itsid.
curl -X POST "https://engine.dhanurai.com/v1/api/agents/agt_.../tasks" \
-H "Authorization: Bearer $DHANUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Hi, what are your delivery charges?", "wait": true}'
In Python:
import os
import requests
res = requests.post(
"https://engine.dhanurai.com/v1/api/agents/agt_.../tasks",
headers={"Authorization": f"Bearer {os.environ['DHANUR_API_KEY']}"},
json={"message": "Hi, what are your delivery charges?", "wait": True},
timeout=45,
)
res.raise_for_status()
print(res.json()["reply"])
Send photos and PDFs
A message can carry up to 3 photos (JPEG, PNG, WebP or GIF) or PDFs. Upload each file first, then list the ids in attachment_ids. The agent must have Let people send files turned on (it is on unless someone turned it off); in a workforce, agents with it off see only the file names.
POST /v1/api/attachments
Send the file as multipart/form-data, in a field called file. The answer is 201 Created:
curl -X POST "https://engine.dhanurai.com/v1/api/attachments" \
-H "Authorization: Bearer $DHANUR_API_KEY" \
-F "file=@invoice-1042.pdf"
{
"id": "att_...",
"name": "invoice-1042.pdf",
"mime": "application/pdf",
"kind": "pdf",
"size_bytes": 182311,
"width": 0,
"height": 0,
"pages": 2,
"task_id": null,
"sent": false,
"uploaded_by": "api",
"created_at": "2026-09-17T09:29:58Z",
"expires_at": "2026-09-18T09:29:58Z"
}
Then send it with a message:
curl -X POST "https://engine.dhanurai.com/v1/api/agents/agt_.../tasks" \
-H "Authorization: Bearer $DHANUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Please check this invoice", "attachment_ids": ["att_..."], "wait": true}'
- What's checked: the file's type is read from the file itself, not from its name. Each file can be up to 4 MB, and a PDF up to 20 pages. Password-protected PDFs and HEIC photos are refused.
- Who can use a file: only the key that uploaded it can send it, and only once. Each message's files are listed under
attachmentsin the task'smessages. - Download:
GET /v1/api/attachments/{id}/filereturns the file itself. A key can download the files it uploaded and the files of tasks it can read. - Remove before sending:
DELETE /v1/api/attachments/{id}removes a file that hasn't been sent (204 No Content). Sent files stay with their task. - How long files are kept: a file that isn't sent is deleted after a day; a sent file is deleted 90 days after it was sent.
expires_atshows when. - Limits: up to 30 uploads a minute for each key, and up to 300 files a day for the workspace. Reading files uses more AI than text, and it is counted in the task's cost.
Give a workforce a task
POST /v1/api/workforces/{workforce_id}/tasks
This works like the agent endpoint above, with the same fields, answers and limits. The message goes to the live workforce: its conditions pick the first agent, and agents may hand the conversation to each other before one replies. A follow-up with task_id goes to the agent that has the conversation now. You find the workforce's id in its Run tab.
curl -X POST "https://engine.dhanurai.com/v1/api/workforces/wfc_.../tasks" \
-H "Authorization: Bearer $DHANUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Please send me a copy of invoice 1042", "wait": true}'
The task you get back also has workforce_id, and its agent_id is the agent that has the conversation now. A workforce's schedules and webhooks are added in its Run tab and work like an agent's.
Read a task
GET /v1/api/tasks/{task_id}
A key can read the tasks in its project that were started through the API, by a schedule or by a webhook. It can't read test chats or conversations from the app.
Both endpoints return a task like this:
{
"id": "tsk_...",
"agent_id": "agt_...",
"status": "done",
"finished": true,
"reply": "Delivery is free on orders above Rs 499...",
"needs_approval": false,
"escalation": null,
"error": null,
"channel": "api",
"messages": [
{"role": "user", "content": "Hi, what are your delivery charges?", "at": "...", "attachments": []},
{"role": "assistant", "content": "Delivery is free on orders above Rs 499...", "at": "...", "attachments": []}
],
"created_at": "2026-09-17T09:30:00Z",
"updated_at": "2026-09-17T09:30:04Z"
}
| Field | Meaning |
|---|---|
status |
queued (waiting to start), running, needs_approval, escalated (handed to your team), done or failed. |
finished |
true once the agent has stopped working on the latest message. Poll until it is true. |
reply |
The agent's latest answer, once it has been sent. It stays empty while a reply waits for approval. |
needs_approval |
true while an action, such as the reply, waits for someone in Tasks. |
escalation, error |
Why the agent handed over, or why the run failed. |
messages[].attachments |
The photos and PDFs sent with each message: id, name, mime, kind (image or pdf), size_bytes, width, height and pages. |
Times are in UTC.
Limits and errors
Each key can start up to 30 tasks a minute, upload up to 30 files a minute, and read tasks and files up to 240 times a minute.
| Status | Meaning |
|---|---|
400 |
The message is empty, the agent doesn't take files, more than 3 files were listed, or a file can't be read (for example a PDF with too many pages). |
401 |
The key is missing, wrong or revoked. |
404 |
The agent, workforce, task or file doesn't exist, isn't in the key's project, or has expired. |
409 |
The agent or workforce isn't live or is paused, it is still working on the last message in this task, or a file was already sent. |
411, 413 |
An upload without its size, or a file over 4 MB. |
415 |
The file isn't a photo (JPEG, PNG, WebP or GIF) or a PDF. |
422 |
The request body isn't valid, for example an unknown field, no message and no files, or a message over 4,000 characters. |
429 |
Too many requests this minute, or the workspace's files for today. Wait for the number of seconds in the Retry-After header when there is one. |
503 |
AI work is paused on the platform, or today's spending limit was reached. Try again later. |
Most error responses look like {"detail": "This agent is paused. Resume it in the agent builder to take new work."}.
Not available yet
Planned. Running your own tools through the API, and a JavaScript SDK. See API. The same API keys also work with our MCP server.
Last updated 24 September 2026
Something unclear or wrong? Tell us