Agents
An agent is a job, not a prompt: it wakes on a schedule, a webhook, a record change, a connected app or a message, works inside the limits you set, and stops to ask when it should. The API gives you the same controls as Agent Studio.
#Draft an agent
Describe the job in plain language. The Agent Builder drafts its operating plan — the same builder and checks as Agent Studio. Takes 10–90 seconds.
curl -X POST "https://app.attria.io/api/v1/agents" \
-H "Authorization: Bearer $ATTRIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Lead follow-up",
"message": "When a new Lead object is created, email them a friendly intro within 5 minutes and notify me.",
"auto": true
}'const res = await fetch("https://app.attria.io/api/v1/agents", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ATTRIA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
name: "Lead follow-up",
message: "When a new Lead object is created, email them a friendly intro within 5 minutes and notify me.",
auto: true,
}),
});
const build = await res.json();import os, uuid, requests
res = requests.post(
"https://app.attria.io/api/v1/agents",
headers={
"Authorization": f"Bearer {os.environ['ATTRIA_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Lead follow-up",
"message": "When a new Lead object is created, email them a friendly intro within 5 minutes and notify me.",
"auto": True,
},
timeout=900, # a turn that waits can take minutes
)
build = res.json()The answer is an agent_build: status: "built" with the full agent, or needs_input with questions. The draft exists either way — answer questions with PATCH /v1/agents/{id} and a message, which is also how you change the plan later.
#Turn it on
A new agent is a draft. Activating compiles the plan, sets up every wake-up — schedules, webhooks, connected-app triggers — and publishes one immutable version.
curl -X POST "https://app.attria.io/api/v1/agents/7c1e9a2b-4d3c-4e5f-8a6b-9c0d1e2f3a4b/activate" \
-H "Authorization: Bearer $ATTRIA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"const res = await fetch("https://app.attria.io/api/v1/agents/7c1e9a2b-4d3c-4e5f-8a6b-9c0d1e2f3a4b/activate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ATTRIA_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
},
});
const activation = await res.json();import os, uuid, requests
res = requests.post(
"https://app.attria.io/api/v1/agents/7c1e9a2b-4d3c-4e5f-8a6b-9c0d1e2f3a4b/activate",
headers={
"Authorization": f"Bearer {os.environ['ATTRIA_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
timeout=30,
)
activation = res.json()It answers with the version and its deployments. A webhook deployment carries its endpoint: POST JSON there to wake the agent — and keep it private. If the plan isn't ready you get 422 with error.details.issues, the same list GET /v1/agents/{id} shows under validation.
Changes edit the draft. An active agent keeps running its activated version until you activate again. POST /v1/agents/{id}/pause stops every wake-up; runs in flight finish.
#Talk to it
Send a message — or a JSON payload as an event — exactly as someone typing to it in Attria. The agent decides whether to answer from what it knows or to run its plan.
curl -X POST "https://app.attria.io/api/v1/agents/7c1e9a2b-4d3c-4e5f-8a6b-9c0d1e2f3a4b/runs" \
-H "Authorization: Bearer $ATTRIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"message": "Summarise yesterday'\''s new leads and email me the top 3.",
"background": true
}'const res = await fetch("https://app.attria.io/api/v1/agents/7c1e9a2b-4d3c-4e5f-8a6b-9c0d1e2f3a4b/runs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ATTRIA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
message: "Summarise yesterday's new leads and email me the top 3.",
background: true,
}),
});
const invocation = await res.json();import os, uuid, requests
res = requests.post(
"https://app.attria.io/api/v1/agents/7c1e9a2b-4d3c-4e5f-8a6b-9c0d1e2f3a4b/runs",
headers={
"Authorization": f"Bearer {os.environ['ATTRIA_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"message": "Summarise yesterday's new leads and email me the top 3.",
"background": True,
},
timeout=900, # a turn that waits can take minutes
)
invocation = res.json()The answer is an agent_invocation: its answer, a status (completed, waiting, failed, cancelled), the steps it took, the run_id when it ran, and a thread_id — pass that back to continue the conversation.
| Send | You get |
|---|---|
| nothing (default) | The finished invocation. |
stream: true | A started event with the thread_id, then one result or error. |
background: true | 202 with status: "running". Repeat the same request with the same Idempotency-Key: 202 while it runs, then the finished invocation. |
#Runs and steps
Every time an agent wakes — a person, a schedule, a webhook, a record change, a connected app — it makes a run. List them with GET /v1/agents/{id}/runs (filter by status or thread_id); GET /v1/runs/{id} has every step in order with its input, result and error, any delegated tasks, its approvals and what it cost.
#Approvals
Depending on its autonomy, an agent asks before it sends an email, spends money, or changes or deletes records. Each approval shows a summary, the exact input that will run and its risk.
curl "https://app.attria.io/api/v1/approvals?status=pending" \
-H "Authorization: Bearer $ATTRIA_API_KEY"const res = await fetch("https://app.attria.io/api/v1/approvals?status=pending", {
headers: {
Authorization: `Bearer ${process.env.ATTRIA_API_KEY}`,
},
});
const page = await res.json();import os, requests
res = requests.get(
"https://app.attria.io/api/v1/approvals?status=pending",
headers={
"Authorization": f"Bearer {os.environ['ATTRIA_API_KEY']}",
},
timeout=30,
)
page = res.json()curl -X POST "https://app.attria.io/api/v1/approvals/b2a1c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
-H "Authorization: Bearer $ATTRIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"decision": "approve"
}'const res = await fetch("https://app.attria.io/api/v1/approvals/b2a1c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ATTRIA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
decision: "approve",
}),
});
const decision = await res.json();import os, uuid, requests
res = requests.post(
"https://app.attria.io/api/v1/approvals/b2a1c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
headers={
"Authorization": f"Bearer {os.environ['ATTRIA_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"decision": "approve",
},
timeout=900, # a turn that waits can take minutes
)
decision = res.json()approve runs the action exactly as shown and resumes the run waiting on it; reject means it never runs. always: true with an approve lets this agent take this kind of action without asking again, where can_always_allow is true. A decided approval answers 409.
Grants and limits are set per agent in the app; see Agents for how they bound what it can do.
#Endpoints
- GET
/v1/agentsList Agents - POST
/v1/agentsCreate an Agent from a description - GET
/v1/agents/{id}Get an Agent - PATCH
/v1/agents/{id}Change an Agent - POST
/v1/agents/{id}/activateTurn an Agent on - POST
/v1/agents/{id}/pausePause an Agent - GET
/v1/agents/{id}/runsList an Agent's runs - POST
/v1/agents/{id}/runsTalk to an Agent (run it) - GET
/v1/runs/{id}Get a run with its steps - GET
/v1/approvalsList approvals - POST
/v1/approvals/{id}Approve or reject an Agent action