Documentation Index
Fetch the complete documentation index at: https://docs.actionlayer.dev/llms.txt
Use this file to discover all available pages before exploring further.
A draft is an AI-generated reply awaiting human approval. Drafts are the central safety mechanism in ActionLayer: nothing sends without your sign-off (unless an identity has auto_approve_replies turned on).
| Method | Path | Purpose |
|---|
POST | /v1/drafts | Submit a new draft from your agent |
GET | /v1/drafts | List drafts |
GET | /v1/drafts/{id} | Get one draft |
PATCH | /v1/drafts/{id} | Edit a pending draft |
POST | /v1/drafts/{id}/approve | Approve a pending draft (does not send) |
POST | /v1/drafts/{id}/reject | Reject with optional reason |
POST | /v1/drafts/{id}/send | Queue delivery (works on pending or approved) |
POST | /v1/drafts/{id}/notify | Re-trigger the approval channel notification |
GET | /v1/drafts/{id}/versions | List edit history snapshots |
Submit a draft
curl -X POST https://api.actionlayer.dev/v1/drafts \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"thread_id": "THREAD_ID",
"identity_id": "IDENTITY_ID",
"based_on_message_id": "LAST_INBOUND_MESSAGE_ID",
"body_text": "Thanks for reaching out — I'll get back to you shortly.",
"rationale": "Acknowledging receipt while I check the calendar."
}'
Request body
| Field | Type | Required | Notes |
|---|
thread_id | UUID | yes | The thread the draft replies to |
identity_id | UUID | yes | The sender identity |
based_on_message_id | UUID | yes | The last inbound message your agent read. Powers stale detection. |
body_text | string | one of body_text/body_html | Plain-text body |
body_html | string | one of body_text/body_html | HTML body |
subject_override | string | no | Defaults to the thread subject |
cc | string[] | no | CC recipients |
bcc | string[] | no | BCC recipients |
rationale | string | no | Why your agent wrote this — surfaced to approvers |
metadata | object | no | Free-form JSON, max 8 KB serialized |
Response (201 Created)
{
"id": "draft_…",
"thread_id": "…",
"identity_id": "…",
"based_on_message_id": "…",
"status": "pending",
"subject": "Re: Pricing question",
"body_text": "Thanks for reaching out…",
"rationale": "Acknowledging while I check the calendar.",
"stale_warning": false,
"auto_approved": null,
"actions": {
"approve": "POST /v1/drafts/draft_…/approve",
"reject": "POST /v1/drafts/draft_…/reject",
"edit": "PATCH /v1/drafts/draft_…",
"send": "POST /v1/drafts/draft_…/send"
},
"created_at": "2026-04-30T14:25:00Z",
"updated_at": "2026-04-30T14:25:00Z"
}
Submission also fires the approval channel notification (Telegram/Slack/email) configured on the identity.
Stale detection
based_on_message_id is the keystone. ActionLayer compares it to the latest inbound message on the thread:
- Soft check on submission. If a newer inbound exists, the response sets
stale_warning: true but does not block submission. Your agent should re-read the thread and regenerate.
- Hard check on send. If a newer inbound has arrived between submission and
POST /drafts/{id}/send, the request returns 409 stale_draft, the draft status flips to stale, and a draft.stale webhook fires.
{
"error": "stale_draft",
"message": "Draft is stale — a newer message has arrived on this thread",
"new_message_id": "msg_…"
}
The right response to a 409 is to reject the stale draft and submit a new one based on the newer message ID.
Auto-approval
If the identity has auto_approve_replies: true, draft submission skips the approval step:
- The draft is approved automatically.
- The send task is queued immediately.
- The response includes
auto_approved: true.
- Plan limits and stale detection still run before delivery.
Auto-approval is always opt-in per identity. The default is false.
List drafts
curl 'https://api.actionlayer.dev/v1/drafts?status=pending&limit=20' \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY"
| Param | Type | Notes |
|---|
thread_id | UUID | Drafts on a single thread |
identity_id | UUID | Drafts from a single identity |
status | string | pending | approved | rejected | sending | sent | stale | failed |
limit / offset | int | Pagination |
Edit a draft
Only drafts with status: "pending" can be edited. Each edit snapshots the prior state to draft_versions (retrievable via GET /v1/drafts/{id}/versions).
curl -X PATCH https://api.actionlayer.dev/v1/drafts/DRAFT_ID \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"body_text": "Updated reply."}'
Editable fields: body_text, body_html, subject_override, cc, bcc, rationale.
Approve a draft
Approval marks the draft ready to send — it does not deliver. Call /send next.
curl -X POST https://api.actionlayer.dev/v1/drafts/DRAFT_ID/approve \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY"
Reject a draft
curl -X POST https://api.actionlayer.dev/v1/drafts/DRAFT_ID/reject \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Tone is off — too casual"}'
Rejection resets the thread status to open. The reason is stored on the draft and emitted in the draft.rejected webhook.
Send a draft
Sends the draft via Postmark. Works on pending or approved drafts (skipping approval is allowed but discouraged — see Auto-approval above for the supported way to do that).
curl -X POST https://api.actionlayer.dev/v1/drafts/DRAFT_ID/send \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY"
Returns 202:
{
"draft_id": "draft_…",
"thread_id": "…",
"status": "sending",
"queued_at": "2026-04-30T14:30:00Z"
}
The hard stale check runs here. A 409 stale_draft response means a newer inbound arrived since the draft was generated — the draft has been moved to stale and a draft.stale webhook fired.
Re-trigger the approval notification
Useful when the original Telegram/Slack/email notification didn’t deliver.
curl -X POST https://api.actionlayer.dev/v1/drafts/DRAFT_ID/notify \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY"
Rate-limited to 10/min per workspace. Throttled per-draft to avoid double-pings.
Draft statuses
| Status | Meaning |
|---|
pending | Submitted, awaiting human approval |
approved | Human approved — call /send to deliver |
sending | Send task is queued or in flight |
sent | Delivered via Postmark |
stale | A newer inbound arrived — blocked from sending |
rejected | Human rejected; thread reset to open |
failed | Postmark returned an error during send |
Errors
| HTTP | error | When |
|---|
| 402 | plan_limit_reached | Workspace is over its outbound quota |
| 404 | not_found | Draft does not exist |
| 409 | stale_draft | Newer message on thread (response includes new_message_id) |
| 422 | invalid_status | Tried to send/edit a draft in a state that doesn’t allow it |
| 429 | rate_limited | Hit the per-draft notify throttle or workspace rate limit |