All docs
5 min read Last updated:

Automations

Per-resource workflows - a trigger, optional conditions, and an ordered list of action steps. Automations hang off a parent resource, so the same eleven endpoints exist under forms, surveys, and funnels. Available on every plan, same token model as the rest of the API.

Base URL: https://formspring.io/api/v1

The paths below use /forms/{form}/...; substitute /surveys/{survey}/... or /funnels/{funnel}/... for the other products.

Method Path Ability
GET /forms/{form}/automations automations:read
POST /forms/{form}/automations automations:write
GET /forms/{form}/automations/{automation} automations:read
PUT /forms/{form}/automations/{automation} automations:write
DELETE /forms/{form}/automations/{automation} automations:write
POST /forms/{form}/automations/{automation}/enable automations:write
POST /forms/{form}/automations/{automation}/disable automations:write
POST /forms/{form}/automations/{automation}/run automations:run
GET /forms/{form}/automations/{automation}/runs automations:read
GET /forms/{form}/automations/{automation}/runs/{run} automations:read
POST /forms/{form}/automations/{automation}/runs/{run}/replay automations:run

The {form}/{survey}/{funnel} parameter takes the parent's public id (funnels also accept the slug). {automation} and {run} are numeric ids. An automation is only reachable under the parent it belongs to - mismatches 404.

List automations

http
GET /forms/{form}/automations

Returns every automation on the parent, each with its ordered steps array, newest first.

Create an automation

http
POST /forms/{form}/automations
Content-Type: application/json

{
  "name": "Route demo requests to sales",
  "trigger_type": "submission.created",
  "trigger_config": {},
  "is_active": true,
  "steps": [
    { "position": 1, "kind": "condition", "config": { "field": "topic", "operator": "equals", "value": "demo" } },
    { "position": 2, "kind": "action", "action_key": "send_email", "config": { "to": "sales@example.com" } }
  ]
}
  • name required (max 120 chars)
  • trigger_type required - e.g. submission.created, submission.updated, submission.tagged, submission.marked_spam, submission.starred, manual, schedule.cron, webhook.failed. Survey parents add survey_submission.created and survey.outcome_matched; funnel parents add funnel_submission.created. See Triggers →.
  • trigger_config - trigger-specific options (e.g. the cron expression)
  • is_active - defaults to true
  • steps - ordered list of condition / action steps; action_key picks the action (see Actions →)

Response 201: the automation with its steps.

Show / update an automation

http
GET /forms/{form}/automations/{automation}
PUT /forms/{form}/automations/{automation}

PUT accepts name, description, trigger_type, trigger_config, is_active, and run_order. Steps are managed through the dashboard builder. Returns the fresh automation with steps.

Enable / disable

http
POST /forms/{form}/automations/{automation}/enable
POST /forms/{form}/automations/{automation}/disable

Flips is_active. Disabled automations keep their run history but stop firing on triggers.

Delete an automation

http
DELETE /forms/{form}/automations/{automation}

Response 200: { "ok": true }. Deletion is processed asynchronously, consistent with every destructive action on the platform.

Run manually

http
POST /forms/{form}/automations/{automation}/run

Queues a manual run regardless of the configured trigger. Requires the dedicated automations:run ability (separate from automations:write so editing tokens can't fire side effects).

Response 202: { "run_id": 481 } - the run executes asynchronously; poll the run endpoint for status.

List runs

http
GET /forms/{form}/automations/{automation}/runs?status=failed&per_page=25

Cursor-paginated run history, newest first. Filters: status (pending, running, succeeded, failed, skipped), from / to date bounds, per_page (1-100, default 25).

Response 200:

json
{
  "data": [
    { "id": 481, "status": "failed", "started_at": "2026-06-09T08:00:01+00:00", "finished_at": "2026-06-09T08:00:04+00:00" }
  ],
  "next_cursor": "eyJpZCI6NDgxfQ",
  "prev_cursor": null
}

Pass next_cursor back as ?cursor= to fetch the next page.

Show a run

http
GET /forms/{form}/automations/{automation}/runs/{run}

Returns the run's scalar fields plus an ordered, typed list of step runs:

json
{
  "id": 481,
  "automation_id": 12,
  "submission_id": 90341,
  "status": "failed",
  "started_at": "2026-06-09T08:00:01+00:00",
  "finished_at": "2026-06-09T08:00:04+00:00",
  "error": "send_email: SMTP 550 mailbox unavailable",
  "created_at": "2026-06-09T08:00:00+00:00",
  "steps": [
    { "position": 1, "kind": "condition", "status": "succeeded", "started_at": "2026-06-09T08:00:01+00:00", "finished_at": "2026-06-09T08:00:01+00:00", "duration_ms": 12, "error": null },
    { "position": 2, "kind": "action", "status": "failed", "started_at": "2026-06-09T08:00:01+00:00", "finished_at": "2026-06-09T08:00:04+00:00", "duration_ms": 2710, "error": "send_email: SMTP 550 mailbox unavailable" }
  ],
  "failed_step_position": 2
}

Replay a run

http
POST /forms/{form}/automations/{automation}/runs/{run}/replay

Dispatches a fresh run against the same submission (when the original run had one). Requires automations:run.

Response 202: { "run_id": 502 } - the new run records which run it replays.

Errors

Status When
402 Team's plan doesn't include API access
403 Token missing required ability
404 Parent or automation doesn't belong to the current team, or the automation isn't owned by that parent
422 Validation failed (missing name or trigger_type, bad status filter, …)

See also