Documentation

Complete reference for the Apruvly REST API.

Integrations

Getting Started

Everything you need to start automating approvals in minutes.

The Apruvly API is a RESTful JSON API. All requests must be authenticated with an API key. Responses are always JSON unless the response is empty (204).

Base URL
https://api.apruvly.io/api/v1
Quick Example

Create a single-step approval workflow with one curl command:

curl -X POST https://api.apruvly.io/api/v1/workflow \
  -H "Authorization: Bearer wf_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "object": {
    "title": "Q1 Marketing Budget",
    "description": "Marketing budget for Q1, $18k total"
  },
  "expires": "48h",
  "start": "step1",
  "workflow": {
    "step1": {
      "type": "email",
      "data": {
        "to": ["[email protected]"],
        "subject": "Q1 Marketing Budget – $18k",
        "body": "Please review the budget breakdown in the linked document and share your decision."
      }
    }
  }
}'

Authentication

All API requests require authentication via an API key.

Pass your API key in the Authorization header as a Bearer token. You can create and manage API keys from the dashboard.

Never expose your API key in client-side code or public repositories. Rotate it immediately if compromised.
Header format
Authorization: Bearer wf_YOUR_API_KEY

You can generate an API key from the API Keys section in the dashboard.

POST

Create Workflow

/api/v1/workflow

Creates a new approval workflow and immediately starts the first step.

Request Body
Field Type Required Description
object object Yes Metadata about the item being approved.
object.title string Yes Human-readable title for the workflow. Max 200 characters.
object.description string No Optional longer description of what is being approved.
object.links string[] No Optional list of URLs related to the approval (documents, files, etc.).
object.tags string[] No Optional list of tags for filtering and organization.
external_id string No Optional caller-supplied business correlation id (max 128 chars) — e.g. contract number, CRM ticket, order reference. Requires Growth plan or higher. Must be unique per account.
expires duration Yes How long the workflow stays open before expiring. Format: 30s, 15m, 2h, 7d, 2w.
start string Yes The key of the first step to execute (must match a key in workflow).
workflow map[string]Step Yes Map of step keys to Step objects defining the approval chain.
on map[string]Action[] No Map of lifecycle events to lists of actions (webhook, email, twilio-sms) fired automatically. Events: workflow_created, workflow_approved, workflow_rejected, workflow_expired, workflow_canceled, workflow_completed, step_approved, step_escalated, user_approval, user_rejection.
disable_email_fallback boolean No Opts out of the default email fallback. When omitted (recommended), the platform automatically delivers the approval request via email if an integration notification (Teams, Slack, WhatsApp) fails. Set to true to skip the fallback and accept the original failure.
debug boolean No Enables debug mode. All notifications (email, Teams, Slack, WhatsApp), webhooks, and escalations are suppressed — the workflow runs through its full logic without contacting any participants. Useful for testing the flow without triggering real communications.
Even in debug mode, credits are consumed normally. Debug mode only suppresses external communications — it does not affect billing.
Example Request
curl -X POST https://api.apruvly.io/api/v1/workflow \
  -H "Authorization: Bearer wf_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "object": {
    "title": "Contract Sign-off",
    "description": "Legal review for client contract #4821",
    "links": ["https://drive.example.com/contracts/4821"],
    "tags": ["legal", "contract"]
  },
  "external_id": "CONTRACT-2025-0042",
  "expires": "72h",
  "start": "legal-review",
  "workflow": {
    "legal-review": {
      "type": "email",
      "data": {
        "to": ["[email protected]"],
        "subject": "Approval Request: Contract #4821",
        "body": "Please review and approve contract #4821."
      },
      "approved": "ceo-approval",
      "escalation": {
        "type": "email",
        "after": "24h",
        "data": {
          "to": ["[email protected]"],
          "subject": "Escalation: Contract #4821 pending approval",
          "body": "Contract #4821 has not been reviewed. Please take action."
        }
      }
    },
    "ceo-approval": {
      "type": "email",
      "data": {
        "to": ["[email protected]"],
        "subject": "Final Approval: Contract #4821",
        "body": "Legal review is complete. Please provide final approval."
      }
    }
  }
}'
Example Response 202 Accepted
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "CONTRACT-2025-0042"
  }
}

Business correlation IDs (external_id)

Attach your own business reference (contract number, CRM ticket, deploy id) when creating a workflow, then poll status or cancel without storing the platform UUID. Requires the Growth plan or higher. Max 128 characters; allowed: letters, digits, and . _ : / -. Unique per account.

402 when your plan is below Growth. 409 when another workflow on your account already uses the same external_id.
Example Response 402 Payment Required
{
  "success": false,
  "message": "Your plan does not include external_id. Upgrade to Growth or higher to attach a business correlation id to workflows."
}
Create Workflow — Example Request
curl -X POST https://api.apruvly.io/api/v1/workflow \
  -H "Authorization: Bearer wf_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "object": {
    "title": "Contract Sign-off",
    "description": "Legal review for client contract #4821"
  },
  "external_id": "CONTRACT-2025-0042",
  "expires": "72h",
  "start": "legal-review",
  "workflow": {
    "legal-review": {
      "type": "email",
      "data": {
        "to": ["[email protected]"],
        "subject": "Approval Request: Contract #4821",
        "body": "Please review and approve contract #4821."
      }
    }
  }
}'
Example Response 202 Accepted
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "CONTRACT-2025-0042"
  }
}
Get by external_id — Example Request

Returns the same payload as GET /workflow/{id}, looked up by the external_id supplied at creation. Requires Growth plan or higher.

curl https://api.apruvly.io/api/v1/workflow/external/CONTRACT-2025-0042 \
  -H "Authorization: Bearer wf_YOUR_API_KEY"
Example Response 200 OK
{
  "success": true,
  "data": {
    "id":                  "550e8400-e29b-41d4-a716-446655440000",
    "externalId":          "CONTRACT-2025-0042",
    "status":              "pending",
    "currentStep":         "legal-review",
    "createdAt":           "2025-01-15T10:00:00Z",
    "expiresAt":           "2025-01-18T10:00:00Z",
    "decisions": []
  }
}
Cancel Workflow — Example Request
curl -X DELETE https://api.apruvly.io/api/v1/workflow/external/CONTRACT-2025-0042 \
  -H "Authorization: Bearer wf_YOUR_API_KEY"
Example Response 200 OK
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
List Workflows — Example Request
curl "https://api.apruvly.io/api/v1/workflows/search?external_id=CONTRACT-2025-0042" \
  -H "Authorization: Bearer wf_YOUR_API_KEY"
Example Response 200 OK
{
  "success": true,
  "data": [
    {
      "id":             "550e8400-e29b-41d4-a716-446655440000",
      "title":          "Contract Sign-off",
      "external_id":    "CONTRACT-2025-0042",
      "current_status": "pending",
      "current_step":   "legal-review",
      "created_at":     "2025-01-15T10:00:00Z",
      "expires_at":     "2025-01-18T10:00:00Z",
      "completed_at":   null
    }
  ]
}
POST

Validate Workflow

/api/v1/workflow/validate

Validates a workflow configuration without creating it. Useful for checking your payload before submitting.

This endpoint does not require authentication and does not consume credits. It only checks whether the configuration is valid.
Example Request
curl -X POST https://api.apruvly.io/api/v1/workflow/validate \
  -H "Authorization: Bearer wf_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "object": {
    "title": "Test Workflow"
  },
  "expires": "24h",
  "start": "step1",
  "workflow": {
    "step1": {
      "type": "email",
      "data": {
        "to": ["[email protected]"],
        "subject": "Test Approval",
        "body": "Please approve this test request."
      }
    }
  }
}'
Example Response 200 OK
{
  "success": true
}
GET

Get Workflow

/api/v1/workflow/{id}

Retrieves the full state of a workflow including its activity log and decision history.

Path Parameters
Field Type Description
id uuid The workflow ID returned when the workflow was created.
Example Request
curl https://api.apruvly.io/api/v1/workflow/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer wf_YOUR_API_KEY"
Example Response 200 OK
{
  "success": true,
  "data": {
    "id":                  "550e8400-e29b-41d4-a716-446655440000",
    "externalId":          "CONTRACT-2025-0042",
    "status":              "pending",
    "currentStep":         "ceo-approval",
    "currentEscalation":  0,
    "createdAt":           "2025-01-15T10:00:00Z",
    "expiresAt":           "2025-01-18T10:00:00Z",
    "nextTickAt":          "2025-01-16T10:00:00Z",
    "decisions": [
      {
        "user":        "[email protected]",
        "is_approved": true,
        "ip":          "203.0.113.42",
        "decisionAt":  "2025-01-15T14:22:00Z"
      }
    ]
  }
}
GET

List Workflows

/api/v1/workflows/search

Returns a paginated list of workflows belonging to the authenticated user.

Query Parameters
Field Type Description
status string Filter by status: pending, approved, rejected, expired, canceled
query string Full-text search on workflow title.
step string Filter by current active step name (e.g. legal-review).
from RFC3339 Filter workflows created on or after this timestamp (RFC 3339 format, e.g. 2025-01-01T00:00:00Z).
to RFC3339 Filter workflows created on or before this timestamp (RFC 3339 format, e.g. 2025-01-31T23:59:59Z).
tags string Filter by workflow tags (comma-separated).
external_id string Exact match on the caller-supplied business correlation id. Requires Growth plan or higher.
limit integer Maximum number of results to return (default 50, max 500). Max results per page (default 50, max 500).
Example Request
curl "https://api.apruvly.io/api/v1/workflows/search?status=pending&query=contract" \
  -H "Authorization: Bearer wf_YOUR_API_KEY"
Example Response 200 OK
{
  "success": true,
  "data": [
    {
      "id":             "550e8400-e29b-41d4-a716-446655440000",
      "title":          "Contract Sign-off",
      "external_id":    "CONTRACT-2025-0042",
      "current_status": "pending",
      "current_step":   "ceo-approval",
      "created_at":     "2025-01-15T10:00:00Z",
      "expires_at":     "2025-01-18T10:00:00Z",
      "completed_at":   null
    }
  ]
}
GET

Recent Workflows

/api/v1/workflows/recent

Returns the most recently created workflows for the authenticated user.

Example Request
curl "https://api.apruvly.io/api/v1/workflows/recent" \
  -H "Authorization: Bearer wf_YOUR_API_KEY"
GET

Billing

/api/v1/billing

Returns the authenticated user's subscription, plan, credit balance and usage summary.

Example Request
curl "https://api.apruvly.io/api/v1/billing" \
  -H "Authorization: Bearer wf_YOUR_API_KEY"
GET

Integrations

/api/v1/integrations

Manage third-party channel credentials (Slack, MS Teams, Twilio, etc.) via the REST API.

MethodEndpointDescription
GET/api/v1/integrationsList configuration status for every supported integration.
GET/api/v1/integrations/{integration}Return configuration status for one integration (secrets are never included).
PUT/api/v1/integrations/{integration}Create or replace credentials for an integration.
DELETE/api/v1/integrations/{integration}Remove stored credentials for an integration.
DELETE

Cancel Workflow

/api/v1/workflow/{id}

Cancels a pending workflow. Completed or expired workflows cannot be canceled.

Only workflows in pending status can be canceled. Returns 406 if the workflow is already completed, rejected or expired.

You can also cancel by the business id you set at creation time — see Business correlation IDs (external_id). Business correlation IDs (external_id)

Path Parameters
Field Type Description
id uuid The workflow ID returned when the workflow was created.
Example Request
curl -X DELETE https://api.apruvly.io/api/v1/workflow/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer wf_YOUR_API_KEY"
Example Response 200 OK
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
PUT

Approve / Reject

/api/v1/workflow/{id}/{challenge}/approve or /api/v1/workflow/{id}/{challenge}/reject

Records an approval or rejection decision for a workflow step. The action (approve or reject) is determined by the URL path suffix, not the request body. No request body is required.

The challenge UUID is unique per approver and is embedded in the notification email link. No authentication header is required for this endpoint - the challenge token is the proof of identity.
Path Parameters
Field Type Description
id uuid The workflow ID returned when the workflow was created.
challenge uuid The per-approver UUID challenge token included in the notification email link.
Example Request
# Approve
curl -X PUT https://api.apruvly.io/api/v1/workflow/550e8400-e29b-41d4-a716-446655440000/7a9f1c2d-4b3e-48f6-9d8c-1a2b3c4d5e6f/approve \
  -H "Authorization: Bearer wf_YOUR_API_KEY"

# Reject
curl -X PUT https://api.apruvly.io/api/v1/workflow/550e8400-e29b-41d4-a716-446655440000/7a9f1c2d-4b3e-48f6-9d8c-1a2b3c4d5e6f/reject \
  -H "Authorization: Bearer wf_YOUR_API_KEY"
Example Response 200 OK
{
  "success": true
}
CRUD

Directory

/api/v1/directory

Manage your address book of areas (teams) and people. Contacts feed email fallback at notification time and link recipients for cross-channel analytics.

Available on every paid plan, but the customer must enable the feature in the web console under /directory first. Requests against this endpoint return 402 (plan not eligible) or 409 (feature disabled) until both conditions are met.
The web console at /directory accepts CSV uploads as a friendlier shortcut to the bulk endpoints — handy for non-technical users. Server-side validation, plan caps and the all-or-nothing transaction are exactly the same as the JSON bulk endpoints.
Areas
MethodEndpointDescription
GET/api/v1/directory/areasList every active area.
POST/api/v1/directory/areasCreate a new area.
POST/api/v1/directory/areas/bulkCreate up to 100 areas in a single call.
PUT/api/v1/directory/areas/bulkRename many areas at once. Each item must include the area id.
GET/api/v1/directory/areas/{id}Fetch one area by id.
PUT/api/v1/directory/areas/{id}Rename an area.
DELETE/api/v1/directory/areas/{id}Delete an area (memberships are cascaded; people are kept).
People
MethodEndpointDescription
GET/api/v1/directory/people?q=&area=&page=Paginated list of people. Filters: q (text), area (uuid), page (1-based).
POST/api/v1/directory/peopleCreate a person with memberships and contacts.
POST/api/v1/directory/people/bulkCreate up to 1,000 people in a single call (with their memberships and contacts).
PUT/api/v1/directory/people/bulkUpdate many people at once (name, memberships and contacts). Each item must include the person id.
GET/api/v1/directory/people/{id}Fetch one person with full memberships and contacts.
PUT/api/v1/directory/people/{id}Replace display name, area memberships and contacts in one call.
DELETE/api/v1/directory/people/{id}Delete the person (contacts and memberships are cascaded).
Example Request — Create a person with memberships and contacts.
curl -X POST https://api.apruvly.io/api/v1/directory/people \
  -H "Authorization: Bearer wf_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Ana Souza",
    "area_ids": ["6c2b6e80-1f74-4cf2-9e2f-0d0e3b9b0001"],
    "contacts": [
      { "provider": "email", "address": "[email protected]", "label": "work" },
      { "provider": "twilio-sms", "address": "+5511999999999" }
    ]
  }'
Example Response 201 Created
{
  "success": true,
  "data": {
    "id": "a4d1f0c6-2d24-49b3-9e3c-7a2e6c4b0001",
    "display_name": "Ana Souza",
    "is_active": true,
    "area_ids": ["6c2b6e80-1f74-4cf2-9e2f-0d0e3b9b0001"],
    "contacts": [
      { "id": "…", "provider": "email", "address": "[email protected]", "label": "work" },
      { "id": "…", "provider": "twilio-sms", "address": "+5511999999999" }
    ]
  }
}
Bulk operations

Bulk endpoints accept an array of items under a single "items" field. Creation is capped per request (100 areas, 1,000 people) and updates are limited to twice the customer's plan size for the relevant resource.

Example Request — Create up to 1,000 people in a single call (with their memberships and contacts).
curl -X POST https://api.apruvly.io/api/v1/directory/people/bulk \
  -H "Authorization: Bearer wf_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "display_name": "Ana Souza",
        "area_ids": ["6c2b6e80-1f74-4cf2-9e2f-0d0e3b9b0001"],
        "contacts": [
          { "provider": "email", "address": "[email protected]", "label": "work" }
        ]
      },
      {
        "display_name": "Bruno Lima",
        "contacts": [
          { "provider": "twilio-sms", "address": "+5511988887777" }
        ]
      }
    ]
  }'
Example Response 201 Created
{
  "success": true,
  "data": {
    "count": 2,
    "items": [
      { "id": "a4d1f0c6-…", "display_name": "Ana Souza", "area_ids": ["6c2b6e80-…"], "contacts": ["…"] },
      { "id": "f10b2e8a-…", "display_name": "Bruno Lima", "area_ids": [], "contacts": ["…"] }
    ]
  }
}

The whole batch is processed together: if any single entry is invalid the request is rejected and nothing is saved.

Required scopes

Each endpoint requires the matching API key scope. Grant scopes under API Keys in the console.

ScopesDescription
api:directory:listAPI · Directory · list
api:directory:readAPI · Directory · read
api:directory:writeAPI · Directory · create / update
api:directory:bulkAPI · Directory · bulk operations
api:directory:deleteAPI · Directory · delete
Contact address formats

Each contact is a (provider, address) pair. Only providers configured under /integrations (plus email) are accepted.

ProviderAddress
email, slack, ms-teamsE-mail address
twilio-sms, twilio-whatsapp, whatsappE.164 phone (e.g. +5511999999999)
telegramNumeric chat id
discord17–20 digit snowflake

DingTalk group robots are not stored in the directory — they broadcast to a group, not an individual contact.

Errors
StatusDescription
400Missing required field or invalid contact address (e.g. malformed e-mail or phone).
402The current plan does not include the directory feature.
409Directory feature is disabled — enable it under /directory in the web console first.

Actions

Side effects the engine fires when workflow lifecycle events occur.

Use the `on` field to register actions for workflow lifecycle events. Each action has a type (webhook, email, or twilio-sms) and a data payload. When an event fires, all actions are executed in order.

Wrapped and flat forms

Each action is a JSON object that can be serialized in two forms. The wrapped form ({"type":"...", "data":{...}}) is the canonical form produced by the API and the only one accepted for twilio-sms. The flat form infers the type from the payload fields (url → webhook, to → email) and is kept for backward compatibility.

{
  "on": {
    "workflow_approved": [
      { "type": "webhook",    "data": { "url": "https://example.com/hooks/approved", "method": "POST" } },
      { "type": "email",      "data": { "to": ["[email protected]"], "subject": "Approved", "body": "Workflow ${id} is approved." } },
      { "type": "twilio-sms", "data": { "to": ["+14155552671"], "body": "Workflow ${id} approved" } }
    ]
  }
}
Lifecycle events
Event Description
workflow_created Fires when a workflow is successfully created.
workflow_approved Fires when the entire workflow reaches an approved final state.
workflow_rejected Fires when the entire workflow is rejected.
workflow_expired Fires when the workflow expires before reaching a final decision.
workflow_canceled Fires when the workflow is explicitly canceled via the API.
workflow_completed Fires when the workflow reaches any terminal state (approved, rejected, expired, or canceled).
step_approved Fires when an individual step is approved and the workflow advances to the next step.
step_escalated Fires when a step's escalation timer triggers and the escalation notification is sent.
user_approval Fires each time a single user casts an approval vote on a step.
user_rejection Fires each time a single user casts a rejection vote on a step.
WebhookAction type: "webhook"

Sends an HTTP request to an external endpoint. Private and loopback hosts are rejected at dispatch time.

Field Type Required Description
type string Yes Must be "webhook".
url string Yes The destination URL. Must be a valid URI.
method string No HTTP method: GET, POST, PUT, PATCH, or DELETE. Defaults to POST.
headers object No Custom HTTP headers sent with the request. Useful for authentication tokens or API keys.
body object|string No Request body. Supports variable interpolation with ${variable} syntax.
timeout duration No Request timeout duration (e.g. "30s", "5m"). Defaults to server configuration.
async boolean No If true, the request is sent asynchronously and does not block the workflow.
skipCertVerify boolean No If true, skips TLS certificate verification. Use only in development environments.
EmailAction type: "email"

Sends an email through your custom SMTP (when configured) or the system postman.

Field Type Required Description
to string[] Yes Recipient addresses. At least one is required.
subject string No Email subject line.
body string No Email body. Supports ${variable} interpolation.
cc string[] No Optional list of CC recipients.
bcc string[] No Optional list of BCC recipients.
html boolean No If true, the body is sent as HTML; otherwise as plain text.
SmsAction type: "twilio-sms"

Sends a fire-and-forget SMS through Twilio. Informational only — no short-code correlation or reply tracking.

Field Type Required Description
to string[] Yes Phone numbers in E.164 format (e.g. +14155552671). Duplicates and invalid entries are rejected.
body string Yes Message text (up to 1600 characters). Supports ${variable} interpolation.
Requires a plan with Twilio enabled and a tenant-level Twilio integration. Recipients on the opt-out list are skipped and the tenant daily quota is enforced. Must use the wrapped form ({"type":"twilio-sms", ...}) to avoid ambiguity with email.
Variable Interpolation

You can use the following variables inside webhook body values. They are automatically replaced at execution time.

Variable Description
${id} The unique workflow UUID.
${status} Current workflow status (e.g. approved, rejected, expired).
${current_step} The identifier of the currently active step.
${escalation_level} Current escalation depth level (0 if no escalation has occurred).
Example Request
{
  "on": {
    "workflow_approved": [
      {
        "type": "webhook",
        "data": {
          "url":     "https://api.yourapp.com/webhooks/approved",
          "method":  "POST",
          "headers": { "Authorization": "Bearer your-token" },
          "body": {
            "workflow_id":  "${id}",
            "status":       "${status}",
            "current_step": "${current_step}"
          },
          "timeout": "30s"
        }
      }
    ],
    "workflow_rejected": [
      {
        "type": "webhook",
        "data": {
          "url":   "https://api.yourapp.com/webhooks/rejected",
          "async": true
        }
      }
    ],
    "step_approved": [
      {
        "type": "webhook",
        "data": {
          "url":  "https://api.yourapp.com/webhooks/step-done",
          "body": {
            "step": "${current_step}"
          }
        }
      }
    ]
  }
}
Per-step actions

In addition to workflow-level events, each step can register its own actions that fire when THAT step is approved or rejected — independent of the final workflow outcome. Use `step.actions.approved` and `step.actions.rejected` to trigger webhooks or emails per decision.

{
  "workflow": {
    "review": {
      "type": "email",
      "data": {
        "to":      ["[email protected]"],
        "subject": "Please review",
        "body":    "Approve or reject ${id}"
      },
      "actions": {
        "approved": [
          {
            "type": "webhook",
            "data": {
              "url":    "https://example.com/hooks/review-approved",
              "method": "POST"
            }
          }
        ],
        "rejected": [
          {
            "type": "email",
            "data": {
              "to":      ["[email protected]"],
              "subject": "Review rejected",
              "body":    "${id} was rejected"
            }
          }
        ]
      }
    }
  }
}

Cost: each webhook counts as 1 credit; each recipient in email or twilio-sms actions counts as 1 credit per recipient. CC and BCC on email actions are not charged.

Credit cost

Each step costs 1 credit plus 1 per configured escalation level. External delivery actions add a single flat surcharge shared between webhook and twilio-sms.

Type Cost
webhook +1 per workflow (regardless of how many webhooks or events).
email No additional cost.
twilio-sms +1 per workflow (shared with webhook — max +1 total).
twilio-whatsapp +1 per workflow (shared with webhook — max +1 total).
discord +1 per workflow (regardless of how many webhooks or events).

Credits are debited once at workflow creation time. There is no per-dispatch charge.

Objects Reference

Detailed description of all request and response objects.

Step
Field Type Required Description
type string Yes Notification type. Currently supported: email, ms-teams, slack, whatsapp, telegram, dingtalk, discord, twilio-sms, twilio-whatsapp
data EmailData | MSTeamsData | SlackData | WhatsAppData | TelegramData | DingTalkData | DiscordData | TwilioSMSData | TwilioWhatsAppData Yes Notification payload. Shape depends on type.
minApprovals integer No Minimum number of approvals needed to advance. Default: 1.
minRejections integer No Minimum number of rejections needed to reject the workflow. Default: 1.
approved string No Key of the step to execute when this one is approved. Omit to end the workflow with approval status.
rejected string No Key of the step to execute when this one is rejected. Omit to end the workflow with rejected status.
escalation Escalation No Optional escalation config to trigger if approvers do not respond in time.
actions {approved, rejected} No Optional actions triggered when THIS step is approved or rejected, independent of the final workflow outcome. See the Actions section for supported shapes. (Per-step actions)
Channel delivery

Shared channel delivery posts one approval message to a team channel, group, or chat. Only users listed in allowList can click the interactive Approve/Reject buttons; the decision log records the clicker, not the channel ID.

Type Description
slack to: channel ID (C…). allowList: Slack user IDs (U…) and/or workspace emails. Requires Signing Secret.
ms-teams to: teamId/channelId. allowList: Azure AD object IDs and/or emails. Channel posts use a Microsoft Graph protected API — your tenant must grant Resource-Specific Consent (e.g. ChannelMessage.Send) on the Teams app or an admin-approved application permission. The Apruvly bot must be installed in the target team.
telegram to: negative group/channel chat ID (-100…). allowList: Telegram user IDs. Bot must be a group member.
discord to: channel snowflake (17–20 digits). allowList: Discord user snowflakes. Requires Interactions URL and Public Key.
dingtalk DingTalk always broadcasts to the robot group — delivery: "channel" is rejected.
CardOverride
Field Type Required Description
title string No Optional title override for the notification card or embed.
description string No Optional text shown in the notification body or embed description.
links string[] No Optional URLs rendered as action buttons (e.g. view request).
EmailData — used when type = email
Field Type Required Description
to string[] Yes List of approver email addresses for this step.
subject string Yes Subject line of the approval email.
body string Yes Plain-text or HTML message to the approver. object.title and object.description appear automatically in a summary card; approve/reject buttons are added for you.
cc string[] No Optional list of CC recipients.
bcc string[] No Optional list of BCC recipients.
isHtml boolean No Set to true if body contains HTML markup. Default: false.
Email body variables

These placeholders can be used in the subject and body fields.

Variable Description
${title} Workflow object title (also shown in the summary card).
${description} Workflow object description (also shown in the summary card).
${id} The unique workflow UUID.
${user_email} Recipient email address.
${user_name} Recipient display name, or the local part of the email when no display name is set.
SlackData — used when type = slack
Field Type Required Description
delivery string No Delivery mode: omit or "direct" (default) sends one message per recipient; "channel" posts a single message to a shared destination with an allow-list of deciders. Supported on slack, ms-teams, telegram, and discord.
allowList string[] No When delivery is "channel", list of emails and/or provider-native user IDs authorized to click Approve/Reject. Required in channel mode.
to string[] Yes Direct mode: workspace email addresses (one DM per recipient). Channel mode: Slack channel IDs starting with C.
card CardOverride No Optional per-step override for the notification title, description, and action links. When omitted, the workflow-level object fields are used.
For channel delivery, configure the Slack Signing Secret and Event Subscriptions webhook URL at /integrations. Invite the bot to the target channel.
MSTeamsData — used when type = ms-teams
Field Type Required Description
delivery string No Delivery mode: omit or "direct" (default) sends one message per recipient; "channel" posts a single message to a shared destination with an allow-list of deciders. Supported on slack, ms-teams, telegram, and discord.
allowList string[] No When delivery is "channel", list of emails and/or provider-native user IDs authorized to click Approve/Reject. Required in channel mode.
to string[] Yes Direct mode: Azure AD email addresses. Channel mode: teamId/channelId pairs (one message per entry).
card CardOverride No Optional per-step override for the notification title, description, and action links. When omitted, the workflow-level object fields are used.
Channel delivery posts Adaptive Cards via Microsoft Graph (POST /teams/{teamId}/channels/{channelId}/messages). This is a protected API: add Resource-Specific Consent on your Teams app manifest (e.g. ChannelMessage.Send.All) or obtain Microsoft approval for application permissions. Install the Apruvly app in the target team and set delivery to channel with an allowList.
WhatsAppData — used when type = whatsapp
Field Type Required Description
to string[] Yes List of approver phone numbers in E.164 format (e.g. +5511999999999).
card CardOverride No Optional per-step override for the notification title, description, and action links. When omitted, the workflow-level object fields are used.
Requires the WhatsApp (Meta) integration to be configured. Recipients are reached via E.164 phone numbers.
TelegramData — used when type = telegram
Field Type Required Description
delivery string No Delivery mode: omit or "direct" (default) sends one message per recipient; "channel" posts a single message to a shared destination with an allow-list of deciders. Supported on slack, ms-teams, telegram, and discord.
allowList string[] No When delivery is "channel", list of emails and/or provider-native user IDs authorized to click Approve/Reject. Required in channel mode.
to string[] Yes Direct mode: positive user chat IDs (e.g. 123456789). Channel mode: negative group or channel chat IDs (e.g. -1001234567890).
markdown boolean No When true, the message is sent using Telegram's MarkdownV2 format instead of HTML. The description field can contain MarkdownV2 syntax (e.g. *bold*, _italic_, `code`). Default: false.
card CardOverride No Optional per-step override for the notification title, description, and action links. When omitted, the workflow-level object fields are used.
Requires the Telegram integration to be configured in your account. Direct mode: recipients must have started a conversation with your bot. Channel mode: bot must be a member of the target group.
DiscordData — used when type = discord
Field Type Required Description
delivery string No Delivery mode: omit or "direct" (default) sends one message per recipient; "channel" posts a single message to a shared destination with an allow-list of deciders. Supported on slack, ms-teams, telegram, and discord.
allowList string[] No When delivery is "channel", list of emails and/or provider-native user IDs authorized to click Approve/Reject. Required in channel mode.
to string[] Yes Direct mode: Discord user snowflakes (17–20 digits). Channel mode: channel snowflake. The bot must share a server with DM recipients.
card CardOverride No Optional per-step override for the notification title, description, and action links. When omitted, the workflow-level object fields are used.
For channel delivery, configure the Discord Interactions URL and Public Key at /integrations. The bot must have access to the target channel.
DingTalkData — used when type = dingtalk
Field Type Required Description
to string[] No Optional mobile numbers (E.164) to @mention in the group ActionCard. DingTalk always posts to the configured robot group.
card CardOverride No Optional per-step override for the notification title, description, and action links. When omitted, the workflow-level object fields are used.
Requires the DingTalk integration to be configured. Messages are broadcast to the group bound to the robot webhook; optional to lists mobile numbers for @mentions.
TwilioSMSData — used when type = twilio-sms
Field Type Required Description
to string[] Yes Enter phone numbers in E.164 format (e.g. +5511999999999). Recipients must have consented to receive SMS.
card CardOverride No Optional per-step override for the notification title, description, and action links. When omitted, the workflow-level object fields are used.
Requires the Twilio integration to be configured in your account and the plan to allow Twilio. Approvers are reached via SMS using their E.164 number.
TwilioWhatsAppData — used when type = twilio-whatsapp
Field Type Required Description
to string[] Yes Phone number of the WhatsApp Sender registered with Twilio, in E.164 format (without the whatsapp: prefix).
card CardOverride No Optional per-step override for the notification title, description, and action links. When omitted, the workflow-level object fields are used.
Escalation
Field Type Required Description
type string Yes Notification type for the escalation. Currently supported: email, ms-teams, slack, whatsapp, telegram, dingtalk, discord, twilio-sms, twilio-whatsapp
after duration Yes How long to wait before triggering the escalation. Minimum: 5m.
data EmailData | TelegramData | … Yes Notification payload sent to the escalation recipients.
escalation Escalation No Optional nested escalation to trigger if this escalation level is also not responded to. Maximum depth: 5.
Decision — returned inside decisions[] in GET /workflow/{id}
Field Type Description
user string Email address of the approver who submitted the decision.
is_approved boolean true if approved, false if rejected.
ip string IP address from which the decision was submitted.
decisionAt RFC3339 RFC 3339 timestamp of when the decision was recorded.
Custom SMTP (account integration)

Business and Professional plans can send approval emails through your own SMTP server instead of the platform mailer. Configure it under Settings → Integrations → Custom SMTP (not in the workflow JSON).

  • Host, port, From address, and optional username/password. Use Send Test Email before saving production workflows.
  • Port 587 with STARTTLS (TLS switch off) or port 465 with implicit TLS (TLS switch on). Private and reserved IP targets are blocked.
  • Use a From address your server is allowed to relay (typically a mailbox on a domain you authenticate with SPF/DKIM).
  • Credentials are encrypted at rest (AES-256-GCM). Outbound connections require TLS 1.2+. Test emails are sent only to your account email.
  • Delivery, bounces, and SPF/DKIM/DMARC alignment are enforced by your SMTP server — Apruvly does not verify your sending domain.
Step Identifier Rules

Step keys (used in start, approved and rejected fields, and as workflow map keys) must match the following pattern - lowercase letter followed by lowercase letters, digits, hyphens or underscores:

^[a-z][a-z0-9_-]+$
Duration Format

Durations are expressed as a number followed by a unit suffix:

30s — 30 seconds 15m — 15 minutes 2h — 2 hours 7d — 7 days 2w — 2 weeks

Workflow expiration must be between 5 minutes (minimum) and 7 days (maximum).

Errors

The API uses standard HTTP status codes. Error responses include a JSON body with error and code fields.

Status Code Meaning
200 Request succeeded.
202 Accepted - workflow was created and is now running.
400 Bad Request - the request body or parameters are invalid.
401 Unauthorized - missing or invalid API key.
402 Payment Required - no active subscription, insufficient credits, or the current plan does not include the requested feature.
403 Forbidden - the API key lacks permission for this resource (e.g. you are not the workflow owner).
404 Not Found - the workflow or resource does not exist.
406 Not Acceptable - the operation cannot be performed in the current workflow state (e.g. cancelling an already-completed workflow).
429 Too Many Requests - rate limit exceeded.
500 Internal Server Error - something went wrong on our side.
Error Response Body
{
  "success": false,
  "message": "Invalid workflow ID.",
  "data": {
    "field": "id",
    "value": "not-a-valid-uuid"
  }
}

Common use cases with friendly descriptions to help you get started quickly.

01
Simple One-Step Approval

The most basic use case: a single person needs to approve something before you proceed. Ideal for expense approvals, content sign-offs, or any single-gatekeeper scenario.

single approver expense beginner
02
Multi-Level Approval Chain

Route an approval through multiple people in sequence - Legal → Manager → CEO. Each step must be approved before the next begins. Perfect for contract sign-offs or policy approvals.

multi-step contract sequential
03
Approval with Auto-Escalation

If the assigned approver does not respond within a set time window, automatically notify a fallback contact. Keeps processes moving without manual follow-ups.

escalation auto-escalate deadline
04
Group Vote (Quorum-Based Approval)

Require a minimum number of approvals from a group - any N out of M members can approve. Useful for board decisions, committee approvals, or peer reviews.

quorum committee group vote
05
Webhook Notification on Completion

Automatically POST to your backend when a workflow reaches a final decision. Trigger downstream automation without polling the API.

webhook automation integration
06
Validate Payload Before Sending

Check that your workflow configuration is valid before creating it. No authentication needed, no credits consumed - just a quick syntax and logic check.

validate no auth dry run
07
HR Employee Onboarding Chain

Route a new hire approval through HR, the hiring manager, and IT provisioning in sequence. Each team only receives the request after the previous step is complete.

hr onboarding sequential
08
Time-Off Request with Escalation

A single-step PTO request sent to the direct manager. If no response in 24 hours, HR is automatically notified. Webhook fires when the decision is made.

hr time-off simple
09
Production Deployment Approval

Gate a production release behind three sequential approvals: tech lead code review, QA sign-off, and ops authorization. A webhook triggers the CI/CD pipeline on approval.

devops deployment webhook
10
Budget Request with Dual Escalation

Two-level approval chain (manager then CFO) each with its own escalation path. If the manager goes silent, the VP steps in; if the CFO delays, the CEO is notified. Webhooks handle downstream finance automation.

budget escalation webhook multi-step
11
Slack Shared Channel Approval

Post a single approval message to a shared Slack channel. Configure delivery: "channel", the channel ID in to, and an allowList of users authorized to click Approve/Reject.

slack channel delivery allow-list

MCP Server

JSON-RPC 2.0 endpoint for AI agents and automation pipelines

The MCP endpoint follows the Model Context Protocol specification using JSON-RPC 2.0 over HTTP. It allows AI agents and automation tools to pause execution and request human approval before taking critical actions.

Endpoint
POST https://mcp.apruvly.io/mcp
Authentication Required
Authorization: Bearer wf_YOUR_API_KEY
Content-Type: application/json
Discovery Optional

MCP-aware clients can fetch a public discovery document at GET /.well-known/mcp to auto-detect the endpoint URL, supported transports and protocol versions, the auth scheme, and the catalog of available tools. No authentication required.

GET https://mcp.apruvly.io/.well-known/mcp
{
  "endpoint": "https://mcp.apruvly.io/mcp",
  "transports": ["streamable-http"],
  "protocolVersions": ["2025-03-26"],
  "auth": { "scheme": "bearer" },
  "tools": ["create_approval_request", "get_approval_status", "cancel_approval_request"]
}
How it works

Your AI agent calls create_approval_request with a title, description and the approvers' emails.

Apruvly sends an approval email to each approver containing approve/reject links.

The agent polls get_approval_status until the status changes to approved, rejected, or expired.

Integrations

Configuration examples for popular AI tools

Add the following MCP server entry to your project's .mcp.json file or global settings:

{
  "mcpServers": {
    "apruvly": {
      "type": "streamable-http",
      "url": "https://mcp.apruvly.io/mcp",
      "headers": {
        "Authorization": "Bearer wf_YOUR_API_KEY"
      }
    }
  }
}

Add the following to the mcpServers section in your Claude Desktop configuration file:

{
  "mcpServers": {
    "apruvly": {
      "url": "https://mcp.apruvly.io/mcp",
      "headers": {
        "Authorization": "Bearer wf_YOUR_API_KEY"
      }
    }
  }
}

Add the MCP server in Cursor settings under Features > MCP Servers, or add it to .cursor/mcp.json in your project:

{
  "mcpServers": {
    "apruvly": {
      "url": "https://mcp.apruvly.io/mcp",
      "headers": {
        "Authorization": "Bearer wf_YOUR_API_KEY"
      }
    }
  }
}

Add the following MCP server entry to your project's .mcp.json file or global settings:

// .vscode/mcp.json
{
  "servers": {
    "apruvly": {
      "type": "http",
      "url": "https://mcp.apruvly.io/mcp",
      "headers": {
        "Authorization": "Bearer wf_YOUR_API_KEY"
      }
    }
  }
}

Any MCP-compatible client can connect to the Apruvly endpoint using Streamable HTTP transport. Send JSON-RPC 2.0 requests directly:

curl -X POST https://mcp.apruvly.io/mcp \
  -H "Authorization: Bearer wf_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "clientInfo": {"name": "my-agent", "version": "1.0"},
      "capabilities": {}
    }
  }'

Tools

Available tools that AI agents can invoke via tools/call

create_approval_request

Creates a human approval request and returns a workflow_id that can be used to poll for the decision. The notification is sent over the channel chosen in `channel` (default email); non-email channels must already be configured for the account.

Parameters
Field Type Description
title string Required Short title (max 200 characters) describing what requires approval.
description string Optional Detailed description of the action requiring human approval (max 4000 characters).
channel string Optional Delivery channel. Defaults to "email". Allowed: email, slack, ms-teams, whatsapp, telegram, dingtalk, discord, twilio-sms, twilio-whatsapp. Non-email channels require the matching integration to be configured at /integrations.
recipients array Optional Uniform recipient list, one entry per approver. Use this to target any channel. The address format depends on the channel (email/slack/ms-teams = email address, whatsapp/twilio-sms/twilio-whatsapp = E.164 phone, telegram = numeric chat ID, discord = 17-20 digit user snowflake). Mutually exclusive with approvers and steps.
approvers array Optional Approvers (comma-separated emails) — {"email":"…","name":"…"}. Email-only shortcut; for other channels use recipients.
steps array Optional Optional sequential approval chain. Each step must be approved before the next runs; any rejection terminates the workflow. Requires the Growth, Business or Professional plan. Mutually exclusive with top-level recipients/approvers.
expires string Optional Duration string: "1h", "24h", "7d". Defaults to "24h". Minimum: 5 minutes — shorter values are rejected before any credit is debited.
links array Optional List of related URLs (documents, files, links).
tags array Optional List of labels for filtering and categorization.
idempotency_key string Optional Optional client-supplied identifier (max 128 chars) that makes the call idempotent. Replaying the same key within 24h returns the original workflow_id without recreating a workflow or charging credits. Equivalent to the Idempotency-Key HTTP header.
callback_url string Optional Optional https URL that receives a signed POST when the workflow reaches a terminal state. Useful for batch agents that cannot keep a polling or SSE connection open.
external_id string Optional Optional caller-supplied correlation id (max 128 chars) — e.g. agent run_id, upstream tool_call_id, internal ticket id. Requires Growth plan or higher. Persisted on the workflow row and echoed back from get_approval_status.
Request Body
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_approval_request",
    "arguments": {
      "title": "Deploy to production",
      "description": "Requesting approval to deploy v2.5.0 to production.",
      "channel": "slack",
      "recipients": [
        { "address": "[email protected]" }
      ],
      "expires": "24h",
      "idempotency_key": "agent-run-2026-05-03-00042",
      "external_id": "deploy-pipeline-7831"
    }
  }
}
Response 200
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "Approval request created…" },
      { "type": "text", "text": "{\"workflow_id\":\"550e8400-…\",\"status\":\"pending\",\"expires_at\":\"2026-05-04T12:00:00Z\",\"external_id\":\"deploy-pipeline-7831\"}" }
    ],
    "structuredContent": {
      "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "pending",
      "expires_at": "2026-05-04T12:00:00Z",
      "external_id": "deploy-pipeline-7831"
    }
  }
}

Tool responses also carry a structuredContent field that mirrors the JSON snapshot in the second content item — declared by the tool's outputSchema so MCP-aware clients can parse it tipado without scanning the text payload.

Example — multi-step + escalation

A two-step chain: the manager has 24h to approve before the request escalates to the director, and only after the manager approves does the VP get the second-level request.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_approval_request",
    "arguments": {
      "title": "Vendor Contract – Novatek",
      "channel": "email",
      "steps": [
        {
          "name": "manager",
          "recipients": [{ "address": "[email protected]" }],
          "escalate_after": "24h",
          "escalate_to": [{ "address": "[email protected]" }]
        },
        {
          "name": "vp",
          "recipients": [{ "address": "[email protected]" }]
        }
      ],
      "expires": "7d"
    }
  }
}
get_approval_status

Returns the current status and step decisions of an approval request. Poll this after create_approval_request until the status changes to approved, rejected, or expired.

Parameters
Field Type Description
workflow_id string Required The workflow ID returned when the workflow was created.
wait string Optional Optional long-poll duration (e.g. "30s", "60s", "1m"). When set, the call blocks until the workflow leaves the pending state or the duration elapses, whichever comes first. Capped at 60s. Reduces the number of polling round-trips.
Request Body
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_approval_status",
    "arguments": {
      "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
      "wait": "60s"
    }
  }
}
Response 200
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      { "type": "text", "text": "Approval status retrieved." },
      { "type": "text", "text": "{\"workflow_id\":\"550e8400-…\",\"status\":\"approved\",\"current_step\":\"approval\",\"external_id\":\"deploy-pipeline-7831\",\"decisions\":[…]}" }
    ],
    "structuredContent": {
      "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "approved",
      "current_step": "approval",
      "created_at": "2026-05-03T11:00:00Z",
      "expires_at": "2026-05-04T12:00:00Z",
      "external_id": "deploy-pipeline-7831",
      "decisions": [{ "user": "[email protected]", "is_approved": true, "decided_at": "2026-05-03T11:42:18Z", "step": "manager" }]
    }
  }
}

Each decision entry may include an optional step field naming the workflow step at decision time. Legacy single-step workflows may omit it.

cancel_approval_request

Cancels a pending approval request. Stops reminders, notifies approvers via every configured channel that the request was canceled, and marks the workflow as terminally canceled. Idempotent: a second call on an already-canceled workflow returns success with already_canceled=true.

Parameters
Field Type Description
workflow_id string Required The workflow ID returned when the workflow was created.
reason string Optional Optional free-text reason for the cancellation (max 500 chars). Recorded on the workflow activity log so the customer can audit why the agent withdrew the request.
Request Body
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "cancel_approval_request",
    "arguments": {
      "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
      "reason": "agent retry resolved the action without human approval"
    }
  }
}
Response 200
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      { "type": "text", "text": "Approval request canceled." },
      { "type": "text", "text": "{\"workflow_id\":\"550e8400-…\",\"status\":\"canceled\",\"already_canceled\":false,\"canceled_at\":\"2026-05-03T12:34:56Z\"}" }
    ],
    "structuredContent": {
      "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "canceled",
      "already_canceled": false,
      "canceled_at": "2026-05-03T12:34:56Z"
    }
  }
}

Protocol Reference

JSON-RPC 2.0 methods, handshake, and error codes

Methods
Method Description
initialize Initialize an MCP session. Negotiates protocol version (echoes the client's version when supported, downgrades to the highest supported version below it, or returns -32602 with the supported list when no compatible version exists) and returns server info plus capabilities.
ping Health check. Returns an empty result object.
tools/list Returns the list of available tools, their JSON Schema input definitions, and an outputSchema describing the structuredContent each tool returns.
tools/call Invokes a tool by name with the provided arguments.
notifications/* Methods starting with notifications/ are accepted with HTTP 202 and no response body, as required by the spec. notifications/cancelled is honored to abort an in-flight tools/call (see "Notifications" below); the rest are silently discarded.
Example — initialize
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "clientInfo": { "name": "my-agent", "version": "1.0" },
    "capabilities": {}
  }
}
Response 200
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "serverInfo": { "name": "apruvly", "version": "1.0.0" },
    "capabilities": { "tools": {} },
    "instructions": "Apruvly is a human-in-the-loop approval service…"
  }
}
Batch requests

The endpoint accepts a JSON array of requests in the body (max 50 items) and returns an array of responses. Items are processed sequentially; get_approval_status wait is capped at 5s inside a batch. notifications/cancelled entries in the same array are applied before any tools/call runs. Notifications produce no response slot; an array containing only notifications returns 202 Accepted with an empty body. An empty array is rejected with -32600 Invalid Request.

Request Body
[
  { "jsonrpc": "2.0", "id": 1, "method": "ping" },
  { "jsonrpc": "2.0", "id": 2, "method": "tools/call",
    "params": { "name": "get_approval_status",
      "arguments": { "workflow_id": "550e8400-e29b-41d4-a716-446655440000" } } }
]
Response 200
[
  { "jsonrpc": "2.0", "id": 1, "result": {} },
  { "jsonrpc": "2.0", "id": 2, "result": { "content": [ … ], "structuredContent": { … } } }
]
Notifications honored

notifications/cancelled aborts an in-flight tools/call so the agent can shorten a long-poll without waiting for its timer to fire. Match the requestId field to the id of the request you want to cancel; the running call returns its current snapshot immediately. Inside a batch, include the notification in the same array — it is honored before tools/call items run. To abort a long-poll already in flight on a separate connection, send notifications/cancelled as a sibling POST. Cancelling a request id that is not currently in flight is a silent no-op.

Example — notifications/cancelled
{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": {
    "requestId": 2,
    "reason": "agent changed its mind"
  }
}

Response 202 Accepted — empty body, per the JSON-RPC notification convention. The matching tools/call wakes from its long-poll and returns its current snapshot to the original caller.

JSON-RPC Error Codes

Application-level errors use code -32000 with a stable slug under data.code so agents can branch programmatically without parsing message text.

Code Meaning
-32700 Parse error — invalid JSON body.
-32600 Invalid Request — missing jsonrpc or method.
-32601 Method not found.
-32602 Invalid params — missing or malformed arguments. Also returned by initialize when the client requests a protocol version older than anything the server supports (the response carries data.supported with the list of acceptable versions).
-32603 Internal error.
-32000 Application error — branch on the stable slug under data.code (see table below).
data.code slugs (application errors)
data.code Meaning extra data fields
subscription_required The caller has no active subscription.
plan_feature_required The current plan does not include the requested feature (MCP access, multi-step approval, …). feature
integration_not_configured The chosen channel needs an integration that the tenant has not configured at /integrations. channel, integration
trial_expired Trial period has expired; upgrade to continue.
approver_limit_exceeded The workflow lists more approvers than the plan allows. max
escalation_limit_exceeded The escalation chain is deeper than the plan allows. max
concurrent_workflow_limit The plan's cap on simultaneously-pending workflows was reached. max
rate_limited Too many requests in the current window. Retry after retry_after_seconds. window, retry_after_seconds
insufficient_credits Account balance cannot cover the cost of the workflow.
workflow_not_found The workflow_id does not exist for the calling account. Cross-tenant lookups intentionally return the same slug to avoid leaking existence.
workflow_not_cancelable cancel_approval_request was called on a workflow already in a non-pending terminal state (approved, rejected, expired, error). status
Error Response Body
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32000,
    "message": "Rate limit exceeded, try again in a moment",
    "data": {
      "code": "rate_limited",
      "window": "minute",
      "retry_after_seconds": 60
    }
  }
}