Skip to main content

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).
MethodPathPurpose
POST/v1/draftsSubmit a new draft from your agent
GET/v1/draftsList drafts
GET/v1/drafts/{id}Get one draft
PATCH/v1/drafts/{id}Edit a pending draft
POST/v1/drafts/{id}/approveApprove a pending draft (does not send)
POST/v1/drafts/{id}/rejectReject with optional reason
POST/v1/drafts/{id}/sendQueue delivery (works on pending or approved)
POST/v1/drafts/{id}/notifyRe-trigger the approval channel notification
GET/v1/drafts/{id}/versionsList 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

FieldTypeRequiredNotes
thread_idUUIDyesThe thread the draft replies to
identity_idUUIDyesThe sender identity
based_on_message_idUUIDyesThe last inbound message your agent read. Powers stale detection.
body_textstringone of body_text/body_htmlPlain-text body
body_htmlstringone of body_text/body_htmlHTML body
subject_overridestringnoDefaults to the thread subject
ccstring[]noCC recipients
bccstring[]noBCC recipients
rationalestringnoWhy your agent wrote this — surfaced to approvers
metadataobjectnoFree-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"
ParamTypeNotes
thread_idUUIDDrafts on a single thread
identity_idUUIDDrafts from a single identity
statusstringpending | approved | rejected | sending | sent | stale | failed
limit / offsetintPagination

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

StatusMeaning
pendingSubmitted, awaiting human approval
approvedHuman approved — call /send to deliver
sendingSend task is queued or in flight
sentDelivered via Postmark
staleA newer inbound arrived — blocked from sending
rejectedHuman rejected; thread reset to open
failedPostmark returned an error during send

Errors

HTTPerrorWhen
402plan_limit_reachedWorkspace is over its outbound quota
404not_foundDraft does not exist
409stale_draftNewer message on thread (response includes new_message_id)
422invalid_statusTried to send/edit a draft in a state that doesn’t allow it
429rate_limitedHit the per-draft notify throttle or workspace rate limit