Skip to content
Flywheel

Documentation

Base URL https://use-flywheel.com/api/v1. This page lists every endpoint that exists. Endpoints from the roadmap are not listed here and are not in the machine-readable spec either.

Quickstart

Two calls from nothing to a lead being answered. The first needs no credentials at all.

Before you can actually send

An account needs its own number. POST /api/v1/accounts/{id}/phone-number buys one, optionally in an area_code you name, falling back to nearby codes and then toll-free. It's idempotent — calling it twice returns the number you already have rather than billing you for a second one — and it answers 503 rather than a validation error when there's simply no inventory, so retrying is the right response.

1. Create an account
curl -sX POST https://use-flywheel.com/api/v1/accounts \
  -H 'Content-Type: application/json' \
  -d '{"account":{"name":"Northside Plumbing","time_zone":"America/Chicago","vertical":"plumbing","serves_minors":false}}'
2. Add a lead and start the conversation
curl -sX POST https://use-flywheel.com/api/v1/accounts/$ID/contacts \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"contact":{"first_name":"Alex","phone":"+13125550142"},"sms_consented":true,"start_conversation":true}'

Conventions

Authentication
Authorization: Bearer <token> on every request except GET /health and POST /accounts. The token from signup is shown exactly once and stored only as a digest — we cannot recover it for you.
Identifiers
Accounts and contacts are addressed by an opaque URL-safe slug. A sequential integer would advertise how many customers exist and invite guessing, so it isn't exposed. Conversations, messages, templates, flows, steps and webhook endpoints use integer ids.
Scope
The account is always resolved from the URL and then authorised against your key, never the other way round — so naming somebody else's account cannot widen your own scope. A mismatch is 403 forbidden, not 404.
Pagination
page (from 1) and per_page (default 50, maximum 200) on list endpoints. Ordered newest first.
Errors
Always {"error": {"code", "message", "details"}}. Branch on code; treat message as human-facing and liable to change.
unauthorized forbidden not_found validation_failed bad_request conflict send_failed

Endpoints

Health

  • GET /health Liveness. Unauthenticated. Checks Flywheel's own database, not just the process.

Accounts

  • POST /accounts Sign up. Unauthenticated. Returns the account and its first key.
  • GET /accounts/{account_id} Fetch an account.
  • PATCH /accounts/{account_id} Update an account.
  • POST /accounts/{account_id}/pause Stop all automation for this account.
  • POST /accounts/{account_id}/resume Start it again.

Message templates

  • GET /accounts/{account_id}/message-templates Your overrides plus every default you have not overridden.
  • POST /accounts/{account_id}/message-templates Override a template by key.
  • PATCH /accounts/{account_id}/message-templates/{id} Edit an override.
  • DELETE /accounts/{account_id}/message-templates/{id} Drop the override and fall back to the default.

Flows

  • GET /accounts/{account_id}/flows Effective flows, with their steps.
  • POST /accounts/{account_id}/flows Create a flow.
  • PATCH /accounts/{account_id}/flows/{id} Update a flow.
  • DELETE /accounts/{account_id}/flows/{id} Delete a flow.
  • GET /accounts/{account_id}/flows/{flow_id}/steps List the steps.
  • POST /accounts/{account_id}/flows/{flow_id}/steps Add a step.
  • PATCH /accounts/{account_id}/flows/{flow_id}/steps/{id} Update a step.
  • DELETE /accounts/{account_id}/flows/{flow_id}/steps/{id} Delete a step.

Contacts

  • GET /accounts/{account_id}/contacts Filter by pipeline_state, external_ref, or open=true.
  • POST /accounts/{account_id}/contacts Add a lead. start_conversation=true begins the sub-60-second reply.
  • GET /accounts/{account_id}/contacts/{id} Fetch a contact by slug.
  • PATCH /accounts/{account_id}/contacts/{id} Update a contact.

Conversations

  • GET /accounts/{account_id}/conversations Filter with needs_human=true.
  • GET /accounts/{account_id}/conversations/{id} With the full message history.
  • GET /conversations/{conversation_id}/messages Messages, oldest first.
  • POST /conversations/{conversation_id}/messages Take over. Pauses automation for 24 hours.
  • POST /conversations/{id}/resume End a takeover early.

Webhook endpoints

  • GET /accounts/{account_id}/webhook-endpoints List registered endpoints.
  • POST /accounts/{account_id}/webhook-endpoints Register one. https only. The secret is returned once.
  • DELETE /accounts/{account_id}/webhook-endpoints/{id} Remove one.

Building a flow

A flow is a list of steps. Each step sends a template, expects an answer of a given shape, writes the captured value somewhere on the contact, and decides where to go next. choices is an ordered array because texting “2” must select the second option; a JSON object would not preserve that order and the wrong answer would be captured.

POST /accounts/{account_id}/flows/{flow_id}/steps
{
  "step": {
    "position": 1,
    "template_key": "intake.problem",
    "expects": "choice",
    "writes_to": "custom_fields.issue",
    "choices": [
      { "value": "leak",    "synonyms": ["leaking", "slab leak", "burst", "water everywhere"] },
      { "value": "clog",    "synonyms": ["clogged", "backed up", "won't drain"] },
      { "value": "install", "synonyms": ["installation", "new water heater", "replace"] }
    ],
    "branch_on": { "leak": 2, "clog": 3, "install": 3 },
    "default_next_position": 4
  }
}

Webhooks

Not yet delivering

You can register endpoints today and they are stored, validated and given a signing secret, but Flywheel is not dispatching events to them yet. Register now if you like — they'll start receiving when delivery ships — but don't build a dependency on receiving anything this week.

When it lands, deliveries are signed HMAC-SHA256 with the secret returned once at registration, and endpoints are disabled automatically after 20 consecutive failures so an outage on your side doesn't build a backlog we later flush at you all at once.

Flywheel pushes outward and never calls back into a subscriber synchronously. Putting someone else's uptime on the critical path of a sub-60-second reply is the one thing that architecture is designed to prevent.

Reading this as an agent?

Skip the HTML. /llms.txt is a plain-text brief covering what the product does, what it guarantees, and what it does not do yet. /openapi.json is the full machine contract. Signup needs no credentials, so you can go from reading to a working account without a human involved.