{
  "openapi": "3.1.0",
  "info": {
    "title": "Zuops API",
    "version": "2.11.0",
    "description": "Zuops is an AI creative + CRM platform. This API drives ad/image/video generation and the full sub-account CRM: leads, messaging, bookings, members, smart forms and automations.\n\nAuthenticate with `Authorization: Bearer zu-...`. Every response is `{ success, data, error }`.\nGeneration is asynchronous: POST /v1/jobs returns a job_id, then poll GET /v1/jobs/{id} or register a webhook.\nSend an `Idempotency-Key` header on POST /v1/jobs so retries never double-charge.\n\nGeneration actions: copy-ad, remix-ad, generate-ad, edit-ad, ad-sizes, generate-ad-copy, score-ad, focus-group, edit-video, nano-banana-2, nano-banana-2-1, nano-banana-pro, gpt-image-2, gpt-image-2-5, seedance-2-5, kling-3-0, kling-4-0, veo-3-1, seedance-2-0, happy-horse, hailuo-3, wan-3-0, wan-3-0-prime, flux-3, gemini-omni-1-1, zuops-motion.\n\n### Authentication\nSend your key as `Authorization: Bearer zu-...`. Keys are created in Settings → API. A key can be limited to scopes (`leads:read`, `jobs:write`, ..., or the coarse `read` / `write` / `admin`); a key with no scopes at all is denied everything. A key can also be restricted to one sub-account, and given a monthly credit budget. A key restricted to one sub-account can only reach that sub-account's records: account-level endpoints return 403, records of another sub-account return 404, and it only sees the generation jobs it submitted itself. Deleting things and anything that sends lead data outside the account (alert recipients, outbound webhooks, job webhooks) needs the `admin` scope.\n\n### Rate limits\nEvery response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining`. On 429 a `Retry-After` header (and `retry_after_seconds` in the body) tells you how long to wait. The limit is per key, per minute: 120 by default, adjustable from 1 to 600 in Settings → API.\n\n### Idempotency\nEvery write (POST, PUT, PATCH, DELETE) accepts `Idempotency-Key: <your-unique-string>`. Replaying the same key with the same body within 24 hours returns the original response instead of doing the work twice (no second charge, no second message); the same key with a different body answers 409. On POST /v1/jobs (and a job retry) the credit charge is tied to the key as well, so two requests sent at the same moment with one key are charged once: the one that arrives while the first is still running answers 409 `idempotency_in_progress` with nothing charged; send it again with the same key a moment later to get the first job. 429 and 503 answers are not stored, so retrying them with the same key does the work. Use a fresh key per logical operation, not per retry.\n\n### Confirmation tokens (402)\nTwo calls need a second, confirming request: DELETE /v1/workspaces/{id}, and POST /v1/meta/publish with status ACTIVE. The first attempt answers 402 with `{ confirmation_required: true, confirmation_token, action, projected_cost_cents, expires_at }`. Re-send the identical request with the header `X-Zuops-Confirmation: <confirmation_token>` within 5 minutes to proceed. Tokens are single-use and tied to the key and the exact body. (A 402 without `confirmation_required` means not enough credits, or the key's monthly credit budget is used up.)\n\n### Pagination\nList endpoints accept `limit` (default 50, max 200) and return the newest rows first. GET /v1/messages is the one endpoint with a cursor: pass `cursor` (or `since`) and follow `next_cursor` to walk forward without duplicates. GET /v1/jobs always returns the 20 most recent jobs.\n\n### Sub-account scoping\nAccount-level keys pass `workspace_id` (query for GET, body for POST / PUT) on any endpoint marked sub-account aware. Keys restricted to one sub-account can omit it: their sub-account is applied automatically, and a different workspace_id in the request is ignored. Such a key sees only its own sub-account (GET /v1/workspaces lists just that one), only the brand linked to it, and only the jobs it submitted; account-level endpoints answer 403. When its sub-account is deleted the key stops working (403) until the sub-account is restored. A deleted sub-account answers 404 to every key.\n\n### Messaging safety limits\nTo protect number reputation, each sub-account can message at most 300 unique contacts per UTC day (plus per-minute burst and daily segment caps). Replying to a contact you already messaged that day does not consume another contact slot. Messaging endpoints return X-Zuops-Contacts-Limit, X-Zuops-Contacts-Used, X-Zuops-Contacts-Remaining and X-Zuops-Contacts-Reset; over the cap sends answer 429 with `code: \"sms_limit\"` and a `limit` object. Check the current state any time with GET /v1/messages/limits. Need a higher ceiling? Request a limit increase from Settings → Messaging and we'll review it.\n\n### Responses and errors\nSuccess is `{ success: true, data, error: null }`. Errors are `{ success: false, data: null, error: string, details?: object }` with a matching HTTP status; scope, rate-limit and budget errors put a machine-readable code in `error` (`scope_denied`, `rate_limit_exceeded`, `credit_limit_exceeded`) and the explanation in `message`. Branch on the status and the code, not on message text.\n\n### URLs you send\nImages, videos, audio, logos and webhook targets are fetched by Zuops or by the AI model it runs, so they must be public http(s) addresses. Localhost, private network ranges and link-local addresses are refused with 400, redirects are re-checked, and downloads stop at 25 MB. Inline `data:` images are accepted where an image URL is.\n\n### Credits and refunds\nThe price of a job is fixed when it is accepted and returned as `credits_used`; it is the same price the app charges for the same settings. If pricing cannot be worked out the call answers 503 and nothing is charged. Failed and cancelled jobs are refunded to the credit bucket they came from, and the refund also frees the key's monthly credit budget.\n",
    "contact": {
      "name": "Zuops",
      "url": "https://zuops.com/api"
    }
  },
  "servers": [
    {
      "url": "https://api.zuops.com/functions/v1/api-gateway",
      "description": "Production"
    }
  ],
  "x-webhooks": {
    "accountEvents": [
      {
        "event": "job.completed",
        "description": "A generation job finished successfully. Payload includes the job id, action and output URLs."
      },
      {
        "event": "job.failed",
        "description": "A generation job failed. Payload includes the job id and the failure reason."
      },
      {
        "event": "job.cancelled",
        "description": "A generation job was cancelled before it finished."
      },
      {
        "event": "meta.ad.published",
        "description": "An ad was published to Meta."
      },
      {
        "event": "meta.ad.paused",
        "description": "An ad was paused on Meta, manually or by an automation."
      },
      {
        "event": "automation.triggered",
        "description": "An ad automation (auto-pause, budget scaler, …) took an action."
      },
      {
        "event": "lead.created",
        "description": "A new CRM lead was captured from any source."
      },
      {
        "event": "phone.purchased",
        "description": "A phone number was provisioned for a sub-account."
      }
    ],
    "workspaceEvents": [
      {
        "event": "lead.created",
        "description": "A lead was captured for this sub-account. Payload is a full lead snapshot including custom form fields."
      },
      {
        "event": "lead.updated",
        "description": "The lead's status or pipeline stage changed (other field edits do not fire it). Payload carries previous_status, status, previous_stage_key and stage_key."
      },
      {
        "event": "lead.qualified",
        "description": "The lead was marked qualified."
      },
      {
        "event": "appointment.booked",
        "description": "An appointment was booked for the lead."
      },
      {
        "event": "appointment.rescheduled",
        "description": "An appointment moved to a new time, in the CRM calendar, through the API, by an inbound webhook or by the customer on their appointment page. Payload carries booking_id, lead_id, the new starts_at / ends_at, timezone, host, calendar, meeting and moved_via (app, api, inbound_webhook or customer)."
      },
      {
        "event": "appointment.cancelled",
        "description": "A booked appointment was cancelled."
      },
      {
        "event": "message.received",
        "description": "An inbound SMS or email arrived on this sub-account's number or inbox. Payload carries message_id, lead_id, channel, direction, body, from/to and media_urls, and `transport` (imessage or mac_sms) when it came in on one of the sub-account's iMessage Macs."
      },
      {
        "event": "message.sent",
        "description": "An outbound SMS or email was handed to the carrier or mail provider — manual, automated or API-sent. Texts carry `transport`: sms from the business number, imessage or mac_sms when one of the sub-account's Macs sent it."
      },
      {
        "event": "message.failed",
        "description": "An outbound message could not be sent. Payload includes the error and, when the contact-safety cap was hit, a `limit` object."
      },
      {
        "event": "call.completed",
        "description": "An AI receptionist call ended (once per call, after its recap is written; practice calls from Settings excluded). Payload carries call_id, lead_id (null for a caller with no lead), from/to, status, duration_sec, outcome, summary, booking_id and started_at / ended_at. The transcript and recording: GET /v1/calls/{call_id}."
      },
      {
        "event": "deal.updated",
        "description": "A lead was marked won, or a won lead's amount or plan changed later (an amount added from Ad Insights, a plan edited). Payload carries deal_type (one_time or plan), deal_value_cents (the first payment), deal_plan_cents, deal_plan_interval, deal_monthly_cents and deal_expected_total_cents, plus the lead snapshot. Every lead event's snapshot carries the same deal fields."
      }
    ],
    "signature": {
      "header": "X-Webhook-Signature",
      "format": "sha256=<hex hmac>",
      "description": "Each delivery is signed with HMAC-SHA256 over the exact raw request body using the secret returned when you registered the webhook. Compute the HMAC over the raw bytes (before JSON parsing) and compare in constant time. `X-Webhook-Event` carries the event name, `X-Webhook-Delivery` a unique delivery id you can use to de-duplicate retries. Failed deliveries are retried with exponential backoff for up to 24 hours. Job webhooks, and sub-account webhooks made before October 21, 2026, also carry the same values as `X-Zuops-Signature`, `X-Zuops-Event` and `X-Zuops-Delivery`."
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Jobs"
    },
    {
      "name": "Brands"
    },
    {
      "name": "Webhooks"
    },
    {
      "name": "Meta"
    },
    {
      "name": "Ad automations"
    },
    {
      "name": "CRM leads"
    },
    {
      "name": "Messaging"
    },
    {
      "name": "Bookings"
    },
    {
      "name": "Members"
    },
    {
      "name": "Smart forms"
    },
    {
      "name": "CRM automations"
    },
    {
      "name": "Workspaces"
    },
    {
      "name": "Setup"
    },
    {
      "name": "Offers"
    },
    {
      "name": "Calendars & hours"
    },
    {
      "name": "Team busy times"
    },
    {
      "name": "Pipeline & fields"
    },
    {
      "name": "Routing & alerts"
    },
    {
      "name": "Lead sources & webhooks"
    },
    {
      "name": "AI agent"
    },
    {
      "name": "Dashboard"
    },
    {
      "name": "Inbox"
    },
    {
      "name": "Calls"
    },
    {
      "name": "AI Brain"
    },
    {
      "name": "Invoices & reviews"
    },
    {
      "name": "Email marketing"
    },
    {
      "name": "Contracts"
    },
    {
      "name": "Ads & Google"
    },
    {
      "name": "Support"
    },
    {
      "name": "Account"
    }
  ],
  "paths": {
    "/v1/jobs": {
      "post": {
        "operationId": "submit_job",
        "tags": [
          "Jobs"
        ],
        "summary": "Submit a generation job",
        "description": "One endpoint for every generator. Pick an `action` from the actions list. Returns a job_id, the credits charged (`credits_used`) and a poll_url; poll GET /v1/jobs/{id} or register a webhook. Send an `Idempotency-Key` header to make retries safe. Every URL you send (images, videos, webhook) must be a public http(s) address; fields that start with an underscore are ignored. A failed job is refunded automatically.\n\nRequired scope: `jobs:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Any unique string; a replay within 24 hours returns the first job instead of charging again. A replay sent while the first request is still running answers 409 `idempotency_in_progress` (nothing charged); send it again with the same key a moment later.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "description": "Generator action, e.g. seedance-2-5, gpt-image-2, copy-ad."
                  },
                  "prompt": {
                    "type": "string",
                    "description": "Prompt for generative actions."
                  },
                  "aspect_ratio": {
                    "type": "string",
                    "description": "Model-specific, e.g. 9:16."
                  },
                  "duration": {
                    "type": "number",
                    "description": "Video length in seconds (see the model table). Must be a number."
                  },
                  "resolution": {
                    "type": "string",
                    "description": "Model-specific, e.g. 720p / 1080p / 4k."
                  },
                  "reference_image_url": {
                    "type": "string",
                    "description": "Starting frame or source image. Some models use reference_image_urls or first_frame_url instead (see the model table)."
                  },
                  "variations": {
                    "type": "number",
                    "description": "Image actions only: how many images (1-10; edit-ad and GPT Image models 1-5)."
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "Optional per-job callback URL. Must be a public http(s) address."
                  }
                },
                "required": [
                  "action"
                ],
                "additionalProperties": true
              },
              "example": {
                "action": "seedance-2-5",
                "prompt": "Slow push-in on a matte black skincare bottle",
                "aspect_ratio": "9:16",
                "duration": 8,
                "resolution": "1080p"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "job_id": "uuid",
                    "status": "queued",
                    "action": "seedance-2-5",
                    "variations": 1,
                    "credits_used": 64,
                    "webhook_url": null,
                    "poll_url": "https://api.zuops.com/functions/v1/api-gateway/v1/jobs/uuid"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "operationId": "list_jobs",
        "tags": [
          "Jobs"
        ],
        "summary": "List the 20 most recent API jobs",
        "description": "Each job in the same shape as GET /v1/jobs/{job_id}. A key restricted to one sub-account only sees the jobs it submitted itself.\n\nRequired scope: `jobs:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "jobs": [
                      {
                        "id": "uuid",
                        "status": "processing",
                        "tool": "seedance-2-5",
                        "created_at": "2026-09-30T15:00:00Z",
                        "completed_at": null,
                        "error": null
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/jobs/{job_id}": {
      "get": {
        "operationId": "job_status",
        "tags": [
          "Jobs"
        ],
        "summary": "Get job status and result",
        "description": "status is queued, processing, completed, failed or cancelled. Image jobs return images[], video jobs video_url; ad-sizes adds sizes[], focus-group focus_group and edit-video video_edit. A failed job's error is plain, customer-safe text (the same text job webhooks carry) and its credits are refunded automatically.\n\nRequired scope: `jobs:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "Job id returned by POST /v1/jobs.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "uuid",
                    "status": "completed",
                    "tool": "nano-banana-pro",
                    "created_at": "2026-09-30T15:00:00Z",
                    "completed_at": "2026-09-30T15:00:41Z",
                    "error": null,
                    "images": [
                      {
                        "url": "https://api.zuops.com/functions/v1/api-serve-image?path=...",
                        "index": 0
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/jobs/{job_id}/cancel": {
      "post": {
        "operationId": "cancel_job",
        "tags": [
          "Jobs"
        ],
        "summary": "Cancel a queued or running job and refund its credits",
        "description": "A job that already finished (completed, failed or cancelled) answers 400. edit-ad, ad-sizes and edit-video jobs can't be stopped once they are being made (409): they finish in a few minutes, and anything that fails is refunded. The refund also goes back to the key's monthly credit budget.\n\nRequired scope: `jobs:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "Job id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "job_id": "uuid",
                    "status": "cancelled",
                    "credits_refunded": true,
                    "credits_refunded_amount": 64
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/jobs/{job_id}/retry": {
      "post": {
        "operationId": "retry_job",
        "tags": [
          "Jobs"
        ],
        "summary": "Retry a failed job",
        "description": "Re-submits the original request as a new job, charged like a new submit. Any field in the body overrides the original value. A job that is still queued, pending, processing or paused answers 409 (nothing charged): wait for it to finish, or cancel it first. Retries sent with the same `Idempotency-Key` start one new job.\n\nRequired scope: `jobs:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "Job id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/brands": {
      "get": {
        "operationId": "list_brands",
        "tags": [
          "Brands"
        ],
        "summary": "List brands",
        "description": "Required scope: `brands:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_brand",
        "tags": [
          "Brands"
        ],
        "summary": "Create a brand",
        "description": "A brand name must be unique across your account (409 otherwise).\n\nRequired scope: `brands:write`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Max 100."
                  },
                  "industry": {
                    "type": "string",
                    "description": "e.g. Roofing."
                  },
                  "website": {
                    "type": "string",
                    "description": "Website URL."
                  },
                  "logo_url": {
                    "type": "string",
                    "description": "Public logo image URL."
                  },
                  "primary_color": {
                    "type": "string",
                    "description": "#rrggbb."
                  },
                  "secondary_color": {
                    "type": "string",
                    "description": "#rrggbb."
                  },
                  "accent_color": {
                    "type": "string",
                    "description": "#rrggbb."
                  },
                  "tagline": {
                    "type": "string",
                    "description": "Short tagline."
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": true
              },
              "example": {
                "name": "Acme Roofing",
                "industry": "Roofing",
                "website": "https://acmeroofing.com",
                "primary_color": "#0f766e",
                "secondary_color": "#111827",
                "accent_color": "#f59e0b",
                "tagline": "Roofs done right."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "uuid",
                    "name": "Acme Roofing",
                    "industry": "Roofing",
                    "logo_url": null,
                    "website": "https://acmeroofing.com",
                    "primary_color": "#0f766e",
                    "secondary_color": "#111827",
                    "accent_color": "#f59e0b",
                    "tagline": "Roofs done right."
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/brands/{brand_id}": {
      "patch": {
        "operationId": "update_brand",
        "tags": [
          "Brands"
        ],
        "summary": "Update a brand",
        "description": "Send only what changes.\n\nRequired scope: `brands:write`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "description": "Brand id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Max 100."
                  },
                  "industry": {
                    "type": "string",
                    "description": "Industry."
                  },
                  "website": {
                    "type": "string",
                    "description": "Website URL."
                  },
                  "logo_url": {
                    "type": "string",
                    "description": "Logo URL."
                  },
                  "primary_color": {
                    "type": "string",
                    "description": "#rrggbb."
                  },
                  "secondary_color": {
                    "type": "string",
                    "description": "#rrggbb."
                  },
                  "accent_color": {
                    "type": "string",
                    "description": "#rrggbb."
                  },
                  "tagline": {
                    "type": "string",
                    "description": "Tagline."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "tagline": "Roofs done right, on time."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_brand",
        "tags": [
          "Brands"
        ],
        "summary": "Delete a brand",
        "description": "Required scope: `brands:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "description": "Brand id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/brands/{brand_id}/ads": {
      "get": {
        "operationId": "list_brand_ads",
        "tags": [
          "Brands"
        ],
        "summary": "List saved ads for a brand",
        "description": "Required scope: `brands:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "description": "Brand id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100. Default 50.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/brands/{brand_id}/assets": {
      "get": {
        "operationId": "list_brand_assets",
        "tags": [
          "Brands"
        ],
        "summary": "List a brand's assets (logos, product shots)",
        "description": "Required scope: `brands:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "description": "Brand id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/brand-assets/{asset_id}": {
      "delete": {
        "operationId": "delete_brand_asset",
        "tags": [
          "Brands"
        ],
        "summary": "Delete a brand asset",
        "description": "Required scope: `brands:write`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "asset_id",
            "in": "path",
            "required": true,
            "description": "Asset id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/saved-ads": {
      "post": {
        "operationId": "create_saved_ad",
        "tags": [
          "Brands"
        ],
        "summary": "Save an ad to a brand library",
        "description": "Required scope: `saved_ads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "image_url": {
                    "type": "string",
                    "description": "Public image URL."
                  },
                  "ad_type": {
                    "type": "string",
                    "description": "What kind of ad, e.g. image or video."
                  },
                  "industry": {
                    "type": "string",
                    "description": "Industry it is for."
                  },
                  "name": {
                    "type": "string",
                    "description": "Name in the library."
                  },
                  "brand_id": {
                    "type": "string",
                    "description": "Brand to file it under (GET /v1/brands)."
                  },
                  "offer": {
                    "type": "string",
                    "description": "The offer it shows."
                  },
                  "business_name": {
                    "type": "string",
                    "description": "Business name on the ad."
                  }
                },
                "required": [
                  "image_url",
                  "ad_type",
                  "industry"
                ],
                "additionalProperties": true
              },
              "example": {
                "brand_id": "uuid",
                "image_url": "https://example.com/ad.png",
                "ad_type": "image",
                "industry": "Roofing",
                "name": "Winter promo"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "uuid",
                    "name": "Winter promo",
                    "image_url": "https://api.zuops.com/functions/v1/api-serve-image?path=...",
                    "ad_type": "image",
                    "industry": "Roofing",
                    "brand_id": "uuid",
                    "created_at": "2026-09-30T15:00:00Z"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/saved-ads/{ad_id}": {
      "delete": {
        "operationId": "delete_saved_ad",
        "tags": [
          "Brands"
        ],
        "summary": "Delete a saved ad",
        "description": "Required scope: `saved_ads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ad_id",
            "in": "path",
            "required": true,
            "description": "Saved ad id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "operationId": "create_webhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Register a webhook (signing secret returned once)",
        "description": "Required scope: `webhooks:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": true
              },
              "example": {
                "url": "https://example.com/hooks/zuops",
                "events": [
                  "job.completed",
                  "job.failed"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "operationId": "list_webhooks",
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhooks",
        "description": "Required scope: `webhooks:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhooks/{webhook_id}": {
      "delete": {
        "operationId": "delete_webhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook",
        "description": "Required scope: `webhooks:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "description": "Webhook id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/meta/accounts": {
      "get": {
        "operationId": "meta_accounts",
        "tags": [
          "Meta"
        ],
        "summary": "List connected Meta ad accounts",
        "description": "Every ad account your Facebook logins reach: the main login's, then each other Facebook profile connected in Settings → Meta, each account once, with profile_id and profile_name (the profile that reads it). Ads, summary and ad performance read each account with whichever profile can, so an account under a second profile works the same.\n\nRequired scope: `meta:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/meta/pages": {
      "get": {
        "operationId": "meta_pages",
        "tags": [
          "Meta"
        ],
        "summary": "List connected Facebook pages",
        "description": "Required scope: `meta:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/meta/ads": {
      "get": {
        "operationId": "meta_ads",
        "tags": [
          "Meta"
        ],
        "summary": "List active ads",
        "description": "Each ad carries media_type, thumbnail_url (600 px) and image_url. A custom since/until window replaces date_preset; the response then has date_preset null plus since and until.\n\nRequired scope: `meta:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "ad_account_id",
            "in": "query",
            "required": true,
            "description": "act_ followed by digits, e.g. act_1234567890.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Default active.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "paused",
                "deleted",
                "archived",
                "in_process",
                "with_issues",
                "campaign_paused",
                "adset_paused",
                "disapproved",
                "pending_review",
                "all"
              ]
            }
          },
          {
            "name": "date_preset",
            "in": "query",
            "required": false,
            "description": "One of Meta's presets (today, yesterday, last_7d, last_30d, this_month, last_month, maximum, ...). Default last_7d.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Custom window start, YYYY-MM-DD (inclusive). Give with until; overrides date_preset.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Custom window end, YYYY-MM-DD (inclusive), no later than today in the ad account's time zone. Up to 37 months back.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/meta/summary": {
      "get": {
        "operationId": "meta_summary",
        "tags": [
          "Meta"
        ],
        "summary": "Account performance summary",
        "description": "Required scope: `meta:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "ad_account_id",
            "in": "query",
            "required": true,
            "description": "act_ followed by digits.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_preset",
            "in": "query",
            "required": false,
            "description": "One of Meta's presets. Default last_7d.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Custom window start, YYYY-MM-DD (inclusive). Give with until; overrides date_preset.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Custom window end, YYYY-MM-DD (inclusive), no later than today in the ad account's time zone. Up to 37 months back.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/meta/ad/{ad_id}/performance": {
      "get": {
        "operationId": "meta_ad_perf",
        "tags": [
          "Meta"
        ],
        "summary": "Performance for one ad",
        "description": "ad.creative carries body, title, media_type (image or video), thumbnail_url (600 px), image_url, video_url (video ads, when Meta exposes the source) and preview_image_url, the best still to show. Media links are Meta CDN URLs that expire after a few days.\n\nRequired scope: `meta:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "ad_id",
            "in": "path",
            "required": true,
            "description": "Numeric Meta ad id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_preset",
            "in": "query",
            "required": false,
            "description": "One of Meta's presets. Default last_7d.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Custom window start, YYYY-MM-DD (inclusive). Give with until; overrides date_preset.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Custom window end, YYYY-MM-DD (inclusive), no later than today in the ad account's time zone. Up to 37 months back.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/meta/publish": {
      "post": {
        "operationId": "meta_publish",
        "tags": [
          "Meta"
        ],
        "summary": "Publish a creative to Meta",
        "description": "Needs your own Meta connection (a team owner's shared connection only works for reading insights). ad_account_id is act_ followed by digits; adset_id and page_id are numeric; destination_url is http(s); image_url must be a public URL (max 30 MB). ACTIVE needs a confirmation token (see Confirmation tokens).\n\nRequired scope: `meta:write`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ad_account_id": {
                    "type": "string",
                    "description": "act_1234567890."
                  },
                  "adset_id": {
                    "type": "string",
                    "description": "Numeric ad set id."
                  },
                  "page_id": {
                    "type": "string",
                    "description": "Numeric Facebook page id."
                  },
                  "image_url": {
                    "type": "string",
                    "description": "Public image URL."
                  },
                  "destination_url": {
                    "type": "string",
                    "description": "Where the ad links to."
                  },
                  "ad_name": {
                    "type": "string",
                    "description": "Max 400."
                  },
                  "primary_text": {
                    "type": "string",
                    "description": "Ad text."
                  },
                  "headline": {
                    "type": "string",
                    "description": "Headline."
                  },
                  "description": {
                    "type": "string",
                    "description": "Link description."
                  },
                  "cta": {
                    "type": "string",
                    "description": "Call to action, e.g. LEARN_MORE."
                  },
                  "status": {
                    "type": "string",
                    "description": "Default PAUSED.",
                    "enum": [
                      "PAUSED",
                      "ACTIVE"
                    ]
                  }
                },
                "required": [
                  "ad_account_id",
                  "adset_id",
                  "page_id",
                  "image_url",
                  "destination_url",
                  "ad_name"
                ],
                "additionalProperties": true
              },
              "example": {
                "ad_account_id": "act_123456789",
                "adset_id": "23851234567890123",
                "page_id": "1234567890",
                "image_url": "https://api.zuops.com/functions/v1/api-serve-image?path=ads/...",
                "destination_url": "https://acme.example.com/offer",
                "ad_name": "Fall promo, square",
                "primary_text": "Save 30% on your first month.",
                "headline": "Start today",
                "cta": "LEARN_MORE",
                "status": "PAUSED"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/automations": {
      "get": {
        "operationId": "list_automations",
        "tags": [
          "Ad automations"
        ],
        "summary": "List ad automations for every engine",
        "description": "Returns one array per engine: auto-pause, budget-scaler, fatigue-detector, spend-killer, restart-stalled, auto-ads.\n\nRequired scope: `automations:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/automations/{engine}": {
      "post": {
        "operationId": "upsert_automation",
        "tags": [
          "Ad automations"
        ],
        "summary": "Create or update an ad automation",
        "description": "Required scope: `automations:write`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "engine",
            "in": "path",
            "required": true,
            "description": "Automation engine.",
            "schema": {
              "type": "string",
              "enum": [
                "auto-pause",
                "budget-scaler",
                "fatigue-detector",
                "spend-killer",
                "restart-stalled",
                "auto-ads"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/automations/{engine}/toggle": {
      "post": {
        "operationId": "toggle_automation",
        "tags": [
          "Ad automations"
        ],
        "summary": "Enable or disable an ad automation",
        "description": "Required scope: `automations:write`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "engine",
            "in": "path",
            "required": true,
            "description": "Automation engine.",
            "schema": {
              "type": "string",
              "enum": [
                "auto-pause",
                "budget-scaler",
                "fatigue-detector",
                "spend-killer",
                "restart-stalled",
                "auto-ads"
              ]
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean",
                    "description": "true or false. Omitted means true."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/automations/{engine}/log": {
      "get": {
        "operationId": "automation_log",
        "tags": [
          "Ad automations"
        ],
        "summary": "Automation run log",
        "description": "Required scope: `automations:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "engine",
            "in": "path",
            "required": true,
            "description": "Automation engine.",
            "schema": {
              "type": "string",
              "enum": [
                "auto-pause",
                "budget-scaler",
                "fatigue-detector",
                "spend-killer",
                "restart-stalled",
                "auto-ads"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 50.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads": {
      "get": {
        "operationId": "list_leads",
        "tags": [
          "CRM leads"
        ],
        "summary": "List and filter leads",
        "description": "Newest first. Filter by tags (`tag_match=any` returns leads with at least one of the tags, `all` leads with every one), status, pipeline stage, assignee, a name / email / phone search and a created-since time. Each lead carries `inquiry` (the CRM's Inquiry column), `address`, `city`, `state`, `zip`, `custom_fields`, `tags` and `stage` (the pipeline stage it shows in). Page with `limit` and `offset`: `next_offset` is set while there are more.\n\nRequired scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Restrict to one sub-account (a key restricted to one sub-account always uses its own).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "required": false,
            "description": "Tag names, comma-separated (tags=VIP,Roof), or a JSON array (tags=[\"Roof, metal\",\"VIP\"]) when a name has a comma. Max 10.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag_match",
            "in": "query",
            "required": false,
            "description": "any (default): at least one of the tags. all: every tag.",
            "schema": {
              "type": "string",
              "enum": [
                "any",
                "all"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "One or more statuses, comma-separated: new, contacted, qualified, booked, won, lost, spam, partial.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "stage_key",
            "in": "query",
            "required": false,
            "description": "Pipeline stage key from GET /v1/stages.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "assigned_to",
            "in": "query",
            "required": false,
            "description": "A user id (GET /v1/members): leads assigned to them. \"me\": the key's own user. \"unassigned\": nobody's.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "State / province / region as saved on the lead, comma-separated (state=TX,FL), like Leads > Filter > State. A name finds its code too (\"Texas\" finds TX). \"none\": leads with no State. Max 20.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Matches name, email or phone (any format).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO timestamp: only leads created at or after it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 50, max 200.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Skip this many leads; pass next_offset from the previous page.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "leads": [
                      {
                        "id": "lead-uuid",
                        "workspace_id": "ws-uuid",
                        "full_name": "Jane Doe",
                        "email": "jane@example.com",
                        "phone": "+14155550123",
                        "source": "api",
                        "status": "contacted",
                        "stage_key": "quote_sent",
                        "score": null,
                        "assigned_to": null,
                        "ad_name": null,
                        "campaign_name": null,
                        "deal_value_cents": null,
                        "deal_currency": "USD",
                        "created_at": "2026-09-23T15:00:00Z",
                        "updated_at": "2026-09-23T15:05:00Z",
                        "last_activity_at": "2026-09-23T15:05:00Z",
                        "stage": "quote_sent",
                        "inquiry": "Roof replacement quote",
                        "address": "12 Main St",
                        "city": "Pittsburgh",
                        "state": "PA",
                        "zip": "15213",
                        "custom_fields": {
                          "roof_type": "Metal",
                          "roof_age_years": 18
                        },
                        "tags": [
                          "Spring promo",
                          "VIP"
                        ]
                      }
                    ],
                    "has_more": false,
                    "next_offset": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_lead",
        "tags": [
          "CRM leads"
        ],
        "summary": "Create a lead",
        "description": "Needs workspace_id plus at least one of full_name, email or phone. Besides the lead's columns you can set `inquiry`, `address`, `city`, `state`, `zip`, `custom_fields` and `tags`. The lead and its tags are saved together, so automations that filter on tags see them; each tag fires the lead.tag_added automation trigger. A lead with the same phone (last 10 digits) or email already in the sub-account answers 409 with its `lead_id` and `matched_on` in `details`: update that lead instead. A field the API does not know answers 400 (it is never silently dropped). By default the intake doesn't run (no first text or AI opener, follow-ups, team alert or Instant Lead Connect); `run_intake: true` works it like any new lead from a form and `intake` in the response says what ran. Either way it is a new lead to the rest of the CRM, as one added by hand is: automations that start on a new lead or a tag run, AI calling campaigns that take new leads pick it up, and the lead.created webhook fires. Returns the lead as GET /v1/leads/{lead_id} shows it.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "full_name": {
                    "type": "string",
                    "description": "Lead name."
                  },
                  "email": {
                    "type": "string",
                    "description": "Email address."
                  },
                  "phone": {
                    "type": "string",
                    "description": "10-digit US number or + and a country code; stored as E.164."
                  },
                  "source": {
                    "type": "string",
                    "description": "Where the lead came from. Defaults to api.",
                    "enum": [
                      "meta_lead_ad",
                      "manual",
                      "website_form",
                      "import",
                      "api",
                      "other",
                      "sms_inbound",
                      "email_inbound",
                      "webhook",
                      "smart_form",
                      "smart_form_partial",
                      "site_widget",
                      "instagram",
                      "messenger",
                      "review_funnel",
                      "calendly",
                      "acuity"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "description": "Base lead status. To move a lead in the pipeline, send stage_key instead.",
                    "enum": [
                      "new",
                      "contacted",
                      "qualified",
                      "booked",
                      "won",
                      "lost",
                      "spam",
                      "partial"
                    ]
                  },
                  "stage_key": {
                    "type": "string",
                    "description": "Pipeline stage key from GET /v1/stages. A stage also sets the status it stands for."
                  },
                  "score": {
                    "type": "number",
                    "description": "A number 0-100 that is yours to set, e.g. a counter your automations keep. Nothing writes it for you (AI lead scoring is off)."
                  },
                  "notes": {
                    "type": "string",
                    "description": "Internal notes."
                  },
                  "assigned_to": {
                    "type": "string",
                    "description": "User id of someone in the sub-account (GET /v1/members). The agency owner only with the owner's own key (the app never offers them to the sub-account's people; a lead already on them may stay). null unassigns. A change is logged on the lead's timeline (Lead assigned / Lead unassigned), as in the app."
                  },
                  "ad_name": {
                    "type": "string",
                    "description": "Ad the lead came from."
                  },
                  "campaign_name": {
                    "type": "string",
                    "description": "Campaign the lead came from."
                  },
                  "utm_source": {
                    "type": "string",
                    "description": "UTM source."
                  },
                  "utm_medium": {
                    "type": "string",
                    "description": "UTM medium."
                  },
                  "utm_campaign": {
                    "type": "string",
                    "description": "UTM campaign."
                  },
                  "utm_term": {
                    "type": "string",
                    "description": "UTM term."
                  },
                  "utm_content": {
                    "type": "string",
                    "description": "UTM content."
                  },
                  "landing_url": {
                    "type": "string",
                    "description": "Page the lead converted on."
                  },
                  "referrer_url": {
                    "type": "string",
                    "description": "Referring page."
                  },
                  "attribution_channel": {
                    "type": "string",
                    "description": "e.g. meta, google, organic, direct."
                  },
                  "gclid": {
                    "type": "string",
                    "description": "Google Ads click id. A lead with one is sent back to the sub-account's Google Ads by its click when it books or is won (CRM conversions)."
                  },
                  "gbraid": {
                    "type": "string",
                    "description": "Google Ads app-to-web click id."
                  },
                  "wbraid": {
                    "type": "string",
                    "description": "Google Ads web-to-app click id."
                  },
                  "fbclid": {
                    "type": "string",
                    "description": "Facebook click id."
                  },
                  "msclkid": {
                    "type": "string",
                    "description": "Microsoft Ads click id."
                  },
                  "deal_value_cents": {
                    "type": "number",
                    "description": "Deal value in cents: on a won job, what the customer paid (the Sales report's Sold, and the revenue sent to Meta)."
                  },
                  "deal_currency": {
                    "type": "string",
                    "description": "3-letter currency code. Default USD."
                  },
                  "deal_parts_cents": {
                    "type": "number",
                    "description": "Won job: what the parts cost, in cents (Sales report)."
                  },
                  "deal_tip_cents": {
                    "type": "number",
                    "description": "Won job: the tip the technician got, in cents."
                  },
                  "deal_sold_by": {
                    "type": "string",
                    "description": "Won job: the user id of the technician credited with the sale (GET /v1/members). null = whoever the lead is assigned to."
                  },
                  "deal_won_at": {
                    "type": "string",
                    "description": "When the job was won (ISO 8601). Set for you when status becomes won; send it to date a sale yourself."
                  },
                  "deal_type": {
                    "type": "string",
                    "description": "Plans & memberships: what the customer bought, one_time (a job) or plan (a plan or membership that pays again). null = one-time. A deal_plan_cents sent alone makes it a plan; one_time clears the plan.",
                    "enum": [
                      "one_time",
                      "plan"
                    ]
                  },
                  "deal_plan_cents": {
                    "type": "number",
                    "description": "Plan: its price in cents, charged every deal_plan_interval. deal_value_cents is the first payment (the plan price when you leave it out)."
                  },
                  "deal_plan_interval": {
                    "type": "string",
                    "description": "Plan: how often it's charged. Default month.",
                    "enum": [
                      "month",
                      "quarter",
                      "year"
                    ]
                  },
                  "deal_plan_start_date": {
                    "type": "string",
                    "description": "Plan: when it starts (YYYY-MM-DD). Default today."
                  },
                  "deal_plan_months": {
                    "type": "number",
                    "description": "Plan: how many months it runs (1-600). null = no end date: the business's default length counts (12 months unless changed in Settings > Features). Leads return deal_monthly_cents and deal_expected_total_cents (first payment + the plan price for the rest of its length)."
                  },
                  "inquiry": {
                    "type": "string",
                    "description": "What the lead asked about: the Inquiry column in the CRM (max 500). Without it the CRM shows the form or ad offer, then General inquiry."
                  },
                  "address": {
                    "type": "string",
                    "description": "Street address."
                  },
                  "city": {
                    "type": "string",
                    "description": "City."
                  },
                  "state": {
                    "type": "string",
                    "description": "State or region. A US state, Canadian province or Australian state is stored as its code (\"Texas\" -> \"TX\"), a Mexican state by name, like every other intake; the list follows the lead's country (its phone number, else the sub-account's country). Anything else is kept as sent."
                  },
                  "zip": {
                    "type": "string",
                    "description": "ZIP or postal code."
                  },
                  "custom_fields": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Custom field values keyed by field_key (GET /v1/custom-fields); a field's label also works. Values are checked against the field type: number/money/rating numbers, boolean true/false, date YYYY-MM-DD, datetime ISO 8601, select one of its options, multiselect a list of options. null or \"\" clears a value; fields you leave out keep their value."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Tag names. New names are created in the sub-account (matched ignoring case). Max 50."
                  },
                  "run_intake": {
                    "type": "boolean",
                    "description": "Default false. true = work the lead like a new lead from a form: first text or AI opener, follow-ups, team alert, Instant Lead Connect."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "full_name": "Jane Doe",
                "phone": "+14155550123",
                "email": "jane@example.com",
                "inquiry": "Roof replacement quote",
                "address": "12 Main St",
                "city": "Pittsburgh",
                "state": "PA",
                "zip": "15213",
                "custom_fields": {
                  "roof_type": "Metal",
                  "roof_age_years": 18
                },
                "tags": [
                  "Spring promo",
                  "VIP"
                ],
                "run_intake": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lead": {
                      "id": "lead-uuid",
                      "workspace_id": "ws-uuid",
                      "full_name": "Jane Doe",
                      "email": "jane@example.com",
                      "phone": "+14155550123",
                      "source": "api",
                      "status": "contacted",
                      "stage_key": "quote_sent",
                      "score": null,
                      "assigned_to": null,
                      "ad_name": null,
                      "campaign_name": null,
                      "deal_value_cents": null,
                      "deal_currency": "USD",
                      "created_at": "2026-09-23T15:00:00Z",
                      "updated_at": "2026-09-23T15:05:00Z",
                      "last_activity_at": "2026-09-23T15:05:00Z",
                      "stage": "quote_sent",
                      "inquiry": "Roof replacement quote",
                      "address": "12 Main St",
                      "city": "Pittsburgh",
                      "state": "PA",
                      "zip": "15213",
                      "custom_fields": {
                        "roof_type": "Metal",
                        "roof_age_years": 18
                      },
                      "tags": [
                        "Spring promo",
                        "VIP"
                      ]
                    },
                    "intake": {
                      "ran": true,
                      "greeted": true
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}": {
      "get": {
        "operationId": "get_lead",
        "tags": [
          "CRM leads"
        ],
        "summary": "Get one lead",
        "description": "The lead with every column, its raw_payload, the flattened `inquiry`, `address`, `city`, `state`, `zip`, `custom_fields`, `tags` and `stage`, plus its 50 most recent timeline events.\n\nRequired scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lead": {
                      "id": "lead-uuid",
                      "workspace_id": "ws-uuid",
                      "full_name": "Jane Doe",
                      "email": "jane@example.com",
                      "phone": "+14155550123",
                      "source": "api",
                      "status": "contacted",
                      "stage_key": "quote_sent",
                      "score": null,
                      "assigned_to": null,
                      "ad_name": null,
                      "campaign_name": null,
                      "deal_value_cents": null,
                      "deal_currency": "USD",
                      "created_at": "2026-09-23T15:00:00Z",
                      "updated_at": "2026-09-23T15:05:00Z",
                      "last_activity_at": "2026-09-23T15:05:00Z",
                      "stage": "quote_sent",
                      "inquiry": "Roof replacement quote",
                      "address": "12 Main St",
                      "city": "Pittsburgh",
                      "state": "PA",
                      "zip": "15213",
                      "custom_fields": {
                        "roof_type": "Metal",
                        "roof_age_years": 18
                      },
                      "tags": [
                        "Spring promo",
                        "VIP"
                      ]
                    },
                    "events": [
                      {
                        "id": "event-uuid",
                        "event_type": "note",
                        "body": "Lead created via API",
                        "created_at": "2026-09-23T15:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "update_lead",
        "tags": [
          "CRM leads"
        ],
        "summary": "Update a lead",
        "description": "Send only what changes; null or \"\" clears a value. `custom_fields` merges: values you leave out are kept. `tags` REPLACES all of the lead's tags; `add_tags` and `remove_tags` change only those (applied in that order: tags, add_tags, remove_tags). A body with only tag changes is fine. `stage_key` moves the lead in the pipeline and sets the status that stage stands for. A field the API does not know answers 400. Returns the updated lead.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "full_name": {
                    "type": "string",
                    "description": "Lead name."
                  },
                  "email": {
                    "type": "string",
                    "description": "Email address."
                  },
                  "phone": {
                    "type": "string",
                    "description": "10-digit US number or + and a country code; stored as E.164."
                  },
                  "source": {
                    "type": "string",
                    "description": "Where the lead came from. Defaults to api.",
                    "enum": [
                      "meta_lead_ad",
                      "manual",
                      "website_form",
                      "import",
                      "api",
                      "other",
                      "sms_inbound",
                      "email_inbound",
                      "webhook",
                      "smart_form",
                      "smart_form_partial",
                      "site_widget",
                      "instagram",
                      "messenger",
                      "review_funnel",
                      "calendly",
                      "acuity"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "description": "Base lead status. To move a lead in the pipeline, send stage_key instead.",
                    "enum": [
                      "new",
                      "contacted",
                      "qualified",
                      "booked",
                      "won",
                      "lost",
                      "spam",
                      "partial"
                    ]
                  },
                  "stage_key": {
                    "type": "string",
                    "description": "Pipeline stage key from GET /v1/stages. A stage also sets the status it stands for."
                  },
                  "score": {
                    "type": "number",
                    "description": "A number 0-100 that is yours to set, e.g. a counter your automations keep. Nothing writes it for you (AI lead scoring is off)."
                  },
                  "notes": {
                    "type": "string",
                    "description": "Internal notes."
                  },
                  "assigned_to": {
                    "type": "string",
                    "description": "User id of someone in the sub-account (GET /v1/members). The agency owner only with the owner's own key (the app never offers them to the sub-account's people; a lead already on them may stay). null unassigns. A change is logged on the lead's timeline (Lead assigned / Lead unassigned), as in the app."
                  },
                  "ad_name": {
                    "type": "string",
                    "description": "Ad the lead came from."
                  },
                  "campaign_name": {
                    "type": "string",
                    "description": "Campaign the lead came from."
                  },
                  "utm_source": {
                    "type": "string",
                    "description": "UTM source."
                  },
                  "utm_medium": {
                    "type": "string",
                    "description": "UTM medium."
                  },
                  "utm_campaign": {
                    "type": "string",
                    "description": "UTM campaign."
                  },
                  "utm_term": {
                    "type": "string",
                    "description": "UTM term."
                  },
                  "utm_content": {
                    "type": "string",
                    "description": "UTM content."
                  },
                  "landing_url": {
                    "type": "string",
                    "description": "Page the lead converted on."
                  },
                  "referrer_url": {
                    "type": "string",
                    "description": "Referring page."
                  },
                  "attribution_channel": {
                    "type": "string",
                    "description": "e.g. meta, google, organic, direct."
                  },
                  "gclid": {
                    "type": "string",
                    "description": "Google Ads click id. A lead with one is sent back to the sub-account's Google Ads by its click when it books or is won (CRM conversions)."
                  },
                  "gbraid": {
                    "type": "string",
                    "description": "Google Ads app-to-web click id."
                  },
                  "wbraid": {
                    "type": "string",
                    "description": "Google Ads web-to-app click id."
                  },
                  "fbclid": {
                    "type": "string",
                    "description": "Facebook click id."
                  },
                  "msclkid": {
                    "type": "string",
                    "description": "Microsoft Ads click id."
                  },
                  "deal_value_cents": {
                    "type": "number",
                    "description": "Deal value in cents: on a won job, what the customer paid (the Sales report's Sold, and the revenue sent to Meta)."
                  },
                  "deal_currency": {
                    "type": "string",
                    "description": "3-letter currency code. Default USD."
                  },
                  "deal_parts_cents": {
                    "type": "number",
                    "description": "Won job: what the parts cost, in cents (Sales report)."
                  },
                  "deal_tip_cents": {
                    "type": "number",
                    "description": "Won job: the tip the technician got, in cents."
                  },
                  "deal_sold_by": {
                    "type": "string",
                    "description": "Won job: the user id of the technician credited with the sale (GET /v1/members). null = whoever the lead is assigned to."
                  },
                  "deal_won_at": {
                    "type": "string",
                    "description": "When the job was won (ISO 8601). Set for you when status becomes won; send it to date a sale yourself."
                  },
                  "deal_type": {
                    "type": "string",
                    "description": "Plans & memberships: what the customer bought, one_time (a job) or plan (a plan or membership that pays again). null = one-time. A deal_plan_cents sent alone makes it a plan; one_time clears the plan.",
                    "enum": [
                      "one_time",
                      "plan"
                    ]
                  },
                  "deal_plan_cents": {
                    "type": "number",
                    "description": "Plan: its price in cents, charged every deal_plan_interval. deal_value_cents is the first payment (the plan price when you leave it out)."
                  },
                  "deal_plan_interval": {
                    "type": "string",
                    "description": "Plan: how often it's charged. Default month.",
                    "enum": [
                      "month",
                      "quarter",
                      "year"
                    ]
                  },
                  "deal_plan_start_date": {
                    "type": "string",
                    "description": "Plan: when it starts (YYYY-MM-DD). Default today."
                  },
                  "deal_plan_months": {
                    "type": "number",
                    "description": "Plan: how many months it runs (1-600). null = no end date: the business's default length counts (12 months unless changed in Settings > Features). Leads return deal_monthly_cents and deal_expected_total_cents (first payment + the plan price for the rest of its length)."
                  },
                  "inquiry": {
                    "type": "string",
                    "description": "What the lead asked about: the Inquiry column in the CRM (max 500). Without it the CRM shows the form or ad offer, then General inquiry."
                  },
                  "address": {
                    "type": "string",
                    "description": "Street address."
                  },
                  "city": {
                    "type": "string",
                    "description": "City."
                  },
                  "state": {
                    "type": "string",
                    "description": "State or region. A US state, Canadian province or Australian state is stored as its code (\"Texas\" -> \"TX\"), a Mexican state by name, like every other intake; the list follows the lead's country (its phone number, else the sub-account's country). Anything else is kept as sent."
                  },
                  "zip": {
                    "type": "string",
                    "description": "ZIP or postal code."
                  },
                  "custom_fields": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Custom field values keyed by field_key (GET /v1/custom-fields); a field's label also works. Values are checked against the field type: number/money/rating numbers, boolean true/false, date YYYY-MM-DD, datetime ISO 8601, select one of its options, multiselect a list of options. null or \"\" clears a value; fields you leave out keep their value."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "REPLACES all of the lead's tags ([] removes them all). To change only some, use add_tags / remove_tags."
                  },
                  "add_tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Tag names to add; the lead's other tags stay. New names are created."
                  },
                  "remove_tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Tag names to remove."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "add_tags": [
                  "Hot lead"
                ],
                "remove_tags": [
                  "Cold"
                ],
                "stage_key": "quote_sent",
                "custom_fields": {
                  "roof_type": "Tile"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lead": {
                      "id": "lead-uuid",
                      "workspace_id": "ws-uuid",
                      "full_name": "Jane Doe",
                      "email": "jane@example.com",
                      "phone": "+14155550123",
                      "source": "api",
                      "status": "contacted",
                      "stage_key": "quote_sent",
                      "score": null,
                      "assigned_to": null,
                      "ad_name": null,
                      "campaign_name": null,
                      "deal_value_cents": null,
                      "deal_currency": "USD",
                      "created_at": "2026-09-23T15:00:00Z",
                      "updated_at": "2026-09-23T15:05:00Z",
                      "last_activity_at": "2026-09-23T15:05:00Z",
                      "stage": "quote_sent",
                      "inquiry": "Roof replacement quote",
                      "address": "12 Main St",
                      "city": "Pittsburgh",
                      "state": "PA",
                      "zip": "15213",
                      "custom_fields": {
                        "roof_type": "Metal",
                        "roof_age_years": 18
                      },
                      "tags": [
                        "Spring promo",
                        "VIP"
                      ]
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_lead",
        "tags": [
          "CRM leads"
        ],
        "summary": "Delete a lead",
        "description": "With delete_appointments=true its appointments go first, each the way the calendar deletes one: the synced Google / Outlook event, the customer's invite (cancelled if it was still on), its reminder texts, then the appointment; if one can't be deleted the lead is kept and the answer says how many went. Without it the appointments stay on the calendar with no lead, as before.\n\nRequired scope: `leads:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "delete_appointments",
            "in": "query",
            "required": false,
            "description": "Also delete the lead's appointments (the app's \"Also delete their appointments\").",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "deleted": true,
                    "id": "uuid",
                    "appointments_deleted": 2
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}/timeline": {
      "get": {
        "operationId": "lead_timeline",
        "tags": [
          "CRM leads"
        ],
        "summary": "Lead activity timeline",
        "description": "Required scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 50.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}/notes": {
      "post": {
        "operationId": "create_lead_note",
        "tags": [
          "CRM leads"
        ],
        "summary": "Add a note to a lead",
        "description": "mentions tags teammates like @Name in the app's note box: each one on the sub-account's team who can see the lead gets an email and a push with the note and a link to the conversation (replies go to you); the answer lists who was told and who was skipped and why (not_on_team, cant_see_lead).\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "Note text."
                  },
                  "actor_label": {
                    "type": "string",
                    "description": "Who the note is from."
                  },
                  "mentions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 20 user ids to tag (GET /v1/members)."
                  }
                },
                "required": [
                  "body"
                ],
                "additionalProperties": true
              },
              "example": {
                "body": "@Sam can you call her back after 5?",
                "mentions": [
                  "uuid"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "event": {
                      "id": "uuid",
                      "event_type": "note",
                      "body": "@Sam can you call her back after 5?"
                    },
                    "mentions": {
                      "tagged": [
                        {
                          "user_id": "uuid",
                          "name": "Sam Tech"
                        }
                      ],
                      "skipped": []
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}/stage": {
      "post": {
        "operationId": "set_lead_stage",
        "tags": [
          "CRM leads"
        ],
        "summary": "Move a lead to a pipeline stage",
        "description": "PATCH on the same path does the same.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "stage_key": {
                    "type": "string",
                    "description": "Stage key from GET /v1/stages."
                  }
                },
                "required": [
                  "stage_key"
                ],
                "additionalProperties": true
              },
              "example": {
                "stage_key": "booked"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/stages": {
      "get": {
        "operationId": "list_stages",
        "tags": [
          "CRM leads"
        ],
        "summary": "List pipeline stages",
        "description": "Required scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_stage",
        "tags": [
          "Pipeline & fields"
        ],
        "summary": "Add a pipeline stage",
        "description": "`behavior` is what the stage means for reporting. The key is made from the label and never changes.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "label": {
                    "type": "string",
                    "description": "Stage name (max 40)."
                  },
                  "behavior": {
                    "type": "string",
                    "description": "Default contacted.",
                    "enum": [
                      "new",
                      "contacted",
                      "booked",
                      "won",
                      "lost"
                    ]
                  },
                  "color": {
                    "type": "string",
                    "description": "Defaults to the first unused colour.",
                    "enum": [
                      "blue",
                      "amber",
                      "violet",
                      "cyan",
                      "emerald",
                      "rose",
                      "teal",
                      "orange",
                      "pink",
                      "indigo",
                      "lime",
                      "slate"
                    ]
                  },
                  "manual_only": {
                    "type": "boolean",
                    "description": "Moving a lead here does not change its status."
                  }
                },
                "required": [
                  "workspace_id",
                  "label"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "label": "Quote sent",
                "behavior": "contacted"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/tags": {
      "get": {
        "operationId": "list_tags",
        "tags": [
          "CRM leads"
        ],
        "summary": "List lead tags",
        "description": "Every tag in the sub-account and how many leads carry it. A tag is created automatically the first time you send its name in a lead's tags or add_tags.\n\nRequired scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "tags": [
                      {
                        "id": "tag-uuid",
                        "name": "VIP",
                        "color": "slate",
                        "lead_count": 12,
                        "created_at": "2026-09-01T10:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/tags/{tag_id}": {
      "patch": {
        "operationId": "update_tag",
        "tags": [
          "CRM leads"
        ],
        "summary": "Rename or recolor a tag",
        "description": "Settings > Tags. A new name (60 characters, no commas, not another tag's name: merge them instead) and/or a color. A rename also rewrites every automation that names the tag (Add / Remove tag steps, the Tag added trigger, tag conditions) unless update_automations is false. Owners and admins.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag_id",
            "in": "path",
            "required": true,
            "description": "The tag (GET /v1/tags).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The new name."
                  },
                  "color": {
                    "type": "string",
                    "description": "The tag's color.",
                    "enum": [
                      "blue",
                      "amber",
                      "violet",
                      "cyan",
                      "emerald",
                      "rose",
                      "teal",
                      "orange",
                      "pink",
                      "indigo",
                      "lime",
                      "slate"
                    ]
                  },
                  "update_automations": {
                    "type": "boolean",
                    "description": "Default true: automations that name the tag follow the rename."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "name": "Roof replacement",
                "color": "amber"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "tag-uuid",
                    "name": "Roof replacement",
                    "color": "amber",
                    "automations_updated": 2
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_tag",
        "tags": [
          "CRM leads"
        ],
        "summary": "Delete a tag",
        "description": "Takes the tag off every contact for good. Automations that name it are left as they are. Owners and admins.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag_id",
            "in": "path",
            "required": true,
            "description": "The tag (GET /v1/tags).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "tag-uuid",
                    "deleted": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/tags/merge": {
      "post": {
        "operationId": "merge_tags",
        "tags": [
          "CRM leads"
        ],
        "summary": "Merge tags into one",
        "description": "Folds tags into the one you keep: their contacts get the kept tag and the others are deleted. Moving a tag is not adding it, so no \"Tag added\" automation starts. Automations that name a folded-in tag are rewritten to the kept one unless update_automations is false. Name tags by name or id. Owners and admins.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "The tags to fold in (names or ids, up to 50)."
                  },
                  "into": {
                    "type": "string",
                    "description": "The tag to keep (name or id)."
                  },
                  "update_automations": {
                    "type": "boolean",
                    "description": "Default true."
                  }
                },
                "required": [
                  "workspace_id",
                  "tags",
                  "into"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "tags": [
                  "roof",
                  "Roofing lead"
                ],
                "into": "Roofing"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "kept": {
                      "id": "tag-uuid",
                      "name": "Roofing"
                    },
                    "merged": [
                      {
                        "id": "uuid",
                        "name": "roof"
                      },
                      {
                        "id": "uuid",
                        "name": "Roofing lead"
                      }
                    ],
                    "contacts_moved": 41,
                    "automations_updated": 1
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/messages": {
      "post": {
        "operationId": "send_message",
        "tags": [
          "Messaging"
        ],
        "summary": "Send an SMS, email, Instagram or Messenger message",
        "description": "Sends from the sub-account's own number / verified sending domain and logs the message on the lead timeline. Email requires a subject. Address the message with `lead_id`, or with a raw `to` (E.164 phone for SMS, email address for email) plus `workspace_id` — Zuops matches an existing contact or creates one. Every send counts against the daily unique-contact safety limit (300 contacts per UTC day by default); replies to contacts already messaged today are free of the cap. Over the cap the call answers 429. Each message is billed at the platform rate and counts against the key's monthly credit budget, if it has one (402 when the budget is used up); a text through one of the sub-account's iMessage Macs is free. A text or email (and an email's subject) can use the lead's variables, as in the Inbox and automations: {first_name}, {full_name}, {business_name} and the rest listed by GET /v1/workspace-automations/reference are filled in for the lead before it goes. The message is sent as the key's user, as from the Inbox: the conversation names them, the Activity Log records them, the numbers and Macs they may text from apply, and an archived conversation comes back to the Inbox.\n\nWith `send_at` the message is scheduled instead, exactly like the Inbox's \"Schedule message\": the call answers 201 with the scheduled message, and at that time it is sent from the lead's usual number or sender as the key's user, with the same opt-out, credit and limit checks as a send now (a refusal then marks it failed with the reason). A contact who opted out, or has no phone or email for the channel, is refused up front (400). A scheduled message counts against the key's credit budget when it is scheduled. See, change, cancel or send it early with /v1/scheduled-messages.\n\nWith `channel` instagram or messenger the message is a reply in the Instagram or Messenger conversation the lead started with the business (`lead_id` only), exactly like replying from the Inbox: text only (Instagram up to 1,000 characters, Messenger 2,000), sent now, through the connected account the lead wrote to last, as the key's user. Instagram and Messenger only allow it within 24 hours of the person's last message there: after that the call answers 409 `window_closed` with `reply_window_ends_at`; also 409 when the lead never messaged there (`no_social_contact`) or the account is switched off or needs connecting again (`channel_disabled`, `needs_reconnect`), and 403 `not_enabled` when Instagram & Messenger is off for the account. It is free, uses none of the credit budget (still refused once the budget is used up) and isn't counted in the daily contact limit. The answer carries `message_id` and `account_id` (GET /v1/social/accounts).\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lead_id": {
                    "type": "string",
                    "description": "Lead to message. Omit when using `to`. Required for instagram and messenger."
                  },
                  "to": {
                    "type": "string",
                    "description": "Raw destination: E.164 phone for sms, email address for email. Requires workspace_id."
                  },
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id. Required when using `to` (omit on pinned keys)."
                  },
                  "channel": {
                    "type": "string",
                    "description": "sms, email, instagram or messenger.",
                    "enum": [
                      "sms",
                      "email",
                      "instagram",
                      "messenger"
                    ]
                  },
                  "body": {
                    "type": "string",
                    "description": "Message text."
                  },
                  "subject": {
                    "type": "string",
                    "description": "Required for email."
                  },
                  "name": {
                    "type": "string",
                    "description": "Contact name used when `to` creates a new lead."
                  },
                  "media_urls": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 10 MMS/attachment URLs."
                  },
                  "actor_label": {
                    "type": "string",
                    "description": "Sender label shown in the timeline."
                  },
                  "send_at": {
                    "type": "string",
                    "description": "Schedule instead of sending now: ISO 8601 with a time zone (Z or an offset), e.g. 2026-10-20T09:00:00-05:00. In the future and at most 365 days ahead. Answers 201 with `scheduled_message`."
                  },
                  "send_as": {
                    "type": "string",
                    "description": "Texts, with an iMessage Mac connected (Beta): imessage sends a blue iMessage from one of the sub-account's iPhone numbers, sms from its business number, auto (default) lets the routing choose. The answer's `transport` says how it went (imessage, or mac_sms for the iPhone's own SMS).",
                    "enum": [
                      "auto",
                      "imessage",
                      "sms"
                    ]
                  },
                  "imessage_line_id": {
                    "type": "string",
                    "description": "Texts: which Mac (iPhone number) sends it (GET /v1/imessage/lines)."
                  },
                  "audio_message": {
                    "type": "boolean",
                    "description": "Texts: the one media_urls file is a voice message. Through a Mac running Advanced mode it arrives as a real iMessage audio message; anywhere else as an audio attachment. Not with send_at."
                  }
                },
                "required": [
                  "channel",
                  "body"
                ],
                "additionalProperties": true
              },
              "example": {
                "lead_id": "uuid",
                "channel": "sms",
                "body": "Hi Jane, are you still looking for a quote?"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "operationId": "list_messages",
        "tags": [
          "Messaging"
        ],
        "summary": "List recent messages on this sub-account",
        "description": "Every SMS and email on the sub-account's Zuops numbers and inboxes, and every Instagram and Messenger DM, newest first. A DM has `channel` instagram or messenger, no from / to / subject, its pictures and files in `media_urls` and `social` { account_id: the connected account (GET /v1/social/accounts), sent_by: user, ai, automation or page (sent from the Instagram or Facebook apps) on the business's side }. For polling, pass `cursor` (or `since`) — results then come oldest-first and `next_cursor` walks forward with no duplicates and no missed messages. Poll no more than once every 10 seconds, or subscribe to the `message.received` webhook instead. A text that went through one of the sub-account's iMessage Macs carries `transport` (imessage, or mac_sms for the iPhone's own green text) and `imessage` { line_id, from, service: imessage | sms | rcs, delivered_at, read_at }; a tapback carries `reaction` { by: them | you, type, emoji, reacts_to: the message it hangs on, removed }.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on pinned keys).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "Only messages with this contact.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "sms, email, instagram or messenger.",
            "schema": {
              "type": "string",
              "enum": [
                "sms",
                "email",
                "instagram",
                "messenger"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "inbound or outbound.",
            "schema": {
              "type": "string",
              "enum": [
                "inbound",
                "outbound"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO-8601 timestamp; returns messages created after it, oldest-first.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous poll.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 50, max 200.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/scheduled-messages": {
      "get": {
        "operationId": "list_scheduled_messages",
        "tags": [
          "Messaging"
        ],
        "summary": "List scheduled texts and emails",
        "description": "Messages waiting to be sent later, scheduled in the Inbox or with POST /v1/messages and `send_at`. By default the upcoming ones (pending, sending, failed), soonest first, like the dashed bubbles at the end of a conversation; `status` = sent, cancelled or all lists history, newest first. A failed message carries `last_error`. Viewers limited to their own leads only see those leads' messages.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "Only this lead's messages.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Default upcoming (pending, sending, failed).",
            "schema": {
              "type": "string",
              "enum": [
                "upcoming",
                "pending",
                "sending",
                "failed",
                "sent",
                "cancelled",
                "all"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "scheduled_messages": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "lead_id": "uuid",
                        "channel": "sms",
                        "body": "Hi Jane, just checking in about your quote.",
                        "subject": null,
                        "from_number": null,
                        "from_sender_id": null,
                        "media_urls": [],
                        "attachment_urls": [],
                        "attachment_names": [],
                        "scheduled_at": "2026-10-20T14:00:00+00:00",
                        "status": "pending",
                        "attempts": 0,
                        "last_error": null,
                        "sent_at": null,
                        "lead_event_id": null,
                        "created_by": "uuid",
                        "cancelled_at": null,
                        "cancelled_by": null,
                        "created_at": "2026-10-16T18:02:11Z",
                        "updated_at": "2026-10-16T18:02:11Z",
                        "lead": {
                          "id": "uuid",
                          "full_name": "Jane Doe",
                          "phone": "+14155550123",
                          "email": null
                        }
                      }
                    ],
                    "has_more": false,
                    "next_cursor": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/scheduled-messages/{scheduled_message_id}": {
      "patch": {
        "operationId": "update_scheduled_message",
        "tags": [
          "Messaging"
        ],
        "summary": "Change, cancel or restore a scheduled message",
        "description": "Like the Inbox's Edit, Cancel and Undo. Send any of body, subject (emails), scheduled_at and media_urls (texts) to change a pending or failed message: it goes back in the queue (status pending). `status: \"cancelled\"` (on its own) stops it; `status: \"pending\"` brings a cancelled one back (send a new scheduled_at when its time has passed). 409 when it is sending or already sent. Owners and admins only.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scheduled_message_id",
            "in": "path",
            "required": true,
            "description": "Scheduled message id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "The new text."
                  },
                  "subject": {
                    "type": "string",
                    "description": "The new subject (emails only)."
                  },
                  "scheduled_at": {
                    "type": "string",
                    "description": "ISO 8601 with a time zone, in the future and at most 365 days ahead."
                  },
                  "media_urls": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Picture URLs for a text, max 10 (replaces the list)."
                  },
                  "status": {
                    "type": "string",
                    "description": "cancelled to cancel, pending to restore.",
                    "enum": [
                      "pending",
                      "cancelled"
                    ]
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "scheduled_at": "2026-10-21T09:00:00-05:00",
                "body": "Hi Jane, following up on your quote. Any questions?"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "scheduled_message": {
                      "id": "uuid",
                      "channel": "sms",
                      "body": "Hi Jane, following up on your quote. Any questions?",
                      "scheduled_at": "2026-10-21T14:00:00+00:00",
                      "status": "pending"
                    },
                    "changed": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/scheduled-messages/{scheduled_message_id}/send-now": {
      "post": {
        "operationId": "send_scheduled_message_now",
        "tags": [
          "Messaging"
        ],
        "summary": "Send a scheduled message now",
        "description": "The Inbox's \"Send now\": a pending or failed message goes out at once, as the teammate who scheduled it, with the usual opt-out, credit and limit checks, and is billed like any text or email. 200 = sent; 202 = a sending hiccup, it tries again by itself in a couple of minutes; 400 = refused, with the reason (the message is marked failed); 409 = cancelled, already sending or sent. Refused when the key's credit budget is used up. Owners and admins only.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scheduled_message_id",
            "in": "path",
            "required": true,
            "description": "Scheduled message id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "sent": true,
                    "scheduled_message": {
                      "id": "uuid",
                      "status": "sent",
                      "sent_at": "2026-10-16T18:05:02Z",
                      "lead_event_id": "uuid"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/messages/{message_id}": {
      "get": {
        "operationId": "get_message",
        "tags": [
          "Messaging"
        ],
        "summary": "Read one message",
        "description": "Required scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "description": "Message id from GET /v1/messages.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/messages/limits": {
      "get": {
        "operationId": "message_limits",
        "tags": [
          "Messaging"
        ],
        "summary": "Check today's contact-safety limits",
        "description": "Returns the daily unique-contact cap (default 300 per UTC day), how many contacts have been used, what's left, segment usage and the reset time. The same values ride on every messaging call as X-Zuops-Contacts-Limit / -Used / -Remaining / -Reset headers.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on pinned keys).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}/conversation": {
      "get": {
        "operationId": "lead_conversation",
        "tags": [
          "Messaging"
        ],
        "summary": "Read the conversation with a lead",
        "description": "Texts, emails, Instagram and Messenger DMs, website chat, calls, voicemails and notes, newest first.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 50.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/imessage/lines": {
      "get": {
        "operationId": "list_imessage_lines",
        "tags": [
          "Messaging"
        ],
        "summary": "List the sub-account's iMessage Macs",
        "description": "Every Mac connected to the sub-account for iMessage, the default first: its iPhone number, whose Mac it is, whether it's online, what the Mac reports about itself (`health`: Messages signed in, Full Disk Access, awake, lid closed allowed, iCloud still syncing, the number missing from or switched off in Messages), whether Advanced mode runs on it (`advanced_mode`: real audio messages and reactions), its settings (scope, create_leads, new_conversations, fallback, offline_minutes, ai_replies), who can text from it (access, allowed_user_ids, and can_use for you), its limits (new conversations a day and an hour, tries and length before someone first replies) and today's usage (new conversations today and this hour, messages waiting, every text it sent today, and how many other accounts share that iPhone's limit). `has_number` says whether the sub-account has a business number to fall back on; without one every text goes through a Mac. Any member. Connecting and disconnecting a Mac happen in the app.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lines": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "number": "+17145550123",
                        "handles": [
                          "+17145550123"
                        ],
                        "label": null,
                        "device_name": "Front desk iMac",
                        "owner": {
                          "user_id": "uuid",
                          "name": "Pat Owner"
                        },
                        "is_default": true,
                        "online": true,
                        "last_seen_at": "2026-10-23T15:32:02Z",
                        "connected_at": "2026-10-01T16:00:00Z",
                        "app_version": "0.6.3",
                        "macos_version": "15.6",
                        "advanced_mode": false,
                        "health": {
                          "messages_running": true,
                          "signed_in": true,
                          "full_disk_access": true,
                          "automation": true,
                          "awake": true,
                          "login_item": true,
                          "laptop": false,
                          "lid_closed_ok": null,
                          "on_battery": false,
                          "icloud_syncing": false,
                          "has_number": true,
                          "number_off": false,
                          "advanced": false,
                          "advanced_ready": false
                        },
                        "settings": {
                          "scope": "crm",
                          "create_leads": true,
                          "new_conversations": "sms",
                          "fallback": "mac_then_number",
                          "offline_minutes": 10,
                          "ai_replies": true
                        },
                        "access": "everyone",
                        "allowed_user_ids": [],
                        "can_use": true,
                        "can_manage": true,
                        "limits": {
                          "new_per_day": 50,
                          "new_per_hour": 6,
                          "first_contact_messages": 5,
                          "first_contact_chars": 300
                        },
                        "usage": {
                          "new_today": 12,
                          "new_this_hour": 2,
                          "waiting": 0,
                          "shared_with": 0,
                          "messages_today": 41
                        }
                      }
                    ],
                    "has_number": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/imessage/lines/{line_id}": {
      "patch": {
        "operationId": "update_imessage_line",
        "tags": [
          "Messaging"
        ],
        "summary": "Change an iMessage Mac's settings",
        "description": "Send only what changes. The sub-account's owners and admins change any Mac; everyone else only their own; only owners and admins choose the default (is_default: true moves it from the old one). access: everyone, owner (only the person whose Mac it is) or selected (allowed_user_ids); owners, admins and the Mac's owner can always text from it, and AI replies and automations aren't limited.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "line_id",
            "in": "path",
            "required": true,
            "description": "The Mac (GET /v1/imessage/lines).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "description": "A name for it (max 80), or null."
                  },
                  "is_default": {
                    "type": "boolean",
                    "description": "true makes it the default Mac."
                  },
                  "access": {
                    "type": "string",
                    "description": "Who can text from it by hand.",
                    "enum": [
                      "everyone",
                      "owner",
                      "selected"
                    ]
                  },
                  "allowed_user_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "With access selected: the team members who can."
                  },
                  "scope": {
                    "type": "string",
                    "description": "crm: only conversations with people already in the CRM sync; all: every one-to-one conversation.",
                    "enum": [
                      "crm",
                      "all"
                    ]
                  },
                  "create_leads": {
                    "type": "boolean",
                    "description": "With scope all: someone new who texts in becomes a lead."
                  },
                  "new_conversations": {
                    "type": "string",
                    "description": "How a first text to someone goes.",
                    "enum": [
                      "sms",
                      "imessage"
                    ]
                  },
                  "fallback": {
                    "type": "string",
                    "description": "When they aren't on iMessage or the Mac is offline: the business number, RCS or SMS from the iPhone through the Mac, the iPhone and then the business number (mac_then_number, the default), or nothing.",
                    "enum": [
                      "number",
                      "mac",
                      "mac_then_number",
                      "none"
                    ]
                  },
                  "offline_minutes": {
                    "type": "number",
                    "description": "How long a message waits for an offline Mac before the fallback, 2-240."
                  },
                  "ai_replies": {
                    "type": "boolean",
                    "description": "The AI texter may answer iMessages."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "access": "selected",
                "allowed_user_ids": [
                  "uuid"
                ],
                "new_conversations": "imessage"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "line": {
                      "id": "uuid",
                      "number": "+17145550123",
                      "access": "selected",
                      "allowed_user_ids": [
                        "uuid"
                      ],
                      "settings": {
                        "new_conversations": "imessage"
                      }
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/messages/{message_id}/reaction": {
      "post": {
        "operationId": "react_to_message",
        "tags": [
          "Messaging"
        ],
        "summary": "React to an iMessage (tapback)",
        "description": "Puts a tapback (❤️ 👍 👎 😂 ‼️ ❓) on a text that went through one of the sub-account's Macs, like the Inbox's reaction picker; `remove: true` takes yours back. Only a Mac running Advanced mode can (`advanced_mode` on GET /v1/imessage/lines): otherwise 409 `advanced_off`. The reaction is queued (202) and shows on the message (its `reaction`, by you) once the Mac has sent it; if it can't, a note says why on the conversation. Anyone who can see the lead and text from that number. Free, like every text through a Mac.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "description": "The text to react to (an id from GET /v1/messages or the lead's conversation).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reaction": {
                    "type": "string",
                    "description": "love, like, dislike, laugh, emphasize or question (or the emoji).",
                    "enum": [
                      "love",
                      "like",
                      "dislike",
                      "laugh",
                      "emphasize",
                      "question"
                    ]
                  },
                  "remove": {
                    "type": "boolean",
                    "description": "true takes your reaction back."
                  }
                },
                "required": [
                  "reaction"
                ],
                "additionalProperties": true
              },
              "example": {
                "reaction": "love"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "queued": true,
                    "job_id": "uuid",
                    "message_id": "uuid",
                    "reaction": "love",
                    "remove": false,
                    "line_id": "uuid"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/imessage/automated-texts": {
      "put": {
        "operationId": "set_automated_texts_from",
        "tags": [
          "Messaging"
        ],
        "summary": "Choose who automated texts send from",
        "description": "Settings > Phone's \"Automated texts send from\" (also on the Automations page): the way the texts the sub-account sends on its own go once it has a Mac connected. Automations, appointment confirmations and reminders, follow-ups and first texts follow it; an AI reply to a customer's own text still answers the way they wrote, and a step or send that names its own way keeps it. automatic lets each iPhone's iMessage settings decide, number sends from the business number, a line_id from that iPhone. GET /v1/imessage/lines returns the current choice as automated_texts_send_from. Owners and admins.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "send_from": {
                    "type": "string",
                    "description": "automatic, number, or the line_id of a connected iPhone (GET /v1/imessage/lines)."
                  }
                },
                "required": [
                  "workspace_id",
                  "send_from"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "send_from": "number"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "automated_texts_send_from": "number"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/social/accounts": {
      "get": {
        "operationId": "list_social_accounts",
        "tags": [
          "Messaging"
        ],
        "summary": "List the connected Instagram and Messenger accounts",
        "description": "The Instagram accounts and Facebook Pages (Messenger) connected in Settings > Instagram & Messenger: whether each is on, whether the AI answers its DMs (`ai_replies`), its connection (`status` connected, needs_reconnect or error, with `last_error`) and `live`: replies can go out through it. Any member. Connecting and switching accounts happen in the app. 403 `not_enabled` when Instagram & Messenger is off for the account.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace_id": "uuid",
                    "accounts": [
                      {
                        "id": "uuid",
                        "platform": "instagram",
                        "name": "@acmeroofing",
                        "page_name": "Acme Roofing",
                        "username": "acmeroofing",
                        "enabled": true,
                        "ai_replies": true,
                        "status": "connected",
                        "live": true,
                        "last_error": null,
                        "last_event_at": "2026-10-28T19:02:11Z",
                        "connected_at": "2026-10-01T16:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/social/comments": {
      "get": {
        "operationId": "list_social_comments",
        "tags": [
          "Messaging"
        ],
        "summary": "Instagram and Facebook comments the comment-to-DM rules saw",
        "description": "Recent comments in Settings > Instagram & Messenger: every comment a comment-to-DM rule matched, newest first, with the post, who wrote it, the rule, whether a public reply went under it, what happened to the DM (`dm_status` sent, failed with `error`, or skipped: that person already got it) and the lead it started a conversation with. `counts` has the number per result with every other filter applied; page with `offset` (`next_offset`). To answer someone, reply in their conversation with POST /v1/messages (channel instagram or messenger). Owners and admins.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "What happened to the DM.",
            "schema": {
              "type": "string",
              "enum": [
                "sent",
                "failed",
                "skipped"
              ]
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "description": "One connected account (GET /v1/social/accounts).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "rule_id",
            "in": "query",
            "required": false,
            "description": "One comment-to-DM rule.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "post_id",
            "in": "query",
            "required": false,
            "description": "One post or reel.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "commenter_id",
            "in": "query",
            "required": false,
            "description": "Everything from one person (commenter.id).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Commenter name or comment text contains (max 100).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO date or time, comments on or after.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO date or time, comments on or before.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "oldest_first",
            "in": "query",
            "required": false,
            "description": "Default false (newest first).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "next_offset from the previous page.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace_id": "uuid",
                    "comments": [
                      {
                        "id": "uuid",
                        "platform": "instagram",
                        "account": {
                          "id": "uuid",
                          "name": "@acmeroofing"
                        },
                        "comment_id": "17890000000000000",
                        "post_id": "17840000000000000",
                        "commenter": {
                          "id": "17841400000000000",
                          "name": "jane.doe"
                        },
                        "text": "PRICE",
                        "rule": {
                          "id": "uuid",
                          "name": "Price keyword"
                        },
                        "public_reply": true,
                        "dm_status": "sent",
                        "error": null,
                        "lead_id": "uuid",
                        "created_at": "2026-10-28T19:02:11Z"
                      }
                    ],
                    "counts": {
                      "all": 142,
                      "sent": 131,
                      "failed": 4,
                      "skipped": 7
                    },
                    "has_more": true,
                    "next_offset": 25
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calendars": {
      "get": {
        "operationId": "list_calendars",
        "tags": [
          "Bookings"
        ],
        "summary": "List booking calendars",
        "description": "Every setting POST / PATCH /v1/calendars takes, read the way the booking page reads it: hours, length, buffers, hosts' mode, texts, pricing (with each option's and add-on's id, used by availability and bookings), design, duration_days (full-day jobs), schedule_group (shared schedule), fields and questions (the booking form), image_url, the payment settings, a summary (price_from_cents, price_to_cents, length_label such as \"4-6 hr\", has_options, takes_payment) and booking_path (/c/<slug>).\n\nRequired scope: `bookings:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "calendars": [
                      {
                        "id": "uuid",
                        "slug": "triple-t-interior-exterior",
                        "name": "Triple T Interior + Exterior",
                        "title": "Triple T Interior + Exterior",
                        "duration_minutes": 240,
                        "duration_days": null,
                        "schedule_group": "Main crew",
                        "assignment_mode": "unassigned",
                        "slots_per_window": 1,
                        "max_days_ahead": 120,
                        "pricing": {
                          "enabled": true,
                          "show_prices": true,
                          "price_cents": null,
                          "price_note": null,
                          "options_label": "Vehicle size",
                          "options": [
                            {
                              "id": "opt-car",
                              "name": "Car",
                              "description": null,
                              "price_cents": 31500,
                              "duration_minutes": 240,
                              "duration_days": null,
                              "image_url": null
                            },
                            {
                              "id": "opt-van",
                              "name": "Van",
                              "description": null,
                              "price_cents": 53500,
                              "duration_minutes": 360,
                              "duration_days": null,
                              "image_url": null
                            }
                          ],
                          "addons_label": "Add-ons",
                          "addons": [
                            {
                              "id": "add-pet",
                              "name": "Pet hair",
                              "description": null,
                              "price_cents": 4000,
                              "extra_minutes": 30,
                              "image_url": null
                            }
                          ],
                          "deposit": {
                            "mode": "fixed",
                            "amount_cents": 5000,
                            "percent": 25
                          }
                        },
                        "design": null,
                        "questions": [
                          {
                            "key": "vehicle",
                            "label": "Vehicle year / make / model",
                            "field_type": "text",
                            "options": [],
                            "required": true,
                            "enabled": true,
                            "save_to_crm": true,
                            "placeholder": null,
                            "helper_text": null
                          }
                        ],
                        "summary": {
                          "price_from_cents": 31500,
                          "price_to_cents": 53500,
                          "length_label": "4-6 hr",
                          "has_options": true,
                          "takes_payment": true
                        },
                        "booking_path": "/c/triple-t-interior-exterior",
                        "is_active": true
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_calendar",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Create a booking calendar",
        "description": "A new calendar has no host unless host_user_id or hosts is sent. Hosts must be members of the sub-account (GET /v1/members). The response is the calendar as GET /v1/calendars shows it, with its public booking path (/c/<slug>). Calendars are switched off with is_active=false rather than deleted. The booking page can show prices with options and add-ons and take a deposit (pricing), have its own look (design), book whole working days (duration_days), share one schedule with other calendars (schedule_group) and ask its own questions (questions). Taking money at booking needs Stripe, Authorize.net or Whop connected to the sub-account in the app first.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "name": {
                    "type": "string",
                    "description": "Calendar name (max 80)."
                  },
                  "title": {
                    "type": "string",
                    "description": "Shown to the person booking."
                  },
                  "duration_minutes": {
                    "type": "number",
                    "description": "5-1440, default 30."
                  },
                  "buffer_minutes": {
                    "type": "number",
                    "description": "0-1440, default 0. Always saved as 0 on an arrival_window calendar (a window books the whole window and pads nothing), as in the app."
                  },
                  "min_notice_minutes": {
                    "type": "number",
                    "description": "Default 60."
                  },
                  "max_days_ahead": {
                    "type": "number",
                    "description": "1-365, default 14."
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA zone, e.g. America/Chicago. Defaults to the sub-account's."
                  },
                  "weekly_hours": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Per day, 24h HH:MM windows: {\"mon\":[[\"09:00\",\"17:00\"]],\"sat\":[]}. Default Mon-Fri 9-5."
                  },
                  "date_overrides": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Days off or special hours: [{\"date\":\"2026-12-25\",\"pairs\":[]}]."
                  },
                  "assignment_mode": {
                    "type": "string",
                    "description": "Who takes the booking. Default unassigned (no host), or single when host_user_id is sent.",
                    "enum": [
                      "single",
                      "round_robin",
                      "collective",
                      "unassigned"
                    ]
                  },
                  "host_user_id": {
                    "type": "string",
                    "description": "Host of a single-host calendar (required for single)."
                  },
                  "hosts": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "User ids sharing a round_robin / collective calendar."
                  },
                  "meeting_type": {
                    "type": "string",
                    "description": "Default phone. in_person: the customer comes to meeting_location (studio, shop, office). customer_address: the team goes to the customer's home, and the booking page asks for their address.",
                    "enum": [
                      "zoom",
                      "google_meet",
                      "phone",
                      "phone_attendee",
                      "in_person",
                      "customer_address",
                      "custom"
                    ]
                  },
                  "scheduling_mode": {
                    "type": "string",
                    "description": "exact times, or an arrival window for trades.",
                    "enum": [
                      "exact",
                      "arrival_window"
                    ]
                  },
                  "arrival_window_minutes": {
                    "type": "number",
                    "description": "15-720."
                  },
                  "appointment_texts": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Confirmation and reminder texts for appointments on this calendar, keyed by confirmation, reminder_24h, reminder_1h, reminder_5m. Each takes {\"mode\":\"custom\",\"template\":\"...\"} (this wording, sent even when the sub-account has that text off), {\"mode\":\"off\"} (never sent for this calendar), or null / {\"mode\":\"inherit\"} (the sub-account's text, the default). Only the texts you name change."
                  },
                  "slots_per_window": {
                    "type": "number",
                    "description": "1-50: how many jobs can run at the same moment (crews)."
                  },
                  "duration_days": {
                    "type": "number",
                    "description": "Full-day jobs, 1-14 working days: each booking runs from opening time on its first day to closing time on its last, skipping closed days. null = timed by duration_minutes."
                  },
                  "schedule_group": {
                    "type": "string",
                    "description": "Shared schedule name (max 60): calendars with the same group count each other's bookings, so one crew's time is never sold twice across services. null = its own schedule."
                  },
                  "pricing": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Prices on the booking page. Merged into what the calendar has, except options and addons, which replace their lists (send each row's id to keep it; ids come back in GET /v1/calendars). Money in cents. { enabled, show_prices, price_cents (no options), price_note (max 160), options_label (\"Vehicle size\"), options: [{ id?, name (max 80), description?, price_cents, duration_minutes (5-1440, its own job length), duration_days (full-day calendars), image_url? }] (max 24; the customer picks one), addons_label, addons: [{ id?, name, description?, price_cents, extra_minutes (0-720), image_url? }] (max 24), deposit: { mode: none | fixed | percent | full, amount_cents, percent (1-100) } }. A calendar that never had pricing switches it on unless enabled is false; null clears it. A deposit needs a connected gateway."
                  },
                  "design": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The booking page look, merged into the current design. { enabled, preset (clean, showroom, midnight, fresh, warm, bold, ocean, spa, mono, graphite: applied first), mode: light | dark | auto, colors: { background, surface, text, button } (#rrggbb, merged), font, heading_font (\"match\" or a font), radius: sharp | soft | rounded | pill, button_style: solid | soft | outline, backdrop: solid | glow | photo, logo_url, cover_url (https), hide_avatar, custom_css (max 12000) }. Fonts: inter, poppins, dm-sans, space-grotesk, manrope, outfit, figtree, plus-jakarta, work-sans, rubik, nunito-sans, montserrat, raleway, urbanist, sora, lexend, archivo, barlow, karla, public-sans, bricolage, bebas, anton, archivo-black, syne, clash, oswald, playfair, dm-serif, lora, libre-baskerville, cormorant, instrument-serif, fraunces, source-serif, merriweather, jetbrains-mono, space-mono, ibm-plex-mono, system. A page that never had a design switches it on unless enabled is false; null = the classic page. It carries into embeds and custom domains."
                  },
                  "questions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "The calendar's own booking questions, up to 30, replacing the list: [{ key?, label (max 160), field_type: text | textarea | number | money | phone | email | url | select | multiselect | boolean | date | datetime | rating | file (photos or PDFs, up to 6), options (select / multiselect: 2-40), option_style?, required?, enabled?, save_to_crm? (never for file), placeholder?, helper_text? }]. Answers show on the appointment and the lead."
                  },
                  "fields": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Contact fields on the booking form, merged: {\"address\":{\"enabled\":true,\"required\":true,\"autocomplete\":true},\"email\":{\"required\":false}}. Keys: full_name, first_name, last_name, email, phone, address, state, notes. Phone or email stays on; full_name or first_name + last_name."
                  },
                  "image_url": {
                    "type": "string",
                    "description": "The service's photo (https): on its booking page and its service menu card."
                  },
                  "currency": {
                    "type": "string",
                    "description": "Prices and payments in this currency.",
                    "enum": [
                      "usd",
                      "cad",
                      "gbp",
                      "eur",
                      "aud"
                    ]
                  },
                  "payment_gateway": {
                    "type": "string",
                    "description": "Charge through this connected gateway; null = whichever is connected (Stripe first).",
                    "enum": [
                      "stripe",
                      "authorize_net",
                      "whop"
                    ]
                  },
                  "payment_hold_minutes": {
                    "type": "number",
                    "description": "15-120: how long the time is held while they pay."
                  },
                  "payment_label": {
                    "type": "string",
                    "description": "What the checkout calls the payment, e.g. \"Deposit\" (max 60)."
                  }
                },
                "required": [
                  "workspace_id",
                  "name"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "name": "Triple T Interior + Exterior",
                "duration_minutes": 240,
                "assignment_mode": "unassigned",
                "schedule_group": "Main crew",
                "max_days_ahead": 120,
                "pricing": {
                  "options_label": "Vehicle size",
                  "options": [
                    {
                      "name": "Car",
                      "price_cents": 31500,
                      "duration_minutes": 240
                    },
                    {
                      "name": "SUV",
                      "price_cents": 38500,
                      "duration_minutes": 300
                    },
                    {
                      "name": "Van",
                      "price_cents": 53500,
                      "duration_minutes": 360
                    }
                  ],
                  "addons": [
                    {
                      "name": "Pet hair",
                      "price_cents": 4000,
                      "extra_minutes": 30
                    }
                  ],
                  "deposit": {
                    "mode": "fixed",
                    "amount_cents": 5000
                  }
                },
                "design": {
                  "preset": "showroom"
                },
                "questions": [
                  {
                    "label": "Vehicle year / make / model",
                    "field_type": "text",
                    "required": true
                  },
                  {
                    "label": "Photos of the vehicle",
                    "field_type": "file"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calendars/{calendar_id}/availability": {
      "get": {
        "operationId": "calendar_availability",
        "tags": [
          "Bookings"
        ],
        "summary": "Get open appointment slots",
        "description": "The times the calendar's booking page offers, worked out the same way: weekly hours and days off, the job length, buffers, capacity (slots_per_window jobs at one moment), calendars sharing its schedule, max per day, minimum notice, the booking window (max_days_ahead, up to a year) and the hosts' own calendars. A calendar with options times each one differently, so send option_id (400 lists the options otherwise) and any addon_ids; the answer carries a quote (option, add-ons, length, total, deposit, balance). Full-day calendars also return slot_ends (when each start's job is done). Always call this before creating a booking.\n\nRequired scope: `bookings:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "calendar_id",
            "in": "path",
            "required": true,
            "description": "Calendar id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "A date (2026-11-03) or an ISO time; defaults to today.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "1-42, default 7.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "option_id",
            "in": "query",
            "required": false,
            "description": "The option picked (its id in GET /v1/calendars pricing.options). Required on a calendar with options.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "addon_ids",
            "in": "query",
            "required": false,
            "description": "Add-on ids, comma-separated: they add their time to the job.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "calendar": {
                      "id": "uuid",
                      "name": "Triple T Interior + Exterior",
                      "timezone": "America/Chicago",
                      "duration_minutes": 390,
                      "duration_days": null,
                      "booking_window_days": 120
                    },
                    "from": "2026-11-03T06:00:00.000Z",
                    "to": "2026-11-10T06:00:00.000Z",
                    "from_date": "2026-11-03",
                    "to_date": "2026-11-10",
                    "slots": [
                      "2026-11-03T14:00:00.000Z"
                    ],
                    "days": [
                      {
                        "date": "2026-11-03",
                        "label": "Tue, Nov 3",
                        "slots": [
                          "2026-11-03T14:00:00.000Z"
                        ]
                      }
                    ],
                    "quote": {
                      "option": {
                        "id": "opt-van",
                        "name": "Van"
                      },
                      "addons": [
                        {
                          "id": "add-pet",
                          "name": "Pet hair"
                        }
                      ],
                      "duration_minutes": 390,
                      "duration_days": null,
                      "length_label": "6 hr 30 min",
                      "total_cents": 57500,
                      "deposit_cents": 5000,
                      "balance_cents": 52500,
                      "currency": "usd",
                      "lines": [
                        {
                          "label": "Van",
                          "cents": 53500,
                          "kind": "option"
                        },
                        {
                          "label": "Pet hair",
                          "cents": 4000,
                          "kind": "addon"
                        }
                      ]
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/bookings": {
      "get": {
        "operationId": "list_bookings",
        "tags": [
          "Bookings"
        ],
        "summary": "List appointments",
        "description": "Soonest first. Each appointment has the same fields as everywhere else in the API: id, lead_id, calendar_id, appointment_number (the sub-account's #1001 and up, as the calendar shows it), title, starts_at, ends_at, timezone, lead_timezone, location, notes, meeting_type, meeting_location, meeting_url, host_user_id, status, source, notify_lead, is_arrival_window (booked as an arrival window: starts_at to ends_at is the window), service_details (on a priced calendar: the service, option, add-ons, length, total_cents, deposit_cents, balance_cents and currency; null otherwise), created_at and updated_at.\n\nRequired scope: `bookings:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO start.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO end.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by status.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "Filter by lead.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "bookings": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "lead_id": "uuid",
                        "calendar_id": "uuid",
                        "title": "Estimate",
                        "starts_at": "2026-09-04T17:00:00Z",
                        "ends_at": "2026-09-04T19:00:00Z",
                        "timezone": "America/Chicago",
                        "lead_timezone": "America/Chicago",
                        "location": "12 Main St",
                        "notes": null,
                        "meeting_type": "in_person",
                        "meeting_location": null,
                        "meeting_url": null,
                        "host_user_id": "uuid",
                        "status": "scheduled",
                        "source": "api",
                        "notify_lead": true,
                        "is_arrival_window": true,
                        "service_details": null,
                        "created_at": "2026-09-01T15:00:00Z",
                        "updated_at": "2026-09-01T15:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_booking",
        "tags": [
          "Bookings"
        ],
        "summary": "Book an appointment",
        "description": "Books like the app: rejects a second overlapping appointment for the same lead; calendar_id must be one of the sub-account's calendars (404 otherwise); ends_at must be after starts_at and at most 24 hours later. With a calendar, the slot's capacity is checked, calendars sharing its schedule included (409 with code slot_full; pick a time from GET /v1/calendars/{calendar_id}/availability, or send force: true) and the host is picked by the calendar's round robin. On a priced calendar, option_id and addon_ids set the job's length and price: the booking's notes start with the pick and its service_details carry the option, add-ons, total, deposit and balance; the lead's deal value is filled when it has none (the response adds a quote; a warning says when a calendar with options was booked without one). Bookings made through the API never take the deposit: to collect it, send the customer the booking link. A full-day calendar's job starts at opening time and ends at closing time on its last working day (400 not_a_start_time otherwise, unless ends_at is sent). By default the lead gets the confirmation and reminder texts and the calendar invite (notify_lead: false to skip them). Fires the appointment.booked outbound webhooks.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id."
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "ISO 8601 start time."
                  },
                  "ends_at": {
                    "type": "string",
                    "description": "ISO 8601 end time (instead of duration_minutes; at most 24 h after starts_at, 31 days on a full-day calendar)."
                  },
                  "lead_id": {
                    "type": "string",
                    "description": "Lead to book."
                  },
                  "calendar_id": {
                    "type": "string",
                    "description": "One of the sub-account's calendars."
                  },
                  "option_id": {
                    "type": "string",
                    "description": "The option booked (GET /v1/calendars pricing.options id), e.g. the vehicle size."
                  },
                  "addon_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Add-ons booked (pricing.addons ids)."
                  },
                  "duration_minutes": {
                    "type": "number",
                    "description": "5-1440. Default: the option's length with its add-ons, else the calendar's, else 30."
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA timezone."
                  },
                  "location": {
                    "type": "string",
                    "description": "Meeting location."
                  },
                  "notes": {
                    "type": "string",
                    "description": "Internal notes."
                  },
                  "notify_lead": {
                    "type": "boolean",
                    "description": "Default true: the lead gets the confirmation email with its calendar invite plus the confirmation and reminder texts. false books quietly, and later updates or cancels stay quiet too; the host still gets the booking alert."
                  },
                  "force": {
                    "type": "boolean",
                    "description": "Skip the calendar's capacity check (the database still never double-books a person or a full calendar)."
                  }
                },
                "required": [
                  "workspace_id",
                  "starts_at"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "lead_id": "uuid",
                "calendar_id": "uuid",
                "starts_at": "2026-11-03T14:00:00Z",
                "option_id": "opt-van",
                "addon_ids": [
                  "add-pet"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/bookings/{booking_id}/reschedule": {
      "post": {
        "operationId": "reschedule_booking",
        "tags": [
          "Bookings"
        ],
        "summary": "Move an appointment",
        "description": "Re-times the reminder texts, updates the calendar mirror and the lead's invite, and fires the appointment.rescheduled outbound webhooks when the time changed. The appointment keeps its length; a full-day job runs to closing time on its new last working day. 409 slot_full when the new time is taken.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "booking_id",
            "in": "path",
            "required": true,
            "description": "Booking id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "starts_at": {
                    "type": "string",
                    "description": "New ISO start time."
                  },
                  "duration_minutes": {
                    "type": "number",
                    "description": "Optional new length, 5-1440."
                  }
                },
                "required": [
                  "starts_at"
                ],
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/bookings/{booking_id}/cancel": {
      "post": {
        "operationId": "cancel_booking",
        "tags": [
          "Bookings"
        ],
        "summary": "Cancel an appointment",
        "description": "DELETE /v1/bookings/{booking_id} does the same.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "booking_id",
            "in": "path",
            "required": true,
            "description": "Booking id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "Cancellation reason."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/bookings/{booking_id}": {
      "patch": {
        "operationId": "update_booking",
        "tags": [
          "Bookings"
        ],
        "summary": "Change an appointment's outcome, title, notes, location, host, lead notifications or lead",
        "description": "Everything but the time (use reschedule for that). status records the outcome: completed, no_show or cancelled release the lead from Booked and stop its reminder texts; confirmed puts it back on. host_user_id must be someone on the sub-account's team (GET /v1/members). notify_lead: false silences the lead's remaining reminder texts and invite updates; true turns them back on (an upcoming appointment's reminders are queued again). lead_id links an appointment that has no lead yet (typed in by hand) to a lead of the same sub-account; one that already has a lead answers 409. Linking an upcoming scheduled appointment books it for the lead like a new booking: the lead is marked booked, its follow-ups and AI calls stop, and the confirmation and reminder texts and the invite go out (unless notify_lead is false). A past or finished appointment is only linked.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "booking_id",
            "in": "path",
            "required": true,
            "description": "Booking id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "The appointment's outcome.",
                    "enum": [
                      "confirmed",
                      "completed",
                      "no_show",
                      "cancelled"
                    ]
                  },
                  "title": {
                    "type": "string",
                    "description": "Max 200."
                  },
                  "notes": {
                    "type": "string",
                    "description": "Max 2000. null clears them."
                  },
                  "location": {
                    "type": "string",
                    "description": "Address or place, max 300. null clears it."
                  },
                  "host_user_id": {
                    "type": "string",
                    "description": "Team member who takes it. null for nobody."
                  },
                  "notify_lead": {
                    "type": "boolean",
                    "description": "false: the lead gets no more reminder texts or invite updates for this appointment. true: they do again."
                  },
                  "lead_id": {
                    "type": "string",
                    "description": "Link an appointment with no lead to this lead (same sub-account). Cannot move an appointment from one lead to another."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "status": "completed",
                "notes": "Signed the quote on site."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/members": {
      "get": {
        "operationId": "list_members",
        "tags": [
          "Members"
        ],
        "summary": "List sub-account members and invites",
        "description": "Each member has user_id, role, email and full_name. Use user_id (or the email) to name a team member in busy blocks. Staff and Technicians, who have no Team page in the app, get names and roles only (no emails, no invitations).\n\nRequired scope: `members:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "invite_member",
        "tags": [
          "Members"
        ],
        "summary": "Invite a member",
        "description": "Required scope: `members:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id."
                  },
                  "email": {
                    "type": "string",
                    "description": "Invitee email."
                  },
                  "role": {
                    "type": "string",
                    "description": "client_admin (Admin), client_viewer (Staff) or client_tech (Technician: only their own leads and appointments). Default client_viewer.",
                    "enum": [
                      "client_admin",
                      "client_viewer",
                      "client_tech"
                    ]
                  }
                },
                "required": [
                  "workspace_id",
                  "email"
                ],
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/members/{member_id}": {
      "patch": {
        "operationId": "update_member_role",
        "tags": [
          "Members"
        ],
        "summary": "Change a member's role",
        "description": "You cannot change your own role.\n\nRequired scope: `members:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "member_id",
            "in": "path",
            "required": true,
            "description": "Member id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "description": "client_admin (Admin), client_viewer (Staff) or client_tech (Technician: only their own leads and appointments).",
                    "enum": [
                      "client_admin",
                      "client_viewer",
                      "client_tech"
                    ]
                  }
                },
                "required": [
                  "role"
                ],
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "remove_member",
        "tags": [
          "Members"
        ],
        "summary": "Remove a member",
        "description": "Required scope: `members:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "member_id",
            "in": "path",
            "required": true,
            "description": "Member id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/smart-forms": {
      "get": {
        "operationId": "list_smart_forms",
        "tags": [
          "Smart forms"
        ],
        "summary": "List smart forms",
        "description": "Required scope: `smart_forms:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_smart_form",
        "tags": [
          "Smart forms"
        ],
        "summary": "Create a smart form",
        "description": "Starts unpublished with the same defaults as a form created in the app. The response includes its public path (/f/<slug>), qualifier_questions, custom_fields, qualification, calendar_routing, service_area and estimate. Questions can ask for photos (field_type file) and put a price on answers (option_prices) for the instant estimate: switched on with estimate, the answers' prices add up to a live total in the form's footer, saved on the lead with its total as the deal value. Qualification routes each lead to Qualified, Nurture or Not eligible: yes/no qualifier_questions (asked first when qualifier_enabled is on) and choice questions with disqualify_options or nurture_options. A service area (fields.address.service_area) only accepts addresses in chosen US states or within a radius: address suggestions stay inside it and an out-of-area visitor sees your message and cannot send the form; switching it on shows and requires the address, like the builder. Design and tracking pixels are edited in the app.\n\nRequired scope: `smart_forms:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "name": {
                    "type": "string",
                    "description": "Form name (max 80)."
                  },
                  "headline": {
                    "type": "string",
                    "description": "Max 160."
                  },
                  "subheadline": {
                    "type": "string",
                    "description": "Max 300."
                  },
                  "primary_color": {
                    "type": "string",
                    "description": "#rrggbb."
                  },
                  "bullet_points": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 8 short lines."
                  },
                  "layout_mode": {
                    "type": "string",
                    "description": "The builder's Layout: single_step (one page, the default), multi_step (a step-by-step wizard), conversational (one question at a time, like a chat) or funnel (one big question per screen).",
                    "enum": [
                      "single_step",
                      "multi_step",
                      "conversational",
                      "funnel"
                    ]
                  },
                  "calendar_id": {
                    "type": "string",
                    "description": "Calendar used by the booking step."
                  },
                  "calendar_routing": {
                    "type": "boolean",
                    "description": "Pick the calendar from the answers: the sub-account's calendar routing rules (/v1/calendar-routes) choose which calendar's times the form shows once the deciding question is answered; calendar_id is used when no rule fits."
                  },
                  "booking_enabled": {
                    "type": "boolean",
                    "description": "Show the booking step."
                  },
                  "fields": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Contact fields: {\"email\":{\"required\":false},\"address\":{\"enabled\":false}}. Keys: first_name, last_name, phone, email, address, state (a State / Province pick list), notes. Phone or email must stay on. address also takes service_area (merged): {\"enabled\":true,\"mode\":\"states\",\"states\":[\"TX\",\"OK\"],\"message\":\"Sorry, we only serve Texas and Oklahoma.\"}, or mode \"radius\" with \"center\":{\"lat\":32.7767,\"lng\":-96.797,\"label\":\"Dallas, TX\"} and \"radius_miles\" (1-500, default 25). A blank message shows \"Sorry, we don't service this area yet.\"; null or enabled:false switches it off."
                  },
                  "is_published": {
                    "type": "boolean",
                    "description": "Default false."
                  },
                  "qualifier_enabled": {
                    "type": "boolean",
                    "description": "Ask the qualifier_questions first."
                  },
                  "qualifier_questions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 10, replaces the list: [{ text (max 200), disqualify_on: \"yes\" | \"no\" (default \"no\"), outcome: \"not_eligible\" | \"nurture\" (default \"not_eligible\") }]."
                  },
                  "questions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Custom questions, up to 30, replaces the list: [{ key?, label (max 160), field_type: text | textarea | number | money | phone | email | url | select | multiselect | boolean | date | datetime | rating | file (photos or PDFs, up to 6, shown on the lead), options (select / multiselect: 2-40), option_prices? (select / multiselect: cents per answer for the instant estimate, e.g. {\"SUV\": 8000}; left out keeps the saved prices, null removes them), disqualify_options? or nurture_options? (choice types; boolean answers \"yes\" / \"no\"; never both), placement?: \"qualify\" | \"details\" (qualify = choice types only), option_style?: dropdown | buttons | cards | auto, no_first? (boolean: show No before Yes), required?, enabled?, save_to_crm? (never for file), placeholder?, helper_text? }]."
                  },
                  "qualification": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Merged: { save_not_eligible: boolean, nurture_page: { headline, subheadline, cta_label, cta_url, redirect_url } }."
                  },
                  "estimate": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The instant price estimate, merged: { enabled, label (default \"Your estimate\"), base_cents (added before any answer), note (e.g. \"Final price after we see the job\"), range_percent (0-50: shown as a range that far either side) }. null switches it off."
                  }
                },
                "required": [
                  "workspace_id",
                  "name"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "name": "Roof quote",
                "qualifier_enabled": true,
                "qualifier_questions": [
                  {
                    "text": "Do you own the home?",
                    "disqualify_on": "no",
                    "outcome": "not_eligible"
                  }
                ],
                "questions": [
                  {
                    "label": "When do you want to start?",
                    "field_type": "select",
                    "options": [
                      "This month",
                      "In 1-3 months",
                      "Just researching"
                    ],
                    "nurture_options": [
                      "Just researching"
                    ],
                    "placement": "qualify"
                  },
                  {
                    "label": "Vehicle size",
                    "field_type": "select",
                    "options": [
                      "Car",
                      "SUV",
                      "Truck"
                    ],
                    "option_prices": {
                      "Car": 31500,
                      "SUV": 38500,
                      "Truck": 45000
                    }
                  },
                  {
                    "label": "Photos of the damage",
                    "field_type": "file"
                  }
                ],
                "qualification": {
                  "save_not_eligible": true
                },
                "estimate": {
                  "enabled": true,
                  "note": "Final price after we see the job",
                  "range_percent": 10
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/smart-forms/{form_id}/submissions": {
      "get": {
        "operationId": "list_form_submissions",
        "tags": [
          "Smart forms"
        ],
        "summary": "List form submissions",
        "description": "Required scope: `smart_forms:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "form_id",
            "in": "path",
            "required": true,
            "description": "Form id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 50.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations": {
      "get": {
        "operationId": "list_ws_automations",
        "tags": [
          "CRM automations"
        ],
        "summary": "List sub-account automations",
        "description": "Required scope: `workspace_automations:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_automation",
        "tags": [
          "CRM automations"
        ],
        "summary": "Build an automation",
        "description": "Saved exactly as the Automations page saves it, so it opens and edits in the app. Saved OFF unless enabled is true; once on, its texts and emails go to real leads through the normal quiet hours, opt-out and daily limits. Steps can send lead data out (webhooks, alerts), so this needs an admin-scoped key. Everything it points at (stages, team members, phone numbers, review pages, Facebook pages, Smart Forms, calendars, email senders) must be in the same sub-account; GET /v1/workspace-automations/reference lists them. Any step can take a label (its name on the step card). Steps include drip (let a number of leads through every period, the rest wait in line, within business hours if asked); triggers include custom_field_changed (a custom field changes, optionally to certain values or only by a person). An inbound_webhook automation comes back with its private inbound_url: POST JSON with a phone or email to start it (it must be on), and every field sent can be used in its messages as {field}.\n\nRequired scope: `workspace_automations:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "name": {
                    "type": "string",
                    "description": "Up to 80 characters."
                  },
                  "description": {
                    "type": "string",
                    "description": "Optional note."
                  },
                  "trigger": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "When it starts: {\"type\":\"new_lead\"}, {\"type\":\"stage_changed\",\"to_stages\":[\"booked\"]}, {\"type\":\"tag_added\",\"tags\":[\"hot\"]}, {\"type\":\"smart_form_submitted\",\"form_ids\":[\"Free quote\"]}, {\"type\":\"appointment_booked\",\"calendar_ids\":[\"Estimate visit\"]}, {\"type\":\"inbound_webhook\"}... Ids or names. See GET /v1/workspace-automations/reference."
                  },
                  "only_if": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Entry rules, all must hold: [{\"field\":\"tags\",\"op\":\"has_none\",\"value\":[\"customer\"]}]."
                  },
                  "steps": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "In order: [{\"action\":\"send_text\",\"message\":\"Hi {first_name}\"},{\"action\":\"wait\",\"days\":1},{\"action\":\"send_email\",\"subject\":\"Your quote\",\"message\":\"..\",\"from_sender_id\":\"<email sender id, address or name>\"},{\"action\":\"if\",\"conditions\":[..],\"then\":[..],\"else\":[..]}]. A send_text step takes send_as (auto, imessage or sms) when the sub-account has an iMessage Mac."
                  },
                  "settings": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "{\"stop_on_reply\":true,\"sending_window\":{\"days\":[\"mon\",\"tue\"],\"start\":\"09:00\",\"end\":\"18:00\"}}."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Default false."
                  }
                },
                "required": [
                  "workspace_id",
                  "name",
                  "trigger",
                  "steps"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "name": "Speed to lead",
                "trigger": {
                  "type": "new_lead"
                },
                "steps": [
                  {
                    "action": "send_text",
                    "message": "Hi {first_name}, thanks for reaching out! When is a good time to talk?"
                  },
                  {
                    "action": "wait",
                    "days": 1
                  },
                  {
                    "action": "send_text",
                    "message": "Still interested, {first_name}?"
                  }
                ],
                "settings": {
                  "stop_on_reply": true
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/{automation_id}/enroll": {
      "post": {
        "operationId": "enroll_automation",
        "tags": [
          "CRM automations"
        ],
        "summary": "Enroll leads into an automation",
        "description": "Can send real texts and emails. Confirm the lead list before calling. Leads are queued and start within the sub-account's daily contact limit. A lead already waiting in this automation's queue is not queued again (already_queued). ignore_filters defaults to false: a lead that fails the automation's only_if rules is skipped when the queue reaches it (the app's Enroll dialog defaults to true).\n\nRequired scope: `workspace_automations:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lead_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Lead ids (max 5000)."
                  },
                  "ignore_filters": {
                    "type": "boolean",
                    "description": "Default false: leads that fail the automation's only_if rules are skipped. true = enroll them anyway."
                  }
                },
                "required": [
                  "lead_ids"
                ],
                "additionalProperties": true
              },
              "example": {
                "lead_ids": [
                  "uuid"
                ],
                "ignore_filters": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "queued": 1,
                    "skipped": 0,
                    "already_queued": 0,
                    "automation": {
                      "id": "uuid",
                      "name": "Speed to lead"
                    },
                    "batch_id": "uuid"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/{automation_id}/toggle": {
      "post": {
        "operationId": "toggle_ws_automation",
        "tags": [
          "CRM automations"
        ],
        "summary": "Enable or disable a sub-account automation",
        "description": "Required scope: `workspace_automations:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean",
                    "description": "Omit to flip the current state."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/reference": {
      "get": {
        "operationId": "automation_reference",
        "tags": [
          "CRM automations"
        ],
        "summary": "Every trigger, step, filter and setting the builder accepts",
        "description": "Read this before building: trigger types and their filters, step actions and their fields, entry-rule fields and operators (with every lead source), settings, message variables, and what this sub-account has for a definition to point at: stages, team, custom fields, phone numbers, review funnel, Facebook pages, Smart Forms, form names (for form_name rules), calendars and email senders.\n\nRequired scope: `workspace_automations:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/{automation_id}": {
      "get": {
        "operationId": "get_automation",
        "tags": [
          "CRM automations"
        ],
        "summary": "One automation as {trigger, only_if, steps, settings}",
        "description": "The same shape create and update take (step names included), with a ref on every step so an update can keep steps unchanged ({\"keep\":\"<ref>\"}). Webhook header values are hidden. An inbound_webhook automation includes its inbound_url; viewers see only its host, like webhook step links.\n\nRequired scope: `workspace_automations:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "update_automation",
        "tags": [
          "CRM automations"
        ],
        "summary": "Change an automation",
        "description": "Send only what changes. steps replaces all steps: keep unchanged ones with {\"keep\":\"<ref>\"} from GET. settings changes only the keys sent. enabled turns it on or off.\n\nRequired scope: `workspace_automations:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New name."
                  },
                  "trigger": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "New trigger."
                  },
                  "only_if": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "New entry rules."
                  },
                  "steps": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "New steps (replaces all)."
                  },
                  "settings": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Settings to change."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "On or off."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_automation",
        "tags": [
          "CRM automations"
        ],
        "summary": "Delete an automation",
        "description": "Leads inside it stop where they are.\n\nRequired scope: `workspace_automations:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/{automation_id}/duplicate": {
      "post": {
        "operationId": "duplicate_automation",
        "tags": [
          "CRM automations"
        ],
        "summary": "Copy an automation",
        "description": "The copy is saved OFF unless enabled is true.\n\nRequired scope: `workspace_automations:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The copy's name."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Default false."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/folders": {
      "get": {
        "operationId": "list_automation_folders",
        "tags": [
          "CRM automations"
        ],
        "summary": "List automation folders",
        "description": "The folders on the Automations page in their order, the automations in each, and the ones at the top level. GET /v1/workspace-automations also takes folder_id (or none for the top level).\n\nRequired scope: `workspace_automations:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "folders": [
                      {
                        "id": "uuid",
                        "name": "Reminders",
                        "position": 1,
                        "created_at": "2026-10-26T10:00:00Z",
                        "automations": [
                          {
                            "id": "uuid",
                            "name": "24 h reminder",
                            "enabled": true
                          }
                        ]
                      }
                    ],
                    "top_level": [
                      {
                        "id": "uuid",
                        "name": "New lead welcome",
                        "enabled": true
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_automation_folder",
        "tags": [
          "CRM automations"
        ],
        "summary": "Create an automation folder",
        "description": "Added after the existing folders. Owners and admins.\n\nRequired scope: `workspace_automations:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "name": {
                    "type": "string",
                    "description": "Up to 60 characters."
                  }
                },
                "required": [
                  "workspace_id",
                  "name"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "name": "Reminders"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "folder": {
                      "id": "uuid",
                      "name": "Reminders",
                      "position": 2,
                      "created_at": "2026-10-29T10:00:00Z"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/folders/{folder_id}": {
      "patch": {
        "operationId": "rename_automation_folder",
        "tags": [
          "CRM automations"
        ],
        "summary": "Rename an automation folder",
        "description": "Required scope: `workspace_automations:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "description": "The folder.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Up to 60 characters."
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": true
              },
              "example": {
                "name": "Appointment reminders"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "folder": {
                      "id": "uuid",
                      "name": "Appointment reminders"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_automation_folder",
        "tags": [
          "CRM automations"
        ],
        "summary": "Delete an automation folder",
        "description": "Its automations move to the top level, unchanged (nothing is turned off or deleted).\n\nRequired scope: `workspace_automations:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "description": "The folder.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "uuid",
                    "deleted": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/move": {
      "post": {
        "operationId": "move_automations",
        "tags": [
          "CRM automations"
        ],
        "summary": "Move automations into a folder",
        "description": "Puts the automations into the folder, or back to the top level with folder_id null. Only where they show on the page changes.\n\nRequired scope: `workspace_automations:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "automation_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 100 automations of the sub-account."
                  },
                  "folder_id": {
                    "type": "string",
                    "description": "The folder, or null for the top level."
                  }
                },
                "required": [
                  "workspace_id",
                  "automation_ids",
                  "folder_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "automation_ids": [
                  "uuid"
                ],
                "folder_id": "uuid"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "moved": 1,
                    "folder_id": "uuid"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/{automation_id}/enrollments": {
      "get": {
        "operationId": "list_automation_enrollments",
        "tags": [
          "CRM automations"
        ],
        "summary": "Who is inside an automation right now",
        "description": "Leads waiting on a step or mid-step, newest first, with the time their next step runs, plus how many are still waiting to be enrolled.\n\nRequired scope: `workspace_automations:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 100.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "automation": {
                      "id": "uuid",
                      "name": "Speed to lead",
                      "steps_total": 3
                    },
                    "enrollments": [
                      {
                        "run_id": "uuid",
                        "lead": {
                          "id": "uuid",
                          "full_name": "Jane Doe",
                          "phone": "+14155550123"
                        },
                        "status": "waiting",
                        "step_index": 1,
                        "started_at": "2026-09-30T14:00:00Z",
                        "next_step_at": "2026-10-01T14:00:00Z"
                      }
                    ],
                    "waiting_to_enroll": 0,
                    "has_more": false,
                    "next_cursor": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspace-automations/{automation_id}/unenroll": {
      "post": {
        "operationId": "unenroll_automation",
        "tags": [
          "CRM automations"
        ],
        "summary": "Take leads out of an automation",
        "description": "Like Remove from workflow in the app: their run stops, queued steps (texts, emails) are cancelled and the lead's timeline notes it. Nothing is sent.\n\nRequired scope: `workspace_automations:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "Automation id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lead_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 500 lead ids."
                  },
                  "all": {
                    "type": "boolean",
                    "description": "true = everyone in it (instead of lead_ids)."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "lead_ids": [
                  "uuid"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "automation_id": "uuid",
                    "removed": 1,
                    "waiting_cancelled": 0
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}/workflows": {
      "get": {
        "operationId": "list_lead_workflows",
        "tags": [
          "CRM automations"
        ],
        "summary": "The automations a lead is inside",
        "description": "Required scope: `workspace_automations:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lead_id": "uuid",
                    "workflows": [
                      {
                        "run_id": "uuid",
                        "automation_id": "uuid",
                        "name": "Speed to lead",
                        "status": "waiting",
                        "step_index": 1,
                        "started_at": "2026-09-30T14:00:00Z",
                        "next_step_at": "2026-10-01T14:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces": {
      "get": {
        "operationId": "list_workspaces",
        "tags": [
          "Workspaces"
        ],
        "summary": "List sub-accounts",
        "description": "Required scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_workspace",
        "tags": [
          "Workspaces"
        ],
        "summary": "Create a sub-account",
        "description": "Send industry (recommended: the AI and the setup checklist use it; default Other). When the plan's sub-account limit is reached the call answers 402 with code plan_limit.\n\nRequired scope: `workspaces:write`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Sub-account name (max 120)."
                  },
                  "industry": {
                    "type": "string",
                    "description": "e.g. Roofing (max 80). Default Other."
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA zone. Default America/New_York."
                  },
                  "brand_id": {
                    "type": "string",
                    "description": "Brand to link (GET /v1/brands). With snapshot_id, defaults to the snapshot source's brand."
                  },
                  "logo_url": {
                    "type": "string",
                    "description": "Public logo URL. Becomes the sub-account's Client logo (sub-account list, CRM header, invoices)."
                  },
                  "snapshot_id": {
                    "type": "string",
                    "description": "Start from one of your agency's snapshots (GET /v1/snapshots): pipeline, fields, calendars, forms, automations, AI settings and the rest load into the new sub-account. Agency side only. The snapshot's logo, app icon and sign-in page are never copied. If it cannot load, the sub-account is not created."
                  },
                  "snapshot_sections": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Load only these parts of the snapshot: pipeline_stages, custom_fields, tags, calendars, availability, offers, smart_forms, playbooks, voice_greetings, messaging, ai_receptionist, ai_calling, lead_routing, instant_lead_connect, price_book, invoice_settings, automations, landing_pages, site_widgets, review_funnel, ai_agents, contract_templates, contract_variables. Default: everything in it."
                  },
                  "snapshot_include_business": {
                    "type": "boolean",
                    "description": "Also copy the snapshot source's business address, phone and email. Default false."
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": true
              },
              "example": {
                "name": "Acme Roofing",
                "industry": "Roofing",
                "timezone": "America/New_York",
                "snapshot_id": "uuid"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace": {
                      "id": "uuid",
                      "name": "Acme Roofing",
                      "industry": "Roofing",
                      "timezone": "America/New_York"
                    },
                    "snapshot": {
                      "id": "uuid",
                      "name": "Roofing starter",
                      "sections": {
                        "pipeline_stages": {
                          "inserted": 7,
                          "updated": 0,
                          "skipped": 0
                        },
                        "automations": {
                          "inserted": 4,
                          "updated": 0,
                          "skipped": 0
                        }
                      },
                      "warnings": []
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces/{workspace_id}": {
      "get": {
        "operationId": "get_workspace",
        "tags": [
          "Workspaces"
        ],
        "summary": "Get one sub-account",
        "description": "Required scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "update_workspace",
        "tags": [
          "Workspaces"
        ],
        "summary": "Update a sub-account",
        "description": "Send only what changes: name, timezone, industry, logo_url, brand_id, archived, notify_on_appointments, enabled_features, hidden_features. logo_url is the sub-account's Client logo, the same image as Settings → Client logo & app icon: changing either changes both (null removes it). enabled_features is Settings > Features: tools only some businesses use, off until turned on (today: sales, the Sales page and the parts / tip / technician on won jobs; plans, Plans & memberships on won jobs); owners and admins. hidden_features is what the agency hides from the sub-account's own people (client admins, Staff and Technicians lose those pages, and their API keys get 403 feature_off on those routes): leads, messages, appointments, calendars, smart_forms, media, campaigns, calls, ai_voice, automations, landing_pages, invoices, sales, activity, team, ad_insights, tools, support, agent. archived, brand_id and hidden_features are the agency's: the owner or an agency admin. Both take { key: true | false } and change only the keys sent.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "logo_url": {
                    "type": "string",
                    "description": "Client logo URL, or null to remove it."
                  },
                  "enabled_features": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Settings > Features, e.g. { \"sales\": true }."
                  },
                  "hidden_features": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Hidden from the client's people, e.g. { \"invoices\": true, \"team\": false }. Owner or agency admin."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "enabled_features": {
                  "sales": true
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_workspace",
        "tags": [
          "Workspaces"
        ],
        "summary": "Delete a sub-account",
        "description": "Needs a confirmation token (see Confirmation tokens). Releases the sub-account's phone numbers. It can be restored for 7 days.\n\nRequired scope: `workspaces:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/snapshots": {
      "get": {
        "operationId": "list_snapshots",
        "tags": [
          "Workspaces"
        ],
        "summary": "List your agency's snapshots",
        "description": "Saved sub-account setups (Sub-accounts → Snapshots in the app) that POST /v1/workspaces (snapshot_id) and POST /v1/workspaces/{workspace_id}/snapshot load. summary counts what each holds, per section. Not available to a key restricted to one sub-account.\n\nRequired scope: `workspaces:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "snapshots": [
                      {
                        "id": "uuid",
                        "name": "Roofing starter",
                        "description": null,
                        "source_workspace_id": "uuid",
                        "source_workspace_name": "Acme Roofing",
                        "summary": {
                          "pipeline_stages": 7,
                          "calendars": 2,
                          "smart_forms": 1,
                          "automations": 4
                        },
                        "created_at": "2026-10-16T12:00:00Z",
                        "updated_at": "2026-10-16T12:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces/{workspace_id}/snapshot": {
      "post": {
        "operationId": "apply_snapshot",
        "tags": [
          "Workspaces"
        ],
        "summary": "Load a snapshot into an existing sub-account",
        "description": "Agency side only (the sub-account owner or an agency admin). Items with the same name are kept (conflict_policy skip, the default) or overwritten (replace); everything else is added. The sub-account keeps its own name, logo, app icon, sign-in page, leads, people, numbers and connected accounts. Answers with what was added, updated and skipped per section.\n\nRequired scope: `workspaces:write`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "snapshot_id": {
                    "type": "string",
                    "description": "Snapshot id (GET /v1/snapshots)."
                  },
                  "sections": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Only these parts: pipeline_stages, custom_fields, tags, calendars, availability, offers, smart_forms, playbooks, voice_greetings, messaging, ai_receptionist, ai_calling, lead_routing, instant_lead_connect, price_book, invoice_settings, automations, landing_pages, site_widgets, review_funnel, ai_agents, contract_templates, contract_variables. Default: everything in the snapshot."
                  },
                  "conflict_policy": {
                    "type": "string",
                    "description": "skip (default) keeps this sub-account's same-named items; replace overwrites them.",
                    "enum": [
                      "skip",
                      "replace"
                    ]
                  },
                  "include_settings": {
                    "type": "boolean",
                    "description": "Also apply the snapshot's sub-account settings (timezone, hours, colors, notification and feature switches). Default true."
                  },
                  "include_business": {
                    "type": "boolean",
                    "description": "Also copy the business address, phone and email. Default false."
                  }
                },
                "required": [
                  "snapshot_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "snapshot_id": "uuid",
                "sections": [
                  "automations",
                  "smart_forms"
                ],
                "conflict_policy": "skip"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace_id": "uuid",
                    "snapshot": {
                      "id": "uuid",
                      "name": "Roofing starter",
                      "sections": {
                        "automations": {
                          "inserted": 3,
                          "updated": 0,
                          "skipped": 1
                        },
                        "smart_forms": {
                          "inserted": 1,
                          "updated": 0,
                          "skipped": 0
                        }
                      },
                      "warnings": []
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces/{workspace_id}/outreach": {
      "post": {
        "operationId": "configure_outreach",
        "tags": [
          "Workspaces"
        ],
        "summary": "Configure outreach (auto-greet, AI replies, sending identity)",
        "description": "Raw column names, for anyone who can manage the sub-account; PATCH on the same path does the same. Prefer PATCH /v1/ai-texting and /v1/nurture-sequence, which take friendly names and check every value. Accepted fields: auto_greet_new_leads, first_message_template, sms_template, greet_delay_minutes_smart_form, greet_delay_minutes_default, greet_window_mode / _start / _end, followup_window_mode / _start / _end, ai_enabled, ai_system_prompt, ai_business_context, ai_agent_name, ai_booking_link, ai_max_auto_replies, reply_delay_minutes, respect_business_hours, quiet_hours_enabled / _start / _end, handoff_on_unknown, handoff_pause_ai, handoff_resume_days, ai_can_cancel_appointments, ai_cancel_retention_enabled, ai_cancel_retention_note, ai_auto_resume_minutes, nurture_enabled, nurture_steps, email_enabled, email_from_address, email_from_name, email_subject, email_template, sms_enabled, missed_call_text_enabled, missed_call_text_body, reminder_24h_enabled / _template, reminder_1h_enabled / _template, reminder_5m_enabled / _template (the 5-minute reminder, off by default), customer_confirmation_enabled / _template, appointment_email_enabled, appointment_email_language (en | es). Unknown fields are ignored; text is capped at 20,000 characters.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "auto_greet_new_leads": {
                    "type": "boolean",
                    "description": "Text new leads automatically."
                  },
                  "first_message_template": {
                    "type": "string",
                    "description": "The first text."
                  },
                  "ai_enabled": {
                    "type": "boolean",
                    "description": "AI replies on or off."
                  },
                  "ai_agent_name": {
                    "type": "string",
                    "description": "Name the AI texts as."
                  },
                  "email_from_name": {
                    "type": "string",
                    "description": "Sender name on emails."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "auto_greet_new_leads": true,
                "ai_enabled": true,
                "ai_agent_name": "Sarah"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/client-access": {
      "get": {
        "operationId": "list_client_access",
        "tags": [
          "Workspaces"
        ],
        "summary": "Lock & pause: every sub-account's access",
        "description": "Your agency's rule (Payments > \"Lock their CRM\": lock_when_unpaid, past_due_grace_days, and can_change: only the agency owner changes it) and each of your sub-accounts you run as the agency (the owner or an agency admin), locked ones first: locked, reason (paused: you paused it; unpaid: its plan's payment failed and the grace days are over; plan_ended: its plan was canceled or ran out; no_plan: the plan you attached was never started), locked_at, the pause and its message, plan_required (its own rule; null = your agency's) and its plan's status. While a sub-account is locked its own people see a lock screen, nothing is sent for them except by your team's hand, and their API keys get 403 `account_locked`; your team keeps full access. A lock for not paying lifts by itself once they pay. Not available to a key restricted to one sub-account.\n\nRequired scope: `billing:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Which sub-accounts. Default all.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "locked",
                "open"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "rule": {
                      "lock_when_unpaid": true,
                      "past_due_grace_days": 7,
                      "can_change": true
                    },
                    "counts": {
                      "total": 12,
                      "locked": 2,
                      "paused": 1,
                      "open": 10
                    },
                    "sub_accounts": [
                      {
                        "workspace_id": "uuid",
                        "name": "Acme Roofing",
                        "archived": false,
                        "locked": true,
                        "reason": "unpaid",
                        "reason_text": "Locked: payment past due",
                        "locked_at": "2026-10-27T09:00:00Z",
                        "paused": false,
                        "paused_at": null,
                        "pause_message": null,
                        "plan_required": null,
                        "plan_id": "uuid",
                        "plan_status": "past_due",
                        "past_due_since": "2026-10-20T09:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "set_client_access_default",
        "tags": [
          "Workspaces"
        ],
        "summary": "Lock & pause: your agency's rule",
        "description": "Payments > \"Lock their CRM\": lock_when_unpaid locks the CRM of every sub-account whose plan payment failed more than past_due_grace_days ago (0-30, default 7; the same grace days also apply to pausing automations and to add-ons after a failed payment) or whose plan ended. Each sub-account can override it (PATCH /v1/workspaces/{workspace_id}/access). Every sub-account is checked again at once; locked_sub_accounts says how many are locked now. Agency owner only.\n\nRequired scope: `billing:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lock_when_unpaid": {
                    "type": "boolean",
                    "description": "Lock clients whose plan is unpaid or ended."
                  },
                  "past_due_grace_days": {
                    "type": "number",
                    "description": "Days after a failed payment before the lock, 0-30."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "lock_when_unpaid": true,
                "past_due_grace_days": 5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "rule": {
                      "lock_when_unpaid": true,
                      "past_due_grace_days": 5,
                      "can_change": true
                    },
                    "locked_sub_accounts": 2
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces/{workspace_id}/access": {
      "get": {
        "operationId": "get_workspace_access",
        "tags": [
          "Workspaces"
        ],
        "summary": "Lock & pause: one sub-account",
        "description": "Billing > Lock & pause: whether the sub-account's people are locked out and why (see GET /v1/client-access), since when, your pause and its message, who paused it, plan_required (true / false, null = your agency's rule) and locks_when_unpaid (what applies now), the plan it pays for or must start, its plan's status, and plans_available: the monthly or yearly plans set up for card payments that can be attached. The owner or an agency admin of the sub-account.\n\nRequired scope: `billing:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "access": {
                      "workspace_id": "uuid",
                      "name": "Acme Roofing",
                      "archived": false,
                      "locked": true,
                      "reason": "paused",
                      "reason_text": "Paused by the agency",
                      "locked_at": "2026-10-28T15:02:11Z",
                      "paused": true,
                      "paused_at": "2026-10-28T15:02:11Z",
                      "pause_message": "Your account is paused. Call us at (555) 010-2030.",
                      "plan_required": null,
                      "plan_id": "uuid",
                      "plan_status": "active",
                      "past_due_since": null,
                      "paused_by_user_id": "uuid",
                      "locks_when_unpaid": true,
                      "agency_rule": {
                        "lock_when_unpaid": true,
                        "past_due_grace_days": 7
                      },
                      "plan": {
                        "id": "uuid",
                        "name": "Growth",
                        "amount_cents": 29700,
                        "currency": "usd",
                        "interval": "month",
                        "interval_count": 1
                      },
                      "billing": {
                        "provider": "stripe",
                        "has_plan_subscription": true,
                        "plan_status": "active",
                        "past_due_since": null
                      },
                      "plans_available": [
                        {
                          "id": "uuid",
                          "name": "Growth",
                          "amount_cents": 29700,
                          "currency": "usd",
                          "interval": "month",
                          "interval_count": 1
                        }
                      ]
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "set_workspace_access",
        "tags": [
          "Workspaces"
        ],
        "summary": "Lock & pause: one sub-account's rule",
        "description": "plan_required: true locks its CRM when its plan isn't paid (and, with a plan attached that was never started, until it starts it); false never locks it for that; null follows your agency's rule. plan_id attaches the plan it has to pay for (plans_available from GET /v1/workspaces/{workspace_id}/access), or null; not while it already pays for a plan (409). The client pays from its lock screen, on your payment account, and the lock lifts once it does. Send either or both. Returns the access as GET shows it. The owner or an agency admin of the sub-account.\n\nRequired scope: `billing:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "plan_required": {
                    "type": "boolean",
                    "description": "true / false, or null for your agency's rule."
                  },
                  "plan_id": {
                    "type": "string",
                    "description": "The plan it must pay for, or null."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "plan_required": true,
                "plan_id": "uuid"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "access": {
                      "workspace_id": "uuid",
                      "name": "Acme Roofing",
                      "archived": false,
                      "locked": true,
                      "reason": "no_plan",
                      "reason_text": "Locked: the plan the agency attached was never started",
                      "locked_at": "2026-10-28T15:02:11Z",
                      "paused": false,
                      "paused_at": null,
                      "pause_message": null,
                      "plan_required": true,
                      "plan_id": "uuid",
                      "plan_status": null,
                      "past_due_since": null,
                      "paused_by_user_id": null,
                      "locks_when_unpaid": true,
                      "agency_rule": {
                        "lock_when_unpaid": true,
                        "past_due_grace_days": 7
                      },
                      "plan": {
                        "id": "uuid",
                        "name": "Growth",
                        "amount_cents": 29700,
                        "currency": "usd",
                        "interval": "month",
                        "interval_count": 1
                      },
                      "billing": {
                        "provider": null,
                        "has_plan_subscription": false,
                        "plan_status": null,
                        "past_due_since": null
                      },
                      "plans_available": [
                        {
                          "id": "uuid",
                          "name": "Growth",
                          "amount_cents": 29700,
                          "currency": "usd",
                          "interval": "month",
                          "interval_count": 1
                        }
                      ]
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces/{workspace_id}/access/pause": {
      "post": {
        "operationId": "pause_workspace_access",
        "tags": [
          "Workspaces"
        ],
        "summary": "Pause a sub-account",
        "description": "Its people see a pause screen with your message instead of their CRM; texts, AI replies and their API keys stop (403 `account_locked`), and only your team can send anything for it by hand. Your team keeps full access. Pausing a paused sub-account changes the message. Undo with POST /v1/workspaces/{workspace_id}/access/resume. The owner or an agency admin of the sub-account.\n\nRequired scope: `billing:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "What the client sees, at most 600 characters."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "message": "Your account is paused. Call us at (555) 010-2030."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "access": {
                      "workspace_id": "uuid",
                      "name": "Acme Roofing",
                      "archived": false,
                      "locked": true,
                      "reason": "paused",
                      "reason_text": "Paused by the agency",
                      "locked_at": "2026-10-28T15:02:11Z",
                      "paused": true,
                      "paused_at": "2026-10-28T15:02:11Z",
                      "pause_message": "Your account is paused. Call us at (555) 010-2030.",
                      "plan_required": null,
                      "plan_id": "uuid",
                      "plan_status": "active",
                      "past_due_since": null,
                      "paused_by_user_id": "uuid",
                      "locks_when_unpaid": true,
                      "agency_rule": {
                        "lock_when_unpaid": true,
                        "past_due_grace_days": 7
                      },
                      "plan": {
                        "id": "uuid",
                        "name": "Growth",
                        "amount_cents": 29700,
                        "currency": "usd",
                        "interval": "month",
                        "interval_count": 1
                      },
                      "billing": {
                        "provider": "stripe",
                        "has_plan_subscription": true,
                        "plan_status": "active",
                        "past_due_since": null
                      },
                      "plans_available": [
                        {
                          "id": "uuid",
                          "name": "Growth",
                          "amount_cents": 29700,
                          "currency": "usd",
                          "interval": "month",
                          "interval_count": 1
                        }
                      ]
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces/{workspace_id}/access/resume": {
      "post": {
        "operationId": "resume_workspace_access",
        "tags": [
          "Workspaces"
        ],
        "summary": "Unpause a sub-account",
        "description": "Ends your pause: its people use the CRM as before, unless it is still locked for not paying (reason then says why). The owner or an agency admin of the sub-account.\n\nRequired scope: `billing:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "access": {
                      "workspace_id": "uuid",
                      "name": "Acme Roofing",
                      "archived": false,
                      "locked": false,
                      "reason": null,
                      "reason_text": null,
                      "locked_at": null,
                      "paused": false,
                      "paused_at": null,
                      "pause_message": null,
                      "plan_required": null,
                      "plan_id": "uuid",
                      "plan_status": "active",
                      "past_due_since": null,
                      "paused_by_user_id": null,
                      "locks_when_unpaid": true,
                      "agency_rule": {
                        "lock_when_unpaid": true,
                        "past_due_grace_days": 7
                      },
                      "plan": {
                        "id": "uuid",
                        "name": "Growth",
                        "amount_cents": 29700,
                        "currency": "usd",
                        "interval": "month",
                        "interval_count": 1
                      },
                      "billing": {
                        "provider": "stripe",
                        "has_plan_subscription": true,
                        "plan_status": "active",
                        "past_due_since": null
                      },
                      "plans_available": [
                        {
                          "id": "uuid",
                          "name": "Growth",
                          "amount_cents": 29700,
                          "currency": "usd",
                          "interval": "month",
                          "interval_count": 1
                        }
                      ]
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/addons": {
      "get": {
        "operationId": "list_addons",
        "tags": [
          "Workspaces"
        ],
        "summary": "Add-ons you sell",
        "description": "Agency > Plans & add-ons (iMessage today): your rule for each add-on (mode: included for everyone, subscription: a price by card with its price, or credits: credits_per_month from the sub-account's credit balance), how many have it, pay, have it free or use it, the plans that include it, and each of your sub-accounts you run as the agency: has_it, via (included, plan, comp, stripe, whop or credits), state (Included, In their plan, Free, Paying, Free trial, Payment failed, Ending, Not added), its period and price, in_use (a Mac is connected) and credits_on (its credit balance is on, which credits add-ons need). credits shows your credit price. Not available to a key restricted to one sub-account.\n\nRequired scope: `billing:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "addons": [
                      {
                        "feature": "imessage",
                        "name": "iMessage",
                        "mode": "credits",
                        "price": null,
                        "credits_per_month": 150,
                        "updated_at": "2026-10-25T13:00:00Z",
                        "summary": {
                          "has_it": 3,
                          "paying": 2,
                          "free": 1,
                          "in_use": 3,
                          "plans_including": [
                            "Growth"
                          ]
                        },
                        "sub_accounts": [
                          {
                            "workspace_id": "uuid",
                            "name": "Acme Roofing",
                            "has_it": true,
                            "via": "credits",
                            "state": "Paying",
                            "status": "active",
                            "source": "credits",
                            "current_period_end": "2026-11-25T13:00:00Z",
                            "cancel_at_period_end": false,
                            "past_due_since": null,
                            "amount_cents": null,
                            "currency": null,
                            "interval": "month",
                            "credits_per_month": 150,
                            "in_use": true,
                            "credits_on": true,
                            "plan_includes": false
                          }
                        ]
                      }
                    ],
                    "credits": {
                      "configured": true,
                      "credit_price": 0.1,
                      "currency": "usd"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/addons/{feature}": {
      "put": {
        "operationId": "set_addon_rule",
        "tags": [
          "Workspaces"
        ],
        "summary": "Set how sub-accounts get an add-on",
        "description": "mode included gives it to every sub-account; credits takes credits_per_month (up to 100000) from a sub-account's credit balance each month once it turns the add-on on (it needs its credit balance on). keep_free_for lists sub-accounts (already using it) that keep it free. A price by card is set in the app (Agency > Plans & add-ons), which creates it on your payment account: sending mode subscription answers 400. Sub-accounts that pay by card keep paying until canceled in the app (still_paying_by_card). The agency (its owner or team).\n\nRequired scope: `billing:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "feature",
            "in": "path",
            "required": true,
            "description": "The add-on.",
            "schema": {
              "type": "string",
              "enum": [
                "imessage"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "description": "How sub-accounts get it.",
                    "enum": [
                      "included",
                      "credits"
                    ]
                  },
                  "credits_per_month": {
                    "type": "number",
                    "description": "With mode credits: credits a month, more than 0."
                  },
                  "keep_free_for": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "With mode credits: sub-account ids that keep it free (not ones that already pay)."
                  }
                },
                "required": [
                  "mode"
                ],
                "additionalProperties": true
              },
              "example": {
                "mode": "credits",
                "credits_per_month": 150,
                "keep_free_for": [
                  "uuid"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "addon": {
                      "feature": "imessage",
                      "name": "iMessage",
                      "mode": "credits",
                      "price": null,
                      "credits_per_month": 150,
                      "updated_at": "2026-10-29T12:00:00Z"
                    },
                    "kept_free": 1,
                    "not_kept_free": 0,
                    "still_paying_by_card": 0
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/workspaces/{workspace_id}/addons/{feature}": {
      "patch": {
        "operationId": "update_workspace_addon",
        "tags": [
          "Workspaces"
        ],
        "summary": "Give an add-on free, or end it",
        "description": "One change: free true gives the sub-account the add-on free (not while it pays for it: 409); free false ends that free pass. cancel_at_period_end true ends a credits add-on when its month is up (false keeps it going); a free pass ends at once. A card add-on is canceled or kept in the app (409). Returns the sub-account's add-on and has_it. The owner or an agency admin of the sub-account.\n\nRequired scope: `billing:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "path",
            "required": true,
            "description": "Sub-account id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "feature",
            "in": "path",
            "required": true,
            "description": "The add-on.",
            "schema": {
              "type": "string",
              "enum": [
                "imessage"
              ]
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "free": {
                    "type": "boolean",
                    "description": "true gives it free, false ends the free pass."
                  },
                  "cancel_at_period_end": {
                    "type": "boolean",
                    "description": "A credits add-on: true ends it with its month, false keeps it."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "free": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "changed": true,
                    "addon": {
                      "workspace_id": "uuid",
                      "feature": "imessage",
                      "has_it": true,
                      "status": "comped",
                      "source": "comp",
                      "current_period_end": null,
                      "cancel_at_period_end": false,
                      "past_due_since": null,
                      "note": "Given free by the agency"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/setup-status": {
      "get": {
        "operationId": "setup_status",
        "tags": [
          "Setup"
        ],
        "summary": "What is set up, and what is missing",
        "description": "Start here. Returns the sub-account's real configuration (numbers, AI texting, receptionist, calendars, forms, automations, offers, team, lead sources, integrations) and a severity-ranked list of findings. Each finding names the API call and MCP tool that fixes it, or says when it can only be done in the app (buying a number, SMS registration, connecting Google or Outlook).\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/offers": {
      "get": {
        "operationId": "list_offers",
        "tags": [
          "Offers"
        ],
        "summary": "List offers",
        "description": "Offers (offer playbooks) tell the AI what this business sells to a lead: the first text, the follow-up sequence, notes for the AI and the booking link.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_offer",
        "tags": [
          "Offers"
        ],
        "summary": "Create an offer",
        "description": "Required scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "name": {
                    "type": "string",
                    "description": "Internal name (max 80)."
                  },
                  "offer_label": {
                    "type": "string",
                    "description": "How the offer is described to the lead (max 200)."
                  },
                  "first_message_template": {
                    "type": "string",
                    "description": "First text to a new lead. Supports {{first_name}} (max 1600)."
                  },
                  "nurture_enabled": {
                    "type": "boolean",
                    "description": "null follows the sub-account's own follow-up setting."
                  },
                  "nurture_steps": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 12 follow-ups: { offset_minutes, body_template }. offset_minutes counts from the previous message."
                  },
                  "ai_context_note": {
                    "type": "string",
                    "description": "What the AI should know about this offer (max 2000)."
                  },
                  "booking_link": {
                    "type": "string",
                    "description": "Public http(s) URL."
                  },
                  "is_active": {
                    "type": "boolean",
                    "description": "Default true."
                  },
                  "smart_first_text_enabled": {
                    "type": "boolean",
                    "description": "true: the AI writes the first text for these leads (first_message_template is the backup); false: first_message_template goes out as written; null (default) follows the sub-account's smart first text switch."
                  },
                  "smart_first_text_instructions": {
                    "type": "string",
                    "description": "What the smart first text should do for these leads (max 1000). Empty uses the sub-account's instructions."
                  },
                  "triggers": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Which leads get this offer: { type, value, label? }. type is meta_form, smart_form, webhook (a lead source id) or source (the lead's source text). Form and lead-source ids must belong to the sub-account."
                  }
                },
                "required": [
                  "workspace_id",
                  "name"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "name": "Free roof inspection",
                "offer_label": "a free 20-point roof inspection",
                "first_message_template": "Hi {{first_name}}, thanks for asking about a free roof inspection. What day works best?",
                "nurture_steps": [
                  {
                    "offset_minutes": 60,
                    "body_template": "Still want that inspection?"
                  }
                ],
                "triggers": [
                  {
                    "type": "source",
                    "value": "Website form"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/offers/{offer_id}": {
      "patch": {
        "operationId": "update_offer",
        "tags": [
          "Offers"
        ],
        "summary": "Update an offer",
        "description": "Send only what changes. `triggers`, when sent, replaces the whole list.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offer_id",
            "in": "path",
            "required": true,
            "description": "Offer id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_offer",
        "tags": [
          "Offers"
        ],
        "summary": "Delete an offer",
        "description": "Required scope: `workspaces:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offer_id",
            "in": "path",
            "required": true,
            "description": "Offer id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calendars/{calendar_id}": {
      "patch": {
        "operationId": "update_calendar",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Update a booking calendar",
        "description": "Takes the same fields as create; send only what changes. `hosts`, when sent, replaces the pool; people who stay keep their place in the rotation. An arrival_window calendar always keeps buffer_minutes at 0 (a full-day one keeps its buffer). `appointment_texts` changes only the texts it names. pricing, design and fields merge; pricing.options, pricing.addons and questions replace their lists. Turning pricing off also stops taking payment at booking.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "calendar_id",
            "in": "path",
            "required": true,
            "description": "Calendar id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": true
              },
              "example": {
                "pricing": {
                  "deposit": {
                    "mode": "percent",
                    "percent": 20
                  }
                },
                "design": {
                  "colors": {
                    "button": "#d4a63a"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calendars/{calendar_id}/copy-design": {
      "post": {
        "operationId": "copy_calendar_design",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Copy a calendar's design to others",
        "description": "The Design tab's \"Use on other calendars\": this calendar's design goes onto the calendars named (or every other calendar of the sub-account with all: true), switched on. A calendar that has its own cover photo keeps it. The calendar must have a design switched on.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "calendar_id",
            "in": "path",
            "required": true,
            "description": "The calendar whose design is copied.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "calendar_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "The calendars to copy it to."
                  },
                  "all": {
                    "type": "boolean",
                    "description": "Every other calendar of the sub-account instead."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "all": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "from_calendar_id": "uuid",
                    "copied_to": [
                      {
                        "id": "uuid",
                        "name": "Quick wash"
                      }
                    ],
                    "design": {
                      "enabled": true,
                      "preset": "showroom",
                      "mode": "dark"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calendars/{calendar_id}/design/generate": {
      "post": {
        "operationId": "generate_calendar_design",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Design a booking page with AI",
        "description": "The Design tab's AI designer: describe the look in one line and/or give the business's website (its colours, fonts and logo are read from it). Returns the design and a one-line summary, and saves it on the calendar unless save is false. readable says whether the text reads well on the card. No credits used.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "calendar_id",
            "in": "path",
            "required": true,
            "description": "Calendar id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "instruction": {
                    "type": "string",
                    "description": "What it should look like, e.g. \"black and gold, like a luxury showroom\" (max 1200)."
                  },
                  "website_url": {
                    "type": "string",
                    "description": "The business's website."
                  },
                  "save": {
                    "type": "boolean",
                    "description": "Default true. false only returns the design (send it later with PATCH design)."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "instruction": "black and gold, like a luxury showroom",
                "website_url": "https://example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "design": {
                      "enabled": true,
                      "preset": null,
                      "mode": "dark",
                      "colors": {
                        "background": "#09090b",
                        "surface": "#141416",
                        "text": "#f5f5f4",
                        "button": "#d4a63a"
                      },
                      "font": "montserrat",
                      "heading_font": "oswald",
                      "radius": "soft",
                      "button_style": "solid",
                      "backdrop": "glow",
                      "logo_url": null,
                      "cover_url": null,
                      "hide_avatar": false,
                      "custom_css": null
                    },
                    "summary": "Dark showroom look with gold buttons and condensed headings.",
                    "saved": true,
                    "website": {
                      "url": "https://example.com",
                      "read": true
                    },
                    "readable": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/booking-menus": {
      "get": {
        "operationId": "list_booking_menus",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "List service menus",
        "description": "Service menus (Calendars → Booking tools → Service menu): one booking link (/c/<slug>, public_path) that lists several calendars in sections, with search, category chips and sorting; the customer picks a service and books it there. Each section lists its services with their price and length as the page shows them. warnings says when its services are on separate schedules (two customers could book the same time on different services) or switched off.\n\nRequired scope: `bookings:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "booking_menus": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "slug": "to-the-t-services",
                        "name": "All services",
                        "title": "Book a detail",
                        "description": null,
                        "sections": [
                          {
                            "id": "sec1",
                            "title": "Quick Services",
                            "description": null,
                            "calendar_ids": [
                              "uuid"
                            ],
                            "services": [
                              {
                                "calendar_id": "uuid",
                                "name": "Express wash",
                                "title": "Express wash",
                                "is_active": true,
                                "price_from_cents": 6000,
                                "price_to_cents": 9000,
                                "length_label": "1-2 hr",
                                "schedule_group": "Main crew"
                              }
                            ]
                          }
                        ],
                        "options": {
                          "show_prices": true,
                          "show_durations": true,
                          "search": true,
                          "filters": true,
                          "sorting": true,
                          "default_sort": "featured"
                        },
                        "design": null,
                        "is_active": true,
                        "public_path": "/c/to-the-t-services",
                        "warnings": [],
                        "created_at": "2026-10-23T12:00:00Z",
                        "updated_at": "2026-10-23T12:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_booking_menu",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Create a service menu",
        "description": "Without sections it starts like the app's: one \"Services\" section with every calendar that is switched on, and a link named after the business. The link shares /c/ with calendars, so a slug a calendar or another menu uses answers 409.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "title": {
                    "type": "string",
                    "description": "Heading customers see (max 120). Default \"Book a service\"."
                  },
                  "name": {
                    "type": "string",
                    "description": "Internal name (max 80). Default \"All services\"."
                  },
                  "description": {
                    "type": "string",
                    "description": "Under the heading (max 600)."
                  },
                  "slug": {
                    "type": "string",
                    "description": "Link name: /c/<slug> (letters, numbers, dashes; max 40)."
                  },
                  "sections": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "In order, replacing the list: [{ id?, title (max 80), description?, calendar_ids: [calendar ids, in the order shown] }]. Up to 30 sections; every calendar must belong to the sub-account."
                  },
                  "options": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Merged: { show_prices, show_durations, search, filters (category chips), sorting, default_sort: featured | price_low | price_high | shortest | name }. All on by default."
                  },
                  "design": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The page look, the same shape as a calendar's design. It carries into each service's booking flow opened from the menu."
                  },
                  "is_active": {
                    "type": "boolean",
                    "description": "Default true."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "title": "Book a detail",
                "sections": [
                  {
                    "title": "Quick Services",
                    "calendar_ids": [
                      "uuid",
                      "uuid"
                    ]
                  },
                  {
                    "title": "Detailing Packages",
                    "calendar_ids": [
                      "uuid"
                    ]
                  }
                ],
                "design": {
                  "preset": "showroom"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/booking-menus/{menu_id}": {
      "patch": {
        "operationId": "update_booking_menu",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Update a service menu",
        "description": "Takes the same fields as create; send only what changes. sections replaces the list; options and design merge.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "menu_id",
            "in": "path",
            "required": true,
            "description": "Menu id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": true
              },
              "example": {
                "options": {
                  "default_sort": "price_low"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_booking_menu",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Delete a service menu",
        "description": "Its link stops working; the calendars on it are not touched.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "menu_id",
            "in": "path",
            "required": true,
            "description": "Menu id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/booking-menus/{menu_id}/share-schedule": {
      "post": {
        "operationId": "share_menu_schedule",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Put a menu's services on one schedule",
        "description": "The menu editor's \"Share one schedule\": every calendar on the menu gets the same schedule_group, so a booking on any of them takes that time on all of them (one crew doing every service). group names it; by default the group one of them already has, else the menu's title.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "menu_id",
            "in": "path",
            "required": true,
            "description": "Menu id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "group": {
                    "type": "string",
                    "description": "The shared schedule's name (max 60)."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "schedule_group": "Main crew",
                    "calendar_ids": [
                      "uuid",
                      "uuid"
                    ],
                    "changed": [
                      "uuid"
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/booking-menus/{menu_id}/design/generate": {
      "post": {
        "operationId": "generate_menu_design",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Design a service menu with AI",
        "description": "Same as POST /v1/calendars/{calendar_id}/design/generate, for a service menu's page.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "menu_id",
            "in": "path",
            "required": true,
            "description": "Menu id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "instruction": {
                    "type": "string",
                    "description": "What it should look like."
                  },
                  "website_url": {
                    "type": "string",
                    "description": "The business's website."
                  },
                  "save": {
                    "type": "boolean",
                    "description": "Default true."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/availability": {
      "get": {
        "operationId": "get_availability",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Get business hours and booking defaults",
        "description": "Required scope: `bookings:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "operationId": "set_availability",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Set business hours and booking defaults",
        "description": "The AI uses these to decide when it may text and which times to offer. Setting the time zone also updates the sub-account's time zone.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA zone."
                  },
                  "weekly_hours": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Same shape as a calendar's."
                  },
                  "date_overrides": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Days off or special hours."
                  },
                  "slot_minutes": {
                    "type": "number",
                    "description": "5-240."
                  },
                  "buffer_minutes": {
                    "type": "number",
                    "description": "0-120. Always saved as 0 when scheduling_mode is arrival_window."
                  },
                  "default_calendar_id": {
                    "type": "string",
                    "description": "A calendar of this sub-account, or null."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "timezone": "America/Chicago",
                "weekly_hours": {
                  "mon": [
                    [
                      "08:00",
                      "17:00"
                    ]
                  ],
                  "sat": [
                    [
                      "09:00",
                      "13:00"
                    ]
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calendar-routes": {
      "get": {
        "operationId": "list_calendar_routes",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "List calendar routing rules",
        "description": "Calendar routing (Calendars -> Calendar routing in the app) sends each lead to the calendar that fits their answers: \"Player group is Girls 13-14 -> Girls 13-14 calendar\". Rules are checked in order (`position` 1 first); the first rule whose conditions ALL match the lead's custom fields or tags picks the calendar. Smart Forms with calendar_routing on, the AI texter and the AI caller follow them. Each rule comes with its calendar's name and a readable `summary`. Rules that are switched off, or whose calendar is off, are skipped.\n\nRequired scope: `bookings:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "calendar_routes": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "calendar_id": "uuid",
                        "calendar_name": "Girls 13-14",
                        "calendar_active": true,
                        "name": "Girls 13-14",
                        "conditions": [
                          {
                            "kind": "field",
                            "key": "player_group",
                            "op": "is",
                            "values": [
                              "Girls 13-14"
                            ],
                            "min": null,
                            "max": null
                          }
                        ],
                        "summary": "Player group is Girls 13-14",
                        "position": 1,
                        "is_active": true,
                        "created_at": "2026-10-07T18:00:00Z",
                        "updated_at": "2026-10-07T18:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_calendar_route",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Add a calendar routing rule",
        "description": "Added last unless you send `position`. Conditions: a field condition reads a custom field by key (GET /v1/custom-fields) with op is, is_not or contains and `values` (any one matches), or op between with min and/or max for numbers (\"11 years old\" counts as 11); a tag condition (kind tag, op is or is_not) reads the lead's tags. Answers compare ignoring case, accents and punctuation. The calendar must belong to the sub-account. `warnings` explains a rule that may never fire (a key that is not a custom field, a calendar that is switched off). Owners and admins only; at most 200 rules.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "calendar_id": {
                    "type": "string",
                    "description": "The calendar this rule books on (GET /v1/calendars)."
                  },
                  "conditions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "All must match, 1-20: [{\"kind\":\"field\",\"key\":\"player_group\",\"op\":\"is\",\"values\":[\"Girls 13-14\"]}], [{\"kind\":\"field\",\"key\":\"age\",\"op\":\"between\",\"min\":9,\"max\":12}], [{\"kind\":\"tag\",\"op\":\"is\",\"values\":[\"VIP\"]}]."
                  },
                  "name": {
                    "type": "string",
                    "description": "Optional label, max 120."
                  },
                  "is_active": {
                    "type": "boolean",
                    "description": "Default true."
                  },
                  "position": {
                    "type": "number",
                    "description": "1 = checked first. Default last."
                  }
                },
                "required": [
                  "workspace_id",
                  "calendar_id",
                  "conditions"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "calendar_id": "uuid",
                "name": "Girls 13-14",
                "conditions": [
                  {
                    "kind": "field",
                    "key": "player_group",
                    "op": "is",
                    "values": [
                      "Girls 13-14"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "calendar_route": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "calendar_id": "uuid",
                      "calendar_name": "Girls 13-14",
                      "calendar_active": true,
                      "name": "Girls 13-14",
                      "conditions": [
                        {
                          "kind": "field",
                          "key": "player_group",
                          "op": "is",
                          "values": [
                            "Girls 13-14"
                          ],
                          "min": null,
                          "max": null
                        }
                      ],
                      "summary": "Player group is Girls 13-14",
                      "position": 3,
                      "is_active": true,
                      "created_at": "2026-10-16T18:00:00Z",
                      "updated_at": "2026-10-16T18:00:00Z"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calendar-routes/{route_id}": {
      "patch": {
        "operationId": "update_calendar_route",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Change a calendar routing rule",
        "description": "Send any of calendar_id, name, conditions (replaces the list) and is_active. To move a rule use POST /v1/calendar-routes/reorder. Owners and admins only.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "route_id",
            "in": "path",
            "required": true,
            "description": "Rule id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "calendar_id": {
                    "type": "string",
                    "description": "Another calendar of the sub-account."
                  },
                  "conditions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces the conditions (see create)."
                  },
                  "name": {
                    "type": "string",
                    "description": "Max 120."
                  },
                  "is_active": {
                    "type": "boolean",
                    "description": "false keeps the rule but skips it."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "is_active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_calendar_route",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Delete a calendar routing rule",
        "description": "Owners and admins only. The other rules keep their order.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "route_id",
            "in": "path",
            "required": true,
            "description": "Rule id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calendar-routes/reorder": {
      "post": {
        "operationId": "reorder_calendar_routes",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Set the order rules are checked in",
        "description": "The first rule that matches wins, so put narrower rules first. `order` lists every rule id of the sub-account exactly once, first to last. Answers the rules in their new order. Owners and admins only.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "order": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Every rule id, first to last."
                  }
                },
                "required": [
                  "workspace_id",
                  "order"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "order": [
                  "uuid-rule-b",
                  "uuid-rule-a"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calendar-routes/match": {
      "get": {
        "operationId": "match_calendar_route",
        "tags": [
          "Calendars & hours"
        ],
        "summary": "Which calendar a lead routes to",
        "description": "Runs the rules the way the booking paths do (active rules on calendars that are on) against a lead's custom fields and tags, and/or `answers` you pass. status: matched (`calendar` and the `route` that won), pending (no rule matched yet but one still can: `missing_fields` are the answers to ask for before offering times), unmatched (no rule fits: the form's or the AI's own calendar applies), or no_rules. Viewers limited to their own leads only reach those leads.\n\nRequired scope: `bookings:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Sub-account id (omit on pinned keys). Needed without lead_id; with lead_id, the lead must be in it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "The lead to route. Send lead_id, answers or both.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "answers",
            "in": "query",
            "required": false,
            "description": "JSON object of field key to answer to try, laid over the lead's own, e.g. {\"player_group\":\"Girls 13-14\"}.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lead_id": "uuid",
                    "status": "pending",
                    "calendar": null,
                    "route": null,
                    "missing_fields": [
                      {
                        "key": "player_group",
                        "label": "Player group"
                      }
                    ],
                    "rules_checked": 4
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/busy-blocks/{external_id}": {
      "put": {
        "operationId": "put_busy_block",
        "tags": [
          "Team busy times"
        ],
        "summary": "Add or update a team member's busy block",
        "description": "For a schedule kept in other software (a field-service app, a dispatch board): the person is treated as booked for that time, so the AI (text, chat and phone), Smart Forms, booking pages and round robin only offer times when they are free. `external_id` is YOUR id for the block (a job or visit id, URL-encoded in the path); sending the same id again replaces the block, so it is safe to resend. A block can make several people busy (a two-tech job). Team members are named by user id or email (GET /v1/members); technicians must be invited as members first. Times need a time zone (Z or an offset). At most 31 days per block. 201 when created, 200 when replaced (changed: false when nothing differed). Owners and admins of the sub-account only.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "external_id",
            "in": "path",
            "required": true,
            "description": "Your own id for the block: 1-200 letters, digits or . _ : ~ @ # | + = -",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "team_member": {
                    "type": "string",
                    "description": "The busy person: a team member's user id or email. Or team_members for several."
                  },
                  "team_members": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Several busy people (user ids or emails), up to 50."
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "ISO 8601 with a zone, e.g. 2026-10-14T08:00:00-05:00."
                  },
                  "ends_at": {
                    "type": "string",
                    "description": "ISO 8601 with a zone; after starts_at."
                  },
                  "title": {
                    "type": "string",
                    "description": "Optional note shown to the team in the CRM calendar (never to leads or the AI), max 200."
                  }
                },
                "required": [
                  "workspace_id",
                  "starts_at",
                  "ends_at"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "team_member": "tech.alex@example.com",
                "starts_at": "2026-10-14T08:00:00-05:00",
                "ends_at": "2026-10-14T11:30:00-05:00",
                "title": "Job #4821 water heater install"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "busy_block": {
                      "id": "uuid",
                      "external_id": "job-4821",
                      "team_members": [
                        {
                          "id": "uuid",
                          "email": "tech.alex@example.com",
                          "name": "Alex Rivera"
                        }
                      ],
                      "starts_at": "2026-10-14T13:00:00.000Z",
                      "ends_at": "2026-10-14T16:30:00.000Z",
                      "title": "Job #4821 water heater install",
                      "source": "api",
                      "created_at": "2026-10-09T15:02:11Z",
                      "updated_at": "2026-10-09T15:02:11Z"
                    },
                    "created": true,
                    "changed": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_busy_block",
        "tags": [
          "Team busy times"
        ],
        "summary": "Remove a busy block",
        "description": "By your own id; the time opens up again at once. 404 when there is no block with that id.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "external_id",
            "in": "path",
            "required": true,
            "description": "Your id for the block (URL-encoded).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/busy-blocks": {
      "get": {
        "operationId": "list_busy_blocks",
        "tags": [
          "Team busy times"
        ],
        "summary": "List busy blocks",
        "description": "Blocks that overlap the window, earliest first. Default window: from yesterday, 60 days long; at most 366 days.\n\nRequired scope: `bookings:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "team_member",
            "in": "query",
            "required": false,
            "description": "Only this person's blocks (user id or email).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO start with a zone.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO end with a zone.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-500, default 100.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Skip this many (has_more says whether there are more).",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/busy-blocks/sync": {
      "post": {
        "operationId": "sync_busy_blocks",
        "tags": [
          "Team busy times"
        ],
        "summary": "Sync a time window in one call",
        "description": "Send your full list of busy blocks for a window: blocks in the list are added or replaced, and blocks you sent earlier that START inside [from, to) but are no longer in the list are removed (cancelled or moved jobs). With team_member, only that person's blocks are replaced and each block defaults to them; a removed block that also names other people keeps them. Up to 500 blocks per call. Good for a nightly or hourly full sync next to the per-block calls. Owners and admins only.\n\nRequired scope: `bookings:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "from": {
                    "type": "string",
                    "description": "Window start, ISO 8601 with a zone."
                  },
                  "to": {
                    "type": "string",
                    "description": "Window end; at most 366 days after from."
                  },
                  "team_member": {
                    "type": "string",
                    "description": "Sync one person only (user id or email)."
                  },
                  "blocks": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "[{ external_id, team_member | team_members, starts_at, ends_at, title? }]. [] clears the window."
                  }
                },
                "required": [
                  "workspace_id",
                  "from",
                  "to",
                  "blocks"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "from": "2026-10-13T00:00:00-05:00",
                "to": "2026-10-20T00:00:00-05:00",
                "team_member": "tech.alex@example.com",
                "blocks": [
                  {
                    "external_id": "job-4821",
                    "starts_at": "2026-10-14T08:00:00-05:00",
                    "ends_at": "2026-10-14T11:30:00-05:00"
                  },
                  {
                    "external_id": "pto-2210",
                    "starts_at": "2026-10-17T00:00:00-05:00",
                    "ends_at": "2026-10-18T00:00:00-05:00",
                    "title": "Day off"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "created": 1,
                    "updated": 0,
                    "unchanged": 1,
                    "removed": 2,
                    "window": {
                      "from": "2026-10-13T05:00:00.000Z",
                      "to": "2026-10-20T05:00:00.000Z"
                    },
                    "team_member": {
                      "id": "uuid",
                      "email": "tech.alex@example.com",
                      "name": "Alex Rivera"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/stages/{stage_id}": {
      "patch": {
        "operationId": "update_stage",
        "tags": [
          "Pipeline & fields"
        ],
        "summary": "Rename, recolour or hide a stage",
        "description": "The five built-in stages (new, contacted, booked, won, lost) can only change label, color and hidden.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "stage_id",
            "in": "path",
            "required": true,
            "description": "Stage id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_stage",
        "tags": [
          "Pipeline & fields"
        ],
        "summary": "Delete a custom stage",
        "description": "Its leads move to the stage named in move_to. Built-in stages cannot be deleted; hide them instead.\n\nRequired scope: `leads:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "stage_id",
            "in": "path",
            "required": true,
            "description": "Stage id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "move_to",
            "in": "query",
            "required": true,
            "description": "Key of the stage that takes over the leads.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/stages/reorder": {
      "post": {
        "operationId": "reorder_stages",
        "tags": [
          "Pipeline & fields"
        ],
        "summary": "Reorder the pipeline",
        "description": "Required scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "order": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Every stage id of the sub-account, first to last."
                  }
                },
                "required": [
                  "workspace_id",
                  "order"
                ],
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/custom-fields": {
      "get": {
        "operationId": "list_custom_fields",
        "tags": [
          "Pipeline & fields"
        ],
        "summary": "List custom lead fields",
        "description": "Required scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_archived",
            "in": "query",
            "required": false,
            "description": "Include archived fields.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_custom_field",
        "tags": [
          "Pipeline & fields"
        ],
        "summary": "Add a custom lead field",
        "description": "field_key and field_type cannot change later, because stored lead values depend on them.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "label": {
                    "type": "string",
                    "description": "Field label (max 80)."
                  },
                  "field_type": {
                    "type": "string",
                    "description": "Default text.",
                    "enum": [
                      "text",
                      "textarea",
                      "number",
                      "money",
                      "phone",
                      "email",
                      "select",
                      "date",
                      "datetime",
                      "url",
                      "boolean",
                      "rating",
                      "multiselect"
                    ]
                  },
                  "field_key": {
                    "type": "string",
                    "description": "Defaults to a key made from the label."
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Choices for select / multiselect."
                  },
                  "required": {
                    "type": "boolean",
                    "description": "Default false."
                  },
                  "show_in_list": {
                    "type": "boolean",
                    "description": "Show as a column in the leads list."
                  }
                },
                "required": [
                  "workspace_id",
                  "label"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "label": "Roof type",
                "field_type": "select",
                "options": [
                  "Shingle",
                  "Metal",
                  "Tile"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/custom-fields/{field_id}": {
      "patch": {
        "operationId": "update_custom_field",
        "tags": [
          "Pipeline & fields"
        ],
        "summary": "Update a custom field",
        "description": "Send `archived: false` to restore an archived field.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "field_id",
            "in": "path",
            "required": true,
            "description": "Field id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "archive_custom_field",
        "tags": [
          "Pipeline & fields"
        ],
        "summary": "Archive a custom field",
        "description": "Hides the field. Values already stored on leads are kept.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "field_id",
            "in": "path",
            "required": true,
            "description": "Field id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/lead-routing": {
      "get": {
        "operationId": "get_lead_routing",
        "tags": [
          "Routing & alerts"
        ],
        "summary": "Get lead round-robin settings",
        "description": "Required scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "operationId": "set_lead_routing",
        "tags": [
          "Routing & alerts"
        ],
        "summary": "Set lead round-robin settings",
        "description": "Assigns NEW LEADS to team members in turn. (A round-robin calendar is separate: it rotates appointment hosts.) An empty eligible_member_ids list means everyone takes turns. member_weights splits leads by percentage (80/20 gives one person 8 of every 10 leads, spread out); {} goes back to even turns.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "round_robin_enabled": {
                    "type": "boolean",
                    "description": "Turn the rotation on or off."
                  },
                  "eligible_member_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "User ids from GET /v1/members. Empty = everyone."
                  },
                  "member_weights": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Percentage split by user id (GET /v1/members): {\"<user_id>\": 80, \"<other_user_id>\": 20}. Whole percents 0-100; the shares above 0 must add up to exactly 100. Sets eligible_member_ids to the people with a share and restarts the rotation. {} = even turns (keeps who is in the rotation)."
                  },
                  "notify_assignee_only": {
                    "type": "boolean",
                    "description": "Alert only the person the lead went to."
                  },
                  "skip_imported_leads": {
                    "type": "boolean",
                    "description": "Leave imported leads unassigned."
                  },
                  "notify_on_reply": {
                    "type": "boolean",
                    "description": "Alert the assignee when the lead replies."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "round_robin_enabled": true,
                "member_weights": {
                  "user-uuid-a": 80,
                  "user-uuid-b": 20
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/alert-recipients": {
      "get": {
        "operationId": "list_alert_recipients",
        "tags": [
          "Routing & alerts"
        ],
        "summary": "List who is alerted about new leads and AI receptionist calls",
        "description": "Required scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_alert_recipient",
        "tags": [
          "Routing & alerts"
        ],
        "summary": "Add a lead alert recipient",
        "description": "Lead details are sent to this phone or inbox, so this needs an admin-scoped key.\n\nRequired scope: `workspaces:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "channel": {
                    "type": "string",
                    "description": "sms or email.",
                    "enum": [
                      "sms",
                      "email"
                    ]
                  },
                  "destination": {
                    "type": "string",
                    "description": "Phone (US 10-digit or +country code; stored as E.164) or email address."
                  },
                  "label": {
                    "type": "string",
                    "description": "e.g. Owner's cell."
                  },
                  "notify_leads": {
                    "type": "boolean",
                    "description": "Gets new-lead, appointment and reply alerts. Default true."
                  },
                  "notify_calls": {
                    "type": "boolean",
                    "description": "Gets a summary after every AI receptionist call (who called, the outcome, any message or booking, the transcript). Default false."
                  }
                },
                "required": [
                  "workspace_id",
                  "channel",
                  "destination"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "channel": "sms",
                "destination": "+14155550123",
                "label": "Owner"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/alert-recipients/{recipient_id}": {
      "patch": {
        "operationId": "update_alert_recipient",
        "tags": [
          "Routing & alerts"
        ],
        "summary": "Update a lead alert recipient",
        "description": "Send any of label, destination, active, notify_leads, notify_calls. A recipient keeps at least one of notify_leads or notify_calls.\n\nRequired scope: `workspaces:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recipient_id",
            "in": "path",
            "required": true,
            "description": "Recipient id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_alert_recipient",
        "tags": [
          "Routing & alerts"
        ],
        "summary": "Remove a lead alert recipient",
        "description": "Required scope: `workspaces:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recipient_id",
            "in": "path",
            "required": true,
            "description": "Recipient id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/lead-sources": {
      "get": {
        "operationId": "list_lead_sources",
        "tags": [
          "Lead sources & webhooks"
        ],
        "summary": "List lead source links",
        "description": "Each link is a private URL that accepts leads, so reading them takes write scope. Treat the URL as a secret.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_lead_source",
        "tags": [
          "Lead sources & webhooks"
        ],
        "summary": "Create a lead source link",
        "description": "Returns a URL any website form, landing page tool or CRM can POST leads (or appointments) to.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "name": {
                    "type": "string",
                    "description": "e.g. Website contact form."
                  },
                  "field_mapping": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Their field name to ours: {\"Full Name\":\"full_name\",\"Roof size\":\"custom:roof_size\"}. Targets: full_name, email, phone, address, city, state, zip, inquiry, notes, appointment_* or custom:<field_key>. A state is saved as its code (\"Texas\" becomes TX)."
                  },
                  "default_source": {
                    "type": "string",
                    "description": "Source label stamped on these leads."
                  },
                  "default_status": {
                    "type": "string",
                    "description": "Default new."
                  },
                  "payload_kind": {
                    "type": "string",
                    "description": "Default auto. update = a form your own team fills in (e.g. during a call): its answers go onto the matching contact (lead_id, phone, then email; added quietly when nobody matches) and nothing is sent: no first text, team alert, Instant Lead Connect call or new-lead automation.",
                    "enum": [
                      "auto",
                      "lead",
                      "appointment",
                      "update"
                    ]
                  }
                },
                "required": [
                  "workspace_id",
                  "name"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "name": "Website contact form",
                "default_source": "Website",
                "field_mapping": {
                  "Your name": "full_name",
                  "Phone number": "phone"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/lead-sources/{lead_source_id}": {
      "patch": {
        "operationId": "update_lead_source",
        "tags": [
          "Lead sources & webhooks"
        ],
        "summary": "Update a lead source link",
        "description": "Links created by an integration (for example Typeform) are managed there and answer 409.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_source_id",
            "in": "path",
            "required": true,
            "description": "Lead source id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_lead_source",
        "tags": [
          "Lead sources & webhooks"
        ],
        "summary": "Delete a lead source link",
        "description": "Required scope: `workspaces:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_source_id",
            "in": "path",
            "required": true,
            "description": "Lead source id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/outbound-webhooks": {
      "get": {
        "operationId": "list_outbound_webhooks",
        "tags": [
          "Lead sources & webhooks"
        ],
        "summary": "List outbound webhooks",
        "description": "Never returns signing secrets or auth tokens.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_outbound_webhook",
        "tags": [
          "Lead sources & webhooks"
        ],
        "summary": "Create an outbound webhook",
        "description": "Pushes lead and appointment events to another system (Zapier, Make, a CRM). Lead data leaves the account, so every write needs an admin-scoped key and target_url must be https on a public host. The signing secret is returned once.\n\nRequired scope: `workspaces:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "name": {
                    "type": "string",
                    "description": "e.g. Zapier: new leads."
                  },
                  "target_url": {
                    "type": "string",
                    "description": "https:// URL that receives the POST."
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Default lead.created and appointment.booked. See the sub-account webhook events list."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Default true."
                  },
                  "preset": {
                    "type": "string",
                    "description": "Payload flavour.",
                    "enum": [
                      "zapier",
                      "make",
                      "salesforce_flow",
                      "service_titan",
                      "custom"
                    ]
                  },
                  "auth_type": {
                    "type": "string",
                    "description": "Default none. Send auth_token (and auth_header_name or auth_username) with it. Write-only.",
                    "enum": [
                      "none",
                      "bearer",
                      "header",
                      "basic"
                    ]
                  }
                },
                "required": [
                  "workspace_id",
                  "name",
                  "target_url"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "name": "Zapier: new leads",
                "target_url": "https://hooks.zapier.com/hooks/catch/123/abc/",
                "events": [
                  "lead.created"
                ],
                "preset": "zapier"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/outbound-webhooks/{webhook_id}": {
      "patch": {
        "operationId": "update_outbound_webhook",
        "tags": [
          "Lead sources & webhooks"
        ],
        "summary": "Update an outbound webhook",
        "description": "Required scope: `workspaces:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "description": "Webhook id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_outbound_webhook",
        "tags": [
          "Lead sources & webhooks"
        ],
        "summary": "Delete an outbound webhook",
        "description": "Required scope: `workspaces:admin`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "description": "Webhook id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/knowledge-base": {
      "get": {
        "operationId": "get_knowledge_base",
        "tags": [
          "AI agent"
        ],
        "summary": "Read the AI's FAQ",
        "description": "Required scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "operationId": "set_knowledge_base",
        "tags": [
          "AI agent"
        ],
        "summary": "Replace the AI's FAQ",
        "description": "The FAQ is what the AI texting agent and AI receptionist answer from. Max 200 entries (question 300 characters, answer 2000).\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "entries": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "[{ question, answer }]. Replaces the whole list."
                  }
                },
                "required": [
                  "workspace_id",
                  "entries"
                ],
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "add_knowledge_base",
        "tags": [
          "AI agent"
        ],
        "summary": "Add FAQ entries",
        "description": "Merges into the existing list. The same question again replaces its answer.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "entries": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "[{ question, answer }]."
                  }
                },
                "required": [
                  "workspace_id",
                  "entries"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "entries": [
                  {
                    "question": "Do you offer financing?",
                    "answer": "Yes, 0% for 12 months on approved credit."
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ai-brain/photos": {
      "get": {
        "operationId": "list_brain_photos",
        "tags": [
          "AI agent"
        ],
        "summary": "AI Brain photos",
        "description": "Photos of the business's work the AI may text a customer who asks to see some (up to 100): link, caption, the service it shows, on / off, how often it was sent. Owners and admins.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "Only on (true) or off (false).",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "add_brain_photo",
        "tags": [
          "AI agent"
        ],
        "summary": "Add an AI Brain photo",
        "description": "From a public link to a JPEG, PNG or WebP (10 MB at most), stored like an upload on the page. The AI writes the caption and service you leave out, which is how it picks the right photo. 409 at 100 photos.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "image_url": {
                    "type": "string",
                    "description": "Public link to the image."
                  },
                  "caption": {
                    "type": "string",
                    "description": "What it shows (max 300)."
                  },
                  "service": {
                    "type": "string",
                    "description": "The service (max 80), e.g. Roof repair."
                  },
                  "active": {
                    "type": "boolean",
                    "description": "Default true."
                  }
                },
                "required": [
                  "workspace_id",
                  "image_url"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "image_url": "https://example.com/jobs/roof-after.jpg",
                "service": "Roof repair"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ai-brain/photos/{photo_id}": {
      "patch": {
        "operationId": "update_brain_photo",
        "tags": [
          "AI agent"
        ],
        "summary": "Change an AI Brain photo",
        "description": "Required scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "photo_id",
            "in": "path",
            "required": true,
            "description": "Photo id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "caption": {
                    "type": "string",
                    "description": "What it shows (max 300)."
                  },
                  "service": {
                    "type": "string",
                    "description": "The service it shows (max 80)."
                  },
                  "active": {
                    "type": "boolean",
                    "description": "Off: the AI stops sending it."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_brain_photo",
        "tags": [
          "AI agent"
        ],
        "summary": "Remove an AI Brain photo",
        "description": "Required scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "photo_id",
            "in": "path",
            "required": true,
            "description": "Photo id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ai-brain/waiting": {
      "get": {
        "operationId": "list_brain_waiting",
        "tags": [
          "AI agent"
        ],
        "summary": "Questions waiting on you",
        "description": "AI Brain > Waiting on you: questions customers asked (texts, chat, DMs, calls) that the AI couldn't answer and handed to the team, newest first, one row per question (times = how many customers asked, variants = how they wrote it). Answered and dismissed ones are left out. Owners and admins.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "waiting": [
                      {
                        "event_id": "uuid",
                        "event_ids": [
                          "uuid",
                          "uuid"
                        ],
                        "variants": [
                          "Do you work on Sundays?",
                          "Are you open Sunday?"
                        ],
                        "lead_id": "uuid",
                        "lead_name": "Jane Doe",
                        "question": "Do you work on Sundays?",
                        "asked_at": "2026-10-24T18:02:00Z",
                        "times": 2,
                        "channel": "sms"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ai-brain/waiting/answer": {
      "post": {
        "operationId": "answer_brain_waiting",
        "tags": [
          "AI agent"
        ],
        "summary": "Answer a waiting question",
        "description": "As Answer it on the page: the answer becomes a saved answer (replacing one to the same question) that the AI texter and receptionist use from now on, the question leaves the list, and the AI turns back on for the leads it was paused on for that question. Nothing is sent to the customer.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "question": {
                    "type": "string",
                    "description": "The question as listed."
                  },
                  "answer": {
                    "type": "string",
                    "description": "What the AI should say (max 2000)."
                  },
                  "event_id": {
                    "type": "string",
                    "description": "The row's event_id."
                  }
                },
                "required": [
                  "workspace_id",
                  "question",
                  "answer"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "event_id": "uuid",
                "question": "Do you work on Sundays?",
                "answer": "No, we're open Monday to Saturday, 8 to 6."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "saved": true,
                    "ok": true,
                    "replaced": false,
                    "entries": 18,
                    "resumed_lead_ids": [
                      "uuid"
                    ],
                    "still_waiting": 0
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ai-brain/waiting/dismiss": {
      "post": {
        "operationId": "dismiss_brain_waiting",
        "tags": [
          "AI agent"
        ],
        "summary": "Dismiss waiting questions",
        "description": "Takes questions off the list without teaching the AI (it still treats them as open); undo: true puts them back.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "event_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "The rows' event_ids."
                  },
                  "questions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Or the questions themselves."
                  },
                  "undo": {
                    "type": "boolean",
                    "description": "Put them back."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ai-receptionist": {
      "get": {
        "operationId": "get_ai_receptionist",
        "tags": [
          "AI agent"
        ],
        "summary": "Read the AI receptionist settings",
        "description": "Also returns the list of voices, and collect_fields as a list of keys.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "operationId": "set_ai_receptionist",
        "tags": [
          "AI agent"
        ],
        "summary": "Configure the AI receptionist",
        "description": "The AI that answers the sub-account's phone. Answered calls bill 1.5 credits per minute of AI conversation (15-second steps; time after a transfer to a person bills as phone minutes), and it needs a phone number on the sub-account to do anything (the response carries a warning when there is none).\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Turn it on or off."
                  },
                  "mode": {
                    "type": "string",
                    "description": "Every call, or only outside business hours.",
                    "enum": [
                      "always",
                      "after_hours"
                    ]
                  },
                  "goal": {
                    "type": "string",
                    "description": "What it tries to do.",
                    "enum": [
                      "book",
                      "support",
                      "book_and_support"
                    ]
                  },
                  "agent_name": {
                    "type": "string",
                    "description": "Max 40."
                  },
                  "voice": {
                    "type": "string",
                    "description": "One of the voices returned by GET."
                  },
                  "greeting": {
                    "type": "string",
                    "description": "Max 400."
                  },
                  "after_hours_greeting": {
                    "type": "string",
                    "description": "Max 400."
                  },
                  "extra_instructions": {
                    "type": "string",
                    "description": "Max 1200."
                  },
                  "transfer_enabled": {
                    "type": "boolean",
                    "description": "Allow transfers to a person."
                  },
                  "transfer_numbers": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 8 phone numbers."
                  },
                  "take_messages": {
                    "type": "boolean",
                    "description": "Take a message when nobody is available."
                  },
                  "max_call_seconds": {
                    "type": "number",
                    "description": "60-1800."
                  },
                  "record_calls": {
                    "type": "boolean",
                    "description": "Record answered calls (billed per recorded minute)."
                  },
                  "update_contact_details": {
                    "type": "boolean",
                    "description": "Let it save the caller's name, email and address to the lead."
                  },
                  "collect_fields": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Details it asks for before booking: \"address\", \"email\" or custom field keys (GET /v1/custom-fields). Max 12."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "enabled": true,
                "mode": "after_hours",
                "goal": "book_and_support",
                "agent_name": "Ava",
                "collect_fields": [
                  "address",
                  "roof_type"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/smart-forms/{form_id}": {
      "patch": {
        "operationId": "update_smart_form",
        "tags": [
          "Smart forms"
        ],
        "summary": "Update or publish a smart form",
        "description": "Takes the same fields as create; send only what changes. questions and qualifier_questions replace their lists; qualification, estimate, fields and fields.address.service_area are merged.\n\nRequired scope: `smart_forms:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "form_id",
            "in": "path",
            "required": true,
            "description": "Form id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "is_published": {
                    "type": "boolean",
                    "description": "Publish or unpublish."
                  },
                  "layout_mode": {
                    "type": "string",
                    "description": "single_step, multi_step, conversational or funnel (see create).",
                    "enum": [
                      "single_step",
                      "multi_step",
                      "conversational",
                      "funnel"
                    ]
                  },
                  "qualifier_enabled": {
                    "type": "boolean",
                    "description": "Ask the qualifier_questions first."
                  },
                  "qualifier_questions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces the list (see create)."
                  },
                  "questions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces the list (see create)."
                  },
                  "qualification": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Merged (see create)."
                  },
                  "estimate": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Merged (see create)."
                  },
                  "calendar_routing": {
                    "type": "boolean",
                    "description": "Pick the calendar from the answers (see create)."
                  },
                  "fields": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Merged, including address.service_area (see create)."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "fields": {
                  "address": {
                    "service_area": {
                      "enabled": true,
                      "mode": "radius",
                      "center": {
                        "lat": 32.7767,
                        "lng": -96.797,
                        "label": "Dallas, TX"
                      },
                      "radius_miles": 50
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ai-texting": {
      "get": {
        "operationId": "get_ai_texting",
        "tags": [
          "AI agent"
        ],
        "summary": "Read the AI texting agent and automatic text settings",
        "description": "AI replies, agent name, reply mode, goal, what it knows, booking link, first text, send window, hand-off, confirmation and reminder texts, missed-call text-back, email greeting, and a preview of its prompt. Says whether the sub-account has a phone number.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "set_ai_texting",
        "tags": [
          "AI agent"
        ],
        "summary": "Change the AI texting agent and automatic texts",
        "description": "Send only the settings to change, by these names: ai_replies, agent_name, reply_mode (auto | suggest), goal (book_appointment | collect_details | customer_support), business_context, booking_link, max_replies (1-15), reply_delay_minutes (1-120), ai_can_cancel, handoff_when_unsure, pause_ai_on_handoff, handoff_resume_days (0, 1, 2, 3, 7), greet_new_leads, first_message, smart_first_text (the AI writes each new lead's first text from their answers and the right calendar's open times; first_message becomes the backup), smart_first_text_goal (what that text should get them to do; empty = book an appointment), greet_delay_minutes, form_greet_delay_minutes, missed_call_text, missed_call_message, confirmation_text, confirmation_message, confirmation_email (the confirmation email with a calendar invite, plus the move and cancel emails), confirmation_email_language (en | es), reminder_24h, reminder_24h_message, reminder_1h, reminder_1h_message, reminder_5m (the 5-minute reminder, off by default), reminder_5m_message, email_greeting, email_greeting_subject, email_greeting_body, send_window (anytime | daily | booking_hours), send_window_start, send_window_end (\"HH:MM\"), ai_answers_email, prompt_addition (one more rule) or prompt (a full rewrite). Owners, agency admins and client admins can change these. Turning AI replies on lets it answer conversations still waiting for a reply.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "ai_replies": {
                    "type": "boolean",
                    "description": "The AI answers leads by text."
                  },
                  "agent_name": {
                    "type": "string",
                    "description": "First name the AI texts as (max 40)."
                  },
                  "reply_mode": {
                    "type": "string",
                    "description": "suggest = drafts each reply for a teammate to approve (GET /v1/reply-suggestions).",
                    "enum": [
                      "auto",
                      "suggest"
                    ]
                  },
                  "first_message": {
                    "type": "string",
                    "description": "First text to a new lead. Variables {first_name}, {agent}, {business}, {service}."
                  },
                  "send_window": {
                    "type": "string",
                    "description": "When automated texts may go out.",
                    "enum": [
                      "anytime",
                      "daily",
                      "booking_hours"
                    ]
                  },
                  "prompt_addition": {
                    "type": "string",
                    "description": "A rule added to the end of the AI's prompt (max 2000)."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "ai_replies": true,
                "agent_name": "Sarah",
                "reply_mode": "auto",
                "prompt_addition": "Always mention free estimates."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "ai_texting": {
                      "ai_replies": true,
                      "agent_name": "Sarah",
                      "reply_mode": "auto",
                      "send_window": "daily",
                      "send_window_start": "08:00",
                      "send_window_end": "21:00",
                      "prompt_characters": 2140
                    },
                    "changed": [
                      "ai_replies",
                      "agent_name",
                      "reply_mode",
                      "prompt_addition"
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/nurture-sequence": {
      "get": {
        "operationId": "get_nurture",
        "tags": [
          "AI agent"
        ],
        "summary": "Read the automatic follow-up sequence",
        "description": "Every text and email step with the day it goes out, whether each channel is on, and offers whose own follow-ups replace it for the leads they match.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "operationId": "set_nurture",
        "tags": [
          "AI agent"
        ],
        "summary": "Replace the follow-up texts (or emails)",
        "description": "Replaces one channel's steps and turns it on. Timing is the app's: the first 3 hours after the first text, then front-loaded days over span_days (about 5 days per message, at most 60), or the explicit days you send. At most 40 texts or 60 emails; email needs a verified sending domain. Pending sends that would change channel are cancelled.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "channel": {
                    "type": "string",
                    "description": "Default sms.",
                    "enum": [
                      "sms",
                      "email"
                    ]
                  },
                  "messages": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "One message per step, in order."
                  },
                  "subjects": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Email only: one subject per message."
                  },
                  "span_days": {
                    "type": "number",
                    "description": "Days from the first message to the last (1-365)."
                  },
                  "days": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Instead of span_days: the day each message goes out, going up. 0 = 3 hours in."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Default true."
                  }
                },
                "required": [
                  "workspace_id",
                  "messages"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "channel": "sms",
                "messages": [
                  "Hi {first_name}, still looking for a quote?",
                  "Quick check-in from {business}: want me to hold a time for you?"
                ],
                "span_days": 7
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "update_nurture",
        "tags": [
          "AI agent"
        ],
        "summary": "Switch follow-ups on or off, or reword one step",
        "description": "Send enabled, or step with message (and subject for email). A reworded step keeps its timing.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "channel": {
                    "type": "string",
                    "description": "Default sms.",
                    "enum": [
                      "sms",
                      "email"
                    ]
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "On or off."
                  },
                  "step": {
                    "type": "number",
                    "description": "Which step to reword (1 = first)."
                  },
                  "message": {
                    "type": "string",
                    "description": "The step's new wording."
                  },
                  "subject": {
                    "type": "string",
                    "description": "Email only: the new subject."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "channel": "sms",
                "step": 2,
                "message": "Hi {first_name}, any questions I can answer?"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "clear_nurture",
        "tags": [
          "AI agent"
        ],
        "summary": "Remove one channel's follow-up steps",
        "description": "Switches that channel off and cancels its pending sends.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Default sms.",
            "schema": {
              "type": "string",
              "enum": [
                "sms",
                "email"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/business-profile": {
      "get": {
        "operationId": "get_business_profile",
        "tags": [
          "Setup"
        ],
        "summary": "Read the business profile",
        "description": "Name, industry, time zone, address, country, business phone and business email (Settings → General).\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "set_business_profile",
        "tags": [
          "Setup"
        ],
        "summary": "Change the business profile",
        "description": "Send only what changes. A new time zone also moves the booking hours' time zone, as in the app.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "name": {
                    "type": "string",
                    "description": "Max 120."
                  },
                  "industry": {
                    "type": "string",
                    "description": "Max 80."
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA zone, e.g. America/Chicago."
                  },
                  "address_line1": {
                    "type": "string",
                    "description": "Street."
                  },
                  "address_line2": {
                    "type": "string",
                    "description": "Suite, unit."
                  },
                  "city": {
                    "type": "string",
                    "description": "City."
                  },
                  "state": {
                    "type": "string",
                    "description": "State or region."
                  },
                  "postal_code": {
                    "type": "string",
                    "description": "ZIP or postal code."
                  },
                  "country": {
                    "type": "string",
                    "description": "Two-letter country code (US, CA, GB, AU; \"United States\" works too), or null. It picks the State list on leads, forms and booking pages, and the list a lead's state is matched against when the lead has no phone."
                  },
                  "business_phone": {
                    "type": "string",
                    "description": "US 10-digit or + and a country code."
                  },
                  "business_email": {
                    "type": "string",
                    "description": "Email address."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "timezone": "America/Chicago",
                "country": "US",
                "business_phone": "+14155550123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/overview": {
      "get": {
        "operationId": "get_overview",
        "tags": [
          "Dashboard"
        ],
        "summary": "Dashboard numbers for a window",
        "description": "New leads, booked leads and appointments (each with the previous window of the same length for comparison), upcoming appointments, contacted, won and closed, speed to lead (median minutes to the first reply: anyone, AI or automation, a person), AI replies sent, calls (answered, missed, voicemails, AI calls, talk time), and stage_reach: of the leads created in the window, how many reached each pipeline stage (`total`, and `reached` keyed by stage key from GET /v1/stages; a lead counts for every stage it has been in, so a lead that sat and then won counts for both; the Overview's \"<Stage> rate\" cards divide reached by total). Moves made before 2026-10-16 are not known. stage_reach is null if it could not be read. Viewers limited to their own leads see only those.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "1-365, default 30.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO start, instead of days (window at most 366 days).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO end, default now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace_id": "uuid",
                    "window": {
                      "from": "2026-08-31T00:00:00Z",
                      "to": "2026-09-30T00:00:00Z",
                      "days": 30
                    },
                    "stats": {
                      "leads": 142,
                      "prev_leads": 120,
                      "booked_leads": 38,
                      "prev_booked_leads": 31,
                      "appointments": 41,
                      "upcoming_appointments": 12,
                      "contacted": 131,
                      "won": 9,
                      "closed": 15,
                      "new_unworked": 4,
                      "median_response_minutes": 0.8,
                      "median_response_minutes_auto": 0.4,
                      "median_response_minutes_human": 22.5,
                      "ai_replies": {
                        "total": 512,
                        "sms": 498,
                        "email": 9,
                        "other_channels": 5,
                        "leads": 96
                      },
                      "calls": {
                        "total": 88,
                        "inbound": 30,
                        "outbound": 58,
                        "answered": 61,
                        "missed_inbound": 6,
                        "voicemails": 9,
                        "ai_calls": 40,
                        "ai_receptionist_calls": 8,
                        "talk_seconds": 15540
                      },
                      "stage_reach": {
                        "total": 142,
                        "reached": {
                          "new": 142,
                          "contacted": 131,
                          "sat": 44,
                          "booked": 38,
                          "won": 9
                        }
                      }
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/health-score": {
      "get": {
        "operationId": "get_health_score",
        "tags": [
          "Dashboard"
        ],
        "summary": "A sub-account's health score",
        "description": "The 0-100 health score from the Sub-accounts list and its breakdown: grade (Excellent, Healthy, Needs attention, At risk, Dormant), the five factors (activity recency 15, lead flow 20, speed to lead 25, leads answered 15, booking rate 25) with the points each earned, its value and how it was worked out; a factor that can't be measured yet has earned null (listed in not_measured) and its weight is spread over the rest. verdict says what goes well and what holds it back; actions is what would raise it, biggest gain first (gain in score points; path is the page inside the sub-account, e.g. settings?tab=messaging), with people waiting for a reply first. history is the score at the end of each of the last weeks (oldest first, the last one now), and last_30_days the funnel, reply speed and lead sources behind it. Leads leave out spam and imported lists. Any member; Technicians and Viewers limited to their own leads are scored on those.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "weeks",
            "in": "query",
            "required": false,
            "description": "Weeks of history, 1-26, default 12.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace_id": "uuid",
                    "name": "Bright Smiles Dental",
                    "score": 72,
                    "grade": "Healthy",
                    "is_new": false,
                    "verdict": "Active today and first replies within 42s. The booking rate (14%) is what holds it back.",
                    "factors": [
                      {
                        "key": "recency",
                        "label": "Activity recency",
                        "weight": 15,
                        "earned": 15,
                        "applies": true,
                        "value": "Today",
                        "detail": "Last new lead, message or call was less than a day ago.",
                        "hint": "Full points when something happens in the account every day.",
                        "gain": 0
                      },
                      {
                        "key": "booking",
                        "label": "Booking rate (30 days)",
                        "weight": 25,
                        "earned": 14,
                        "applies": true,
                        "value": "14%",
                        "detail": "6 of 42 leads from the last 30 days booked an appointment.",
                        "hint": "Full points at a 25% booking rate.",
                        "gain": 11
                      }
                    ],
                    "not_measured": [],
                    "applied_weight": 100,
                    "actions": [
                      {
                        "key": "follow_ups",
                        "title": "Turn on follow-ups",
                        "why": "Only 14% of leads booked. Most bookings come from the 2nd to 5th message.",
                        "gain": 11,
                        "path": "settings?tab=messaging&section=nurture",
                        "cta": "Follow-up settings",
                        "urgent": false
                      }
                    ],
                    "inputs": {
                      "last_activity_at": "2026-10-29T14:10:00Z",
                      "leads_7d": 9,
                      "bookings_7d": 2,
                      "leads_30d": 42,
                      "booked_30d": 6,
                      "median_response_seconds": 42,
                      "reply_base_30d": 41,
                      "replied_30d": 40
                    },
                    "waiting": {
                      "count": 0,
                      "longest_since": null
                    },
                    "history": [
                      {
                        "as_of": "2026-08-13T15:00:00Z",
                        "score": 64,
                        "grade": "Needs attention"
                      },
                      {
                        "as_of": "2026-10-29T15:00:00Z",
                        "score": 72,
                        "grade": "Healthy"
                      }
                    ],
                    "last_30_days": {
                      "funnel": {
                        "leads": 42,
                        "replied": 40,
                        "booked": 6,
                        "showed": 4,
                        "no_show": 1,
                        "won": 3,
                        "lost": 5,
                        "still_new": 1
                      },
                      "speed": {
                        "within_1m": 30,
                        "within_5m": 6,
                        "within_1h": 2,
                        "within_1d": 1,
                        "later": 0,
                        "no_reply": 1
                      },
                      "sources": [
                        {
                          "channel": "facebook",
                          "leads": 30,
                          "booked": 4
                        }
                      ],
                      "upcoming_appointments": 5
                    },
                    "setup": {
                      "phone_numbers": 1,
                      "ai_texter": true,
                      "first_text": true,
                      "follow_ups": false,
                      "calendars": 1,
                      "automations": 3,
                      "meta_pages": 1,
                      "smart_forms": 0,
                      "webhooks": 0
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/health-scores": {
      "get": {
        "operationId": "list_health_scores",
        "tags": [
          "Dashboard"
        ],
        "summary": "Health scores of every sub-account",
        "description": "Every sub-account the key's person can open, best first, scored as GET /v1/health-score scores them: score, grade, rank among the active ones (null for a sub-account where nothing has happened yet), the factor holding it back most (held_back_by, when it costs 3 points or more), each factor's points and the inputs; plus the median score and the medians of each input across the active ones. A key restricted to one sub-account gets just that one. Archived sub-accounts are left out unless include_archived=true.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Just this sub-account.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_archived",
            "in": "query",
            "required": false,
            "description": "Also archived sub-accounts.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspaces": [
                      {
                        "workspace_id": "uuid",
                        "name": "Bright Smiles Dental",
                        "archived": false,
                        "score": 72,
                        "grade": "Healthy",
                        "is_new": false,
                        "rank": 3,
                        "held_back_by": {
                          "key": "booking",
                          "label": "Booking rate (30 days)",
                          "value": "14%",
                          "gain": 11
                        },
                        "factors": [
                          {
                            "key": "booking",
                            "earned": 14,
                            "weight": 25,
                            "value": "14%",
                            "gain": 11
                          }
                        ],
                        "inputs": {
                          "last_activity_at": "2026-10-29T14:10:00Z",
                          "leads_7d": 9,
                          "bookings_7d": 2,
                          "leads_30d": 42,
                          "booked_30d": 6,
                          "median_response_seconds": 42,
                          "reply_base_30d": 41,
                          "replied_30d": 40
                        }
                      }
                    ],
                    "active": 18,
                    "median_score": 61,
                    "medians": {
                      "leads_7d": 6,
                      "median_response_seconds": 95,
                      "answer_rate": 88,
                      "booking_rate": 17,
                      "days_since_activity": 0.4
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "get_usage",
        "tags": [
          "Dashboard"
        ],
        "summary": "Credits used by tool",
        "description": "What the sub-account spent in a window, by tool (the Usage tab's names: texts, lead alert texts, email, AI text replies, website chat widget, AI agents, phone calls, AI receptionist, AI calling, phone numbers, ad tools) and by day. unpaid counts events that ran while the account was out of credits.\n\nRequired scope: `billing:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "1-365, default 30.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO start, instead of days.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO end, default now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace_id": "uuid",
                    "window": {
                      "from": "2026-08-31T00:00:00Z",
                      "to": "2026-09-30T00:00:00Z",
                      "days": 30
                    },
                    "total_credits": 318.5,
                    "total_events": 2210,
                    "unpaid_events": 0,
                    "by_tool": [
                      {
                        "tool": "ai_text",
                        "label": "AI text replies",
                        "events": 512,
                        "credits": 128,
                        "unpaid": 0
                      },
                      {
                        "tool": "messaging",
                        "label": "Texts",
                        "events": 1404,
                        "credits": 105.3,
                        "unpaid": 0
                      }
                    ],
                    "by_day": [
                      {
                        "date": "2026-09-29",
                        "events": 81,
                        "credits": 11.5
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/conversations": {
      "get": {
        "operationId": "list_conversations",
        "tags": [
          "Inbox"
        ],
        "summary": "The Inbox: conversations, newest first",
        "description": "Every lead with a message either way (or an unread one), like the Inbox: unread count, archive state, whether the AI is paused on it (and the question it handed to the team), and the last message or call. The first page also returns the Inbox counts. Read the thread with GET /v1/leads/{lead_id}/conversation.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "archived",
            "in": "query",
            "required": false,
            "description": "true = the Archived tab. Default false.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "unread",
            "in": "query",
            "required": false,
            "description": "Only unread conversations.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "assigned_to",
            "in": "query",
            "required": false,
            "description": "A user id, \"me\" or \"unassigned\".",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Name, email or phone contains (2-80 characters).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "imessage_line_id",
            "in": "query",
            "required": false,
            "description": "Only the conversations this iMessage Mac has had (GET /v1/imessage/lines), like the Inbox's number filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "conversations": [
                      {
                        "lead_id": "uuid",
                        "workspace_id": "uuid",
                        "name": "Jane Doe",
                        "phone": "+14155550123",
                        "email": null,
                        "status": "contacted",
                        "stage_key": "contacted",
                        "assigned_to": null,
                        "unread": true,
                        "unread_count": 2,
                        "archived": false,
                        "ai": {
                          "paused": false,
                          "paused_until": null,
                          "pause_reason": null,
                          "handoff_question": null
                        },
                        "channel": "sms",
                        "last_message": {
                          "id": "uuid",
                          "channel": "sms",
                          "direction": "inbound",
                          "body": "Can you come Thursday?",
                          "subject": null,
                          "sent_by": null,
                          "created_at": "2026-09-30T15:02:00Z"
                        },
                        "last_inbound_at": "2026-09-30T15:02:00Z",
                        "last_outbound_at": "2026-09-30T14:40:00Z",
                        "last_activity_at": "2026-09-30T15:02:00Z"
                      }
                    ],
                    "has_more": true,
                    "next_cursor": "MjAyNi0wOS0zMFQxNTowMjowMCswMDowMHx1dWlk",
                    "counts": {
                      "open": 210,
                      "unread": 7,
                      "archived": 54
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/conversations/{lead_id}/read": {
      "post": {
        "operationId": "mark_conversation_read",
        "tags": [
          "Inbox"
        ],
        "summary": "Mark a conversation read",
        "description": "Anyone who can see the lead can, as in the Inbox.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/conversations/{lead_id}/unread": {
      "post": {
        "operationId": "mark_conversation_unread",
        "tags": [
          "Inbox"
        ],
        "summary": "Mark a conversation unread",
        "description": "Required scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/conversations/{lead_id}/archive": {
      "post": {
        "operationId": "archive_conversation",
        "tags": [
          "Inbox"
        ],
        "summary": "Archive a conversation",
        "description": "As in the app, archiving switches the lead's automatic follow-up texts off. A new message from the lead brings the conversation back.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/conversations/{lead_id}/unarchive": {
      "post": {
        "operationId": "unarchive_conversation",
        "tags": [
          "Inbox"
        ],
        "summary": "Move a conversation back to the Inbox",
        "description": "Follow-ups switched off by archiving stay off.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}/ai-pause": {
      "post": {
        "operationId": "set_lead_ai_pause",
        "tags": [
          "Inbox"
        ],
        "summary": "Pause or resume the AI on one lead",
        "description": "paused: true stops AI replies to this lead (and its automatic follow-ups at send time) until someone turns it back on, or until `until`. paused: false turns it back on and clears any hand-off. The sub-account-wide switch is ai_replies on PATCH /v1/ai-texting.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "paused": {
                    "type": "boolean",
                    "description": "Pause (true) or resume (false)."
                  },
                  "until": {
                    "type": "string",
                    "description": "ISO time the AI turns itself back on (paused: true only, at most a year away)."
                  }
                },
                "required": [
                  "paused"
                ],
                "additionalProperties": true
              },
              "example": {
                "paused": true,
                "until": "2026-10-07T15:00:00Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lead_id": "uuid",
                    "ai": {
                      "paused": true,
                      "paused_until": "2026-10-07T15:00:00.000Z",
                      "pause_reason": "Paused from the API",
                      "handoff_question": null
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/reply-suggestions": {
      "get": {
        "operationId": "list_reply_suggestions",
        "tags": [
          "Inbox"
        ],
        "summary": "AI suggested replies waiting for a teammate",
        "description": "When AI texting's reply_mode is suggest, the AI drafts its reply to each text or email here instead of sending it. Each one shows the lead, the draft, and the appointment it would book or cancel. reply_mode in the response says whether the sub-account is in suggest mode.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Default pending.",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "sending",
                "sent",
                "dismissed",
                "superseded",
                "failed",
                "all"
              ]
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "One lead's suggestions.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "reply_mode": "suggest",
                    "suggestions": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "lead_id": "uuid",
                        "channel": "sms",
                        "body": "Thursday at 2pm works! I've put you down, see you then.",
                        "status": "pending",
                        "error": null,
                        "inbound_at": "2026-09-30T15:02:00Z",
                        "sent_body": null,
                        "decided_at": null,
                        "created_at": "2026-09-30T15:02:20Z",
                        "updated_at": "2026-09-30T15:02:20Z",
                        "books": {
                          "starts_at": "2026-10-02T19:00:00Z",
                          "ends_at": "2026-10-02T20:00:00Z",
                          "title": "Roof inspection",
                          "timezone": "America/New_York",
                          "location": null,
                          "is_arrival_window": false
                        },
                        "cancels": null,
                        "lead": {
                          "id": "uuid",
                          "full_name": "Jane Doe",
                          "phone": "+14155550123",
                          "email": null
                        }
                      }
                    ],
                    "has_more": false,
                    "next_cursor": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/reply-suggestions/{suggestion_id}/dismiss": {
      "post": {
        "operationId": "dismiss_reply_suggestion",
        "tags": [
          "Inbox"
        ],
        "summary": "Dismiss a suggested reply",
        "description": "Nothing is sent. The AI does not suggest again until the lead writes. 409 when it was already handled.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "suggestion_id",
            "in": "path",
            "required": true,
            "description": "Suggestion id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/reply-suggestions/{suggestion_id}/approve": {
      "post": {
        "operationId": "approve_reply_suggestion",
        "tags": [
          "Inbox"
        ],
        "summary": "Send a suggested reply",
        "description": "Sends it to the lead as the AI (edited, if you send body), makes the booking or cancellation it decided on, and is charged exactly like an automatic AI reply plus the text, through the same code as the Inbox's Send button. Owners and admins only. 409 = someone already answered or the lead wrote again (nothing sent), 422 = not sent for a reason in `error` (e.g. AI paused, out of credits, that time is no longer open), 503 = a sending hiccup, try again.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "suggestion_id",
            "in": "path",
            "required": true,
            "description": "Suggestion id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "The edited reply to send instead (texts up to 1600 characters, emails 5000)."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "body": "Thursday at 2pm works! See you then."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "suggestion_id": "uuid",
                    "sent": true,
                    "status": "sent",
                    "booked": true,
                    "cancelled": false
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}/reply-ideas": {
      "post": {
        "operationId": "lead_reply_ideas",
        "tags": [
          "Inbox"
        ],
        "summary": "Reply ideas for a conversation",
        "description": "The Inbox composer's reply assistant: up to three ready replies in the business's tone (Settings > Reply assistant), written from the conversation, calls, appointments, the lead's stage and the AI Brain. `kind` says what they are: reply (an answer to their message), follow_up (a nudge after the business's own message) or first (the first message). Send `draft` to polish your own text instead (`how`: improve, grammar, shorter, a tone, or custom with `instruction`); the answer is `text`. Nothing is sent or saved: send the one you pick with POST /v1/messages. Free, and counted in the person's hourly allowance shared with the Inbox (429 when it's used up). Anyone who can see the lead.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string",
                    "description": "Where the reply goes. Default sms.",
                    "enum": [
                      "sms",
                      "email",
                      "chat",
                      "instagram",
                      "messenger"
                    ]
                  },
                  "tone": {
                    "type": "string",
                    "description": "New ideas only: this tone instead of the business's own.",
                    "enum": [
                      "friendly",
                      "professional",
                      "casual",
                      "formal"
                    ]
                  },
                  "avoid": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "New ideas only: up to 6 replies you already have, to get different ones."
                  },
                  "draft": {
                    "type": "string",
                    "description": "Polish this text instead (max 4000)."
                  },
                  "how": {
                    "type": "string",
                    "description": "With draft. Default improve.",
                    "enum": [
                      "improve",
                      "grammar",
                      "shorter",
                      "friendly",
                      "professional",
                      "casual",
                      "formal",
                      "custom"
                    ]
                  },
                  "instruction": {
                    "type": "string",
                    "description": "With how custom: what to change (max 300)."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "channel": "sms"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lead_id": "uuid",
                    "channel": "sms",
                    "kind": "reply",
                    "replies": [
                      {
                        "label": "Confirm Thursday",
                        "text": "Hi Jane, Thursday at 2 works. See you then!"
                      },
                      {
                        "label": "Offer a time",
                        "text": "Hi Jane, we can do Thursday at 2 or Friday morning. Which suits you?"
                      }
                    ],
                    "subject": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/needs-reply": {
      "get": {
        "operationId": "list_needs_reply",
        "tags": [
          "Inbox"
        ],
        "summary": "Who is waiting on a person (Needs you now)",
        "description": "The CRM overview's \"Needs you now\": everyone from the last 14 days waiting on someone at the business, newest first, and why. `ai_handoff`: the AI told them the team would get back to them and nobody has written, called or booked them since (`question` is what it couldn't answer, `asked_again` when they chased it). `missed_call`: they called and nobody picked up or called back (`voicemail`, `message_taken` by the AI receptionist, `call_note`). `message`: their message has no reply from a person or the AI (reminders, follow-ups and automations aren't replies; a thanks or ok after a message that asked nothing needs none; with AI replies on, a brand-new message is the AI's for its first 3 minutes). `total`, `by_reason` and `longest_since` count everyone; `leads` lists up to `limit`. Technicians and limited Staff see only their own leads.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many people to list, 1-100, default 25.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace_id": "uuid",
                    "total": 3,
                    "longest_since": "2026-10-28T14:10:00Z",
                    "by_reason": {
                      "ai_handoff": 1,
                      "missed_call": 1,
                      "message": 1
                    },
                    "leads": [
                      {
                        "lead_id": "uuid",
                        "name": "Jane Doe",
                        "phone": "+14155550123",
                        "reason": "ai_handoff",
                        "since": "2026-10-28T14:10:00Z",
                        "last_at": "2026-10-28T16:40:00Z",
                        "question": "Do you install skylights?",
                        "preview": "Any update?",
                        "channel": "sms",
                        "asked_again": true,
                        "called": false,
                        "voicemail": false,
                        "message_taken": false,
                        "call_note": null,
                        "ai_out_of_replies": false
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calls": {
      "get": {
        "operationId": "list_calls",
        "tags": [
          "Calls"
        ],
        "summary": "Phone calls, newest first",
        "description": "Every call on the sub-account's numbers: direction, type (softphone, bridge, inbound, forward, ai, ai_receptionist), status, duration, voicemail, the AI's outcome and summary, and the lead. Get one for its transcript and recording.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Inbound or outbound.",
            "schema": {
              "type": "string",
              "enum": [
                "inbound",
                "outbound"
              ]
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Call type.",
            "schema": {
              "type": "string",
              "enum": [
                "softphone",
                "bridge",
                "inbound",
                "forward",
                "ai",
                "ai_receptionist"
              ]
            }
          },
          {
            "name": "outcome",
            "in": "query",
            "required": false,
            "description": "The Calls tab's outcome filter.",
            "schema": {
              "type": "string",
              "enum": [
                "connected",
                "missed",
                "voicemail"
              ]
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "One lead's calls.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO time, calls at or after.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "ISO time, calls before.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "calls": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "lead_id": "uuid",
                        "direction": "inbound",
                        "mode": "ai_receptionist",
                        "status": "completed",
                        "from_number": "+14155550123",
                        "to_number": "+14155550199",
                        "answered_at": "2026-09-30T15:00:04Z",
                        "ended_at": "2026-09-30T15:03:10Z",
                        "duration_sec": 186,
                        "is_voicemail": false,
                        "voicemail_read_at": null,
                        "ai_outcome": "booked",
                        "ai_summary": "Caller wanted a roof inspection; booked Thursday 2pm.",
                        "ai_booking_id": "uuid",
                        "campaign_id": null,
                        "created_at": "2026-09-30T15:00:00Z",
                        "ai_call": true,
                        "has_recording": true,
                        "lead": {
                          "id": "uuid",
                          "full_name": "Jane Doe",
                          "phone": "+14155550123"
                        }
                      }
                    ],
                    "has_more": false,
                    "next_cursor": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/calls/{call_id}": {
      "get": {
        "operationId": "get_call",
        "tags": [
          "Calls"
        ],
        "summary": "One call: summary, transcript and recording",
        "description": "recording.url is a fresh link that works for 15 minutes; ask again for a new one.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "call_id",
            "in": "path",
            "required": true,
            "description": "Call id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "call": {
                      "id": "uuid",
                      "direction": "inbound",
                      "mode": "ai_receptionist",
                      "status": "completed",
                      "duration_sec": 186,
                      "ai_outcome": "booked",
                      "ai_summary": "Caller wanted a roof inspection; booked Thursday 2pm.",
                      "transcript": "AI: Thanks for calling...\nCaller: Hi, I need...",
                      "recording": {
                        "url": "https://.../lead-media/...mp3?token=...",
                        "expires_at": "2026-09-30T15:20:00Z",
                        "expires_in_seconds": 900
                      },
                      "lead": {
                        "id": "uuid",
                        "full_name": "Jane Doe",
                        "phone": "+14155550123"
                      }
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/call-campaigns": {
      "get": {
        "operationId": "list_call_campaigns",
        "tags": [
          "Calls"
        ],
        "summary": "AI calling campaigns and their progress",
        "description": "Leads, calls placed and connected, bookings, and credits used against the spend cap. Owners and admins only.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only this status.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "running",
                "paused",
                "completed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/call-campaigns/{campaign_id}/pause": {
      "post": {
        "operationId": "pause_call_campaign",
        "tags": [
          "Calls"
        ],
        "summary": "Pause a running AI calling campaign",
        "description": "Its queued calls are cancelled. Resuming places paid calls, so it is done in the app. 409 when it is not running.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "path",
            "required": true,
            "description": "Campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ai-lessons": {
      "get": {
        "operationId": "list_ai_lessons",
        "tags": [
          "AI Brain"
        ],
        "summary": "What the team has taught the AI",
        "description": "Settings → AI Brain: rules (instructions every AI reply follows), good examples (thumbs up) and lessons (thumbs down: what was wrong, a better reply, never send anything like it again). The AI texter and the voice agent read the active ones on every reply. Owners and admins only.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Only this kind.",
            "schema": {
              "type": "string",
              "enum": [
                "good",
                "avoid",
                "rule"
              ]
            }
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "Only switched on (true) or off (false).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 50.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lessons": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "kind": "rule",
                        "channel": "sms",
                        "ai_message": null,
                        "lead_message": null,
                        "better_reply": null,
                        "reasons": [],
                        "note": "Never quote prices by text.",
                        "never_again": false,
                        "source": "brain",
                        "lead_id": null,
                        "active": true,
                        "created_at": "2026-09-30T12:00:00Z",
                        "updated_at": "2026-09-30T12:00:00Z",
                        "message_id": null
                      }
                    ],
                    "has_more": false,
                    "next_cursor": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_ai_lesson",
        "tags": [
          "AI Brain"
        ],
        "summary": "Add a rule the AI follows",
        "description": "Like Add rule in Settings → AI Brain. channel limits it to texts (sms), email or calls (voice); leave it out for everywhere. To teach from a specific reply, rate it (POST /v1/messages/{message_id}/rating). Owners and admins only; at most 200 rules.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "note": {
                    "type": "string",
                    "description": "The rule, max 600 characters."
                  },
                  "channel": {
                    "type": "string",
                    "description": "Where it applies. Default everywhere.",
                    "enum": [
                      "sms",
                      "email",
                      "voice",
                      "all"
                    ]
                  },
                  "active": {
                    "type": "boolean",
                    "description": "Default true."
                  }
                },
                "required": [
                  "workspace_id",
                  "note"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "note": "Never quote prices by text; offer a free estimate visit instead.",
                "channel": "sms"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ai-lessons/{lesson_id}": {
      "patch": {
        "operationId": "update_ai_lesson",
        "tags": [
          "AI Brain"
        ],
        "summary": "Switch a lesson off or reword it",
        "description": "What can change depends on the kind, as in the app: a rule's note, channel and active; a thumbs-down lesson's note, better_reply, never_again and active; a good example's active only.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lesson_id",
            "in": "path",
            "required": true,
            "description": "Lesson id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "active": {
                    "type": "boolean",
                    "description": "On or off."
                  },
                  "note": {
                    "type": "string",
                    "description": "Max 600."
                  },
                  "channel": {
                    "type": "string",
                    "description": "Rules only.",
                    "enum": [
                      "sms",
                      "email",
                      "voice",
                      "all"
                    ]
                  },
                  "better_reply": {
                    "type": "string",
                    "description": "Thumbs-down lessons only, max 2000."
                  },
                  "never_again": {
                    "type": "boolean",
                    "description": "Thumbs-down lessons only."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_ai_lesson",
        "tags": [
          "AI Brain"
        ],
        "summary": "Delete a lesson for good",
        "description": "active: false on PATCH keeps it but switches it off.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lesson_id",
            "in": "path",
            "required": true,
            "description": "Lesson id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/messages/{message_id}/rating": {
      "post": {
        "operationId": "rate_ai_message",
        "tags": [
          "AI Brain"
        ],
        "summary": "Thumbs up or down on an AI message",
        "description": "Exactly the Inbox's rating. up keeps the reply as a good example; down records what was wrong (reasons), what it should have said (better_reply), a note, and by default never to send anything close again; clear removes the rating. Rating the same message again replaces its lesson. Works on messages and calls sent to the lead (message ids from GET /v1/leads/{lead_id}/conversation). Owners and admins only.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "description": "The message (lead event) id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rating": {
                    "type": "string",
                    "description": "up, down or clear.",
                    "enum": [
                      "up",
                      "down",
                      "clear"
                    ]
                  },
                  "reasons": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "down only: wrong_info, too_pushy, too_long, missed_question, wrong_tone, should_not_reply."
                  },
                  "better_reply": {
                    "type": "string",
                    "description": "down only: what it should have said (max 2000)."
                  },
                  "note": {
                    "type": "string",
                    "description": "down only: a note (max 600)."
                  },
                  "never_again": {
                    "type": "boolean",
                    "description": "down only. Default true."
                  }
                },
                "required": [
                  "rating"
                ],
                "additionalProperties": true
              },
              "example": {
                "rating": "down",
                "reasons": [
                  "too_pushy"
                ],
                "better_reply": "No rush at all, happy to answer any questions first."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "message_id": "uuid",
                    "rating": "down",
                    "lesson_id": "uuid",
                    "lesson": {
                      "id": "uuid",
                      "kind": "avoid",
                      "channel": "sms",
                      "ai_message": "Book now before slots fill up!",
                      "better_reply": "No rush at all, happy to answer any questions first.",
                      "reasons": [
                        "too_pushy"
                      ],
                      "never_again": true,
                      "active": true
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/phone-numbers": {
      "get": {
        "operationId": "list_phone_numbers",
        "tags": [
          "Setup"
        ],
        "summary": "The sub-account's phone numbers",
        "description": "Number, name, default, capabilities, texting registration and caller-name status, and who may use it: assigned_user_id (a number that belongs to one team member: it rings only them and other technicians can't text from it), tech_can_answer and tech_can_use (whether it rings technicians and whether they may call and text from it). Buying and releasing numbers is done in the app. iMessage Macs: GET /v1/imessage/lines.\n\nRequired scope: `phone_numbers:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_released",
            "in": "query",
            "required": false,
            "description": "Include released numbers.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "phone_numbers": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "phone_number": "+14155550199",
                        "friendly_name": "Main line",
                        "is_default": true,
                        "capabilities": {
                          "sms": true,
                          "voice": true,
                          "mms": true
                        },
                        "sms_registration_status": "ready",
                        "cnam_display_name": "ACME ROOFING",
                        "cnam_status": "approved",
                        "shaken_status": null,
                        "voice_forward_enabled": false,
                        "created_at": "2026-09-01T00:00:00Z",
                        "released_at": null,
                        "texting_ready": true
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/invoices": {
      "get": {
        "operationId": "list_invoices",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "Invoices (read only)",
        "description": "The sub-account's invoices, newest first. effective_status adds overdue (sent and past due), as the app shows it. kind is invoice, deposit (it lists the whole job but asks only for deposit_cents now; deposit_percent when it was a percent) or balance (made by Bill the balance: deposit_invoice_id is the deposit it follows and deposit_applied_cents comes off the job total). total_cents is always what this invoice asks for. Owners and admins only. Payment processor details are never returned.\n\nRequired scope: `billing:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "paid",
                "void",
                "overdue"
              ]
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "One lead's invoices.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "invoices": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "lead_id": "uuid",
                        "number": "INV-000042",
                        "status": "sent",
                        "currency": "usd",
                        "issue_date": "2026-09-20",
                        "due_date": "2026-09-27",
                        "customer_name": "Jane Doe",
                        "customer_email": "jane@example.com",
                        "customer_phone": "+14155550123",
                        "total_cents": 125000,
                        "amount_paid_cents": 0,
                        "collect_payment": true,
                        "payment_gateway": "stripe",
                        "sent_at": "2026-09-20T15:00:00Z",
                        "viewed_at": null,
                        "paid_at": null,
                        "created_at": "2026-09-20T14:55:00Z",
                        "updated_at": "2026-09-20T15:00:00Z",
                        "effective_status": "overdue",
                        "balance_cents": 125000
                      }
                    ],
                    "has_more": false,
                    "next_cursor": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/invoices/{invoice_id}": {
      "get": {
        "operationId": "get_invoice",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "One invoice with its line items and history",
        "description": "Adds the customer's address, notes, terms, subtotal, discount and tax, the PDF link, line items, the event history (created, sent, viewed, paid) and public_path, the customer's view-and-pay page on the sub-account's domain (none for drafts).\n\nRequired scope: `billing:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "invoice_id",
            "in": "path",
            "required": true,
            "description": "Invoice id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/estimates": {
      "get": {
        "operationId": "list_estimates",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "Estimates (read only)",
        "description": "The sub-account's estimates, newest first, as the Estimates tab lists them: number, title, the customer, status and effective_status (expired: sent and past its last day), when it was sent, viewed, approved (and by whom) or declined (and why), the invoice it became, its Good / Better / Best options with their totals, and total_cents (the option the customer picked, else the recommended one). Technicians and Staff limited to their own leads see the estimates for leads they can see and ones they made without a lead, as in the app.\n\nRequired scope: `billing:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "approved",
                "declined",
                "expired",
                "converted"
              ]
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "One lead's estimates.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "estimates": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "lead_id": "uuid",
                        "number": "EST-001042",
                        "title": "Duct cleaning, whole house",
                        "status": "sent",
                        "currency": "usd",
                        "issue_date": "2026-10-20",
                        "expires_at": "2026-11-19",
                        "customer_name": "Jane Doe",
                        "customer_email": "jane@example.com",
                        "customer_phone": "+14155550123",
                        "selected_option_id": null,
                        "sent_at": "2026-10-20T16:00:00Z",
                        "viewed_at": "2026-10-20T18:12:00Z",
                        "approved_at": null,
                        "approved_by_name": null,
                        "declined_at": null,
                        "decline_reason": null,
                        "converted_invoice_id": null,
                        "created_by": "uuid",
                        "created_at": "2026-10-20T15:40:00Z",
                        "updated_at": "2026-10-20T18:12:00Z",
                        "effective_status": "sent",
                        "total_cents": 64900,
                        "options": [
                          {
                            "id": "uuid",
                            "name": "Good",
                            "is_recommended": false,
                            "total_cents": 39900
                          },
                          {
                            "id": "uuid",
                            "name": "Better",
                            "is_recommended": true,
                            "total_cents": 64900
                          }
                        ]
                      }
                    ],
                    "has_more": false,
                    "next_cursor": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_estimate",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "Create an estimate (draft)",
        "description": "Saved as a draft exactly as the estimate editor saves one: the sub-account's next number, its default terms, days valid and tax rate, the lead's name, email, phone and address filled in. Send items for one option, or options (2-4, e.g. Good / Better / Best) each with its items; every option's subtotal, tax and total are worked out as the editor does (each line rounded, tax on taxable lines). Answers with the estimate as GET /v1/estimates/{estimate_id} shows it. Send it with POST /v1/estimates/{estimate_id}/send. Technicians only while the sub-account lets them make estimates, and only for their own leads.\n\nRequired scope: `billing:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "lead_id": {
                    "type": "string",
                    "description": "Who it is for (fills in the customer; needed to send it). Or customer_name."
                  },
                  "title": {
                    "type": "string",
                    "description": "Max 200, e.g. Whole-house duct cleaning."
                  },
                  "intro": {
                    "type": "string",
                    "description": "A note to the customer at the top (max 2000)."
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "One option's lines: [{ description (the line's name, max 300), details? (max 2000), image_url? (https), quantity? (default 1), unit_price_cents, taxable? (default true) }]."
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Instead of items, up to 4 choices: [{ name, description?, image_url?, recommended?, items: [...] }]. One is recommended (the first marked, else the first)."
                  },
                  "tax_rate": {
                    "type": "number",
                    "description": "Percent, 0-30. Default: the sub-account's."
                  },
                  "valid_days": {
                    "type": "number",
                    "description": "Days it is valid (1-365). Default: the sub-account's. Or expires_at."
                  },
                  "expires_at": {
                    "type": "string",
                    "description": "Last day, YYYY-MM-DD, or null for no end."
                  },
                  "terms": {
                    "type": "string",
                    "description": "Max 4000. Default: the sub-account's estimate terms."
                  },
                  "currency": {
                    "type": "string",
                    "description": "3 letters, default usd."
                  },
                  "customer_name": {
                    "type": "string",
                    "description": "Without a lead, or to change what the lead fills in."
                  },
                  "customer_email": {
                    "type": "string",
                    "description": "Customer email."
                  },
                  "customer_phone": {
                    "type": "string",
                    "description": "Customer phone."
                  },
                  "customer_address": {
                    "type": "string",
                    "description": "Customer address."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "lead_id": "uuid",
                "title": "Duct cleaning",
                "options": [
                  {
                    "name": "Good",
                    "items": [
                      {
                        "description": "Main trunk lines",
                        "unit_price_cents": 29900
                      }
                    ]
                  },
                  {
                    "name": "Better",
                    "recommended": true,
                    "items": [
                      {
                        "description": "Every vent and return",
                        "details": "Up to 20 vents, before and after photos.",
                        "unit_price_cents": 49900
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/estimates/{estimate_id}": {
      "get": {
        "operationId": "get_estimate",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "One estimate with its options, lines and history",
        "description": "Adds the customer's address, intro, terms, tax rate and the customer's approval note, every option (name, description, photo, recommended, subtotal, tax and total) with its lines (name, details, photo, quantity, unit price, taxable), the history (created, sent, viewed, approved, declined) and public_path, the customer's page on the sub-account's domain (none for drafts).\n\nRequired scope: `billing:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "estimate_id",
            "in": "path",
            "required": true,
            "description": "Estimate id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "update_estimate",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "Change an estimate",
        "description": "Send only what changes. options (or items) replace every option and line, as the editor saves; a new tax_rate re-totals the options. An approved estimate or one already made into an invoice answers 409: make a new one.\n\nRequired scope: `billing:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "estimate_id",
            "in": "path",
            "required": true,
            "description": "Estimate id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "New title."
                  },
                  "intro": {
                    "type": "string",
                    "description": "New note at the top."
                  },
                  "lead_id": {
                    "type": "string",
                    "description": "Or null."
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces the options with one."
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces the options."
                  },
                  "tax_rate": {
                    "type": "number",
                    "description": "Percent, 0-30; re-totals the options."
                  },
                  "valid_days": {
                    "type": "number",
                    "description": "Days valid from today (1-365)."
                  },
                  "expires_at": {
                    "type": "string",
                    "description": "Last day, YYYY-MM-DD, or null."
                  },
                  "terms": {
                    "type": "string",
                    "description": "New terms."
                  },
                  "customer_name": {
                    "type": "string",
                    "description": "Customer name."
                  },
                  "customer_email": {
                    "type": "string",
                    "description": "Customer email."
                  },
                  "customer_phone": {
                    "type": "string",
                    "description": "Customer phone."
                  },
                  "customer_address": {
                    "type": "string",
                    "description": "Customer address."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "tax_rate": 8.25
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_estimate",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "Delete an estimate",
        "description": "One that became an invoice stays (409), so the invoice still points at it.\n\nRequired scope: `billing:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "estimate_id",
            "in": "path",
            "required": true,
            "description": "Estimate id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/price-book": {
      "get": {
        "operationId": "list_price_book",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "The price book",
        "description": "The saved items estimates and invoices pick their lines from: name, details, category, unit price, taxable, photo, and how often it's used (most used first).\n\nRequired scope: `billing:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Name or category contains (2-80 characters).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_inactive",
            "in": "query",
            "required": false,
            "description": "Include archived items.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-500, default 100.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "items": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "name": "Dryer vent cleaning",
                        "description": "Brush and vacuum the full run, check the exterior flap.",
                        "category": "Vents",
                        "unit_price_cents": 14900,
                        "taxable": true,
                        "image_url": null,
                        "is_active": true,
                        "usage_count": 37,
                        "last_used_at": "2026-10-22T14:00:00Z",
                        "created_at": "2026-09-01T00:00:00Z",
                        "updated_at": "2026-10-22T14:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/estimates/{estimate_id}/send": {
      "post": {
        "operationId": "send_estimate",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "Send an estimate to the lead",
        "description": "Like the editor's Send: a text and/or an email to the estimate's lead with the link to review and approve it (on the sub-account's own domain when it has one), in the editor's own words unless you send message (it must include {link}). Sent as the key's person, with the usual opt-out, credit and limit checks; each is billed like any text or email. A draft (or a declined or expired one) becomes sent. sent lists each channel and why one didn't go.\n\nRequired scope: `billing:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "estimate_id",
            "in": "path",
            "required": true,
            "description": "Estimate id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string",
                    "description": "Default both (whichever the lead has).",
                    "enum": [
                      "sms",
                      "email",
                      "both"
                    ]
                  },
                  "message": {
                    "type": "string",
                    "description": "Your own wording, with {link}."
                  },
                  "subject": {
                    "type": "string",
                    "description": "The email's subject."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "channel": "sms"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "sent": [
                      {
                        "channel": "sms",
                        "ok": true
                      }
                    ],
                    "link": "https://book.acme.com/e/abc123",
                    "status": "sent"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/estimates/{estimate_id}/convert": {
      "post": {
        "operationId": "convert_estimate",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "Make the invoice",
        "description": "Convert to invoice: a draft invoice from the option the customer picked (else the recommended one, or option_id), with its lines, the estimate's tax rate and the sub-account's due days, terms and notes; the estimate becomes converted. Owners and admins (invoices are theirs). Send the invoice from the app.\n\nRequired scope: `billing:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "estimate_id",
            "in": "path",
            "required": true,
            "description": "Estimate id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "option_id": {
                    "type": "string",
                    "description": "The option to invoice."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "estimate_id": "uuid",
                    "status": "converted",
                    "invoice": {
                      "id": "uuid",
                      "number": "INV-000124",
                      "status": "draft",
                      "total_cents": 49900
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/email-campaigns": {
      "get": {
        "operationId": "list_email_campaigns",
        "tags": [
          "Email marketing"
        ],
        "summary": "Email campaigns and their results",
        "description": "The sub-account's marketing emails, most recently changed first, as Tools > Email Marketing lists them: status (draft, scheduled, sending, sent, failed, canceled), subject, who it goes to, the pace (drip: every emails a batch, minutes between batches; null = all at once), how far it got and the results (delivered, opened, clicked, booked, booked after a click, unsubscribed, bounced). Owners and admins.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "scheduled",
                "sending",
                "sent",
                "failed",
                "canceled"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "campaigns": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "name": "Fall tune-up",
                        "status": "sent",
                        "subject": "Your $49 tune-up is back",
                        "audience": "tags",
                        "stages": [],
                        "tags": [
                          "uuid"
                        ],
                        "not_booked": true,
                        "drip": {
                          "every": 50,
                          "minutes": 60
                        },
                        "scheduled_at": null,
                        "started_at": "2026-10-20T15:00:00Z",
                        "sent_at": "2026-10-21T02:00:00Z",
                        "next_batch_at": null,
                        "total_recipients": 540,
                        "sent_count": 532,
                        "failed_count": 2,
                        "skipped_count": 6,
                        "credits_charged": 1.6,
                        "error": null,
                        "results": {
                          "recipients": 540,
                          "queued": 0,
                          "sent": 532,
                          "failed": 2,
                          "delivered": 528,
                          "opened": 241,
                          "clicked": 63,
                          "booked": 9,
                          "booked_after_click": 7,
                          "unsubscribed": 3,
                          "bounced": 4
                        },
                        "created_at": "2026-10-19T18:00:00Z",
                        "updated_at": "2026-10-21T02:00:00Z"
                      }
                    ],
                    "has_more": false
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_email_campaign",
        "tags": [
          "Email marketing"
        ],
        "summary": "Create an email campaign (draft)",
        "description": "With brief, the AI designs and writes the whole email from one or two sentences, as Write it for me does (it can take a minute or two), and suggests an audience (suggested_audience); pictures: true also makes AI header pictures (0.5 credits each, three a round). Without, a draft from the business's own details and the content you send. The button goes to destination (default: the newest published Smart Page, else a booking form) or button_url. Nothing is sent. Owners and admins.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "name": {
                    "type": "string",
                    "description": "Your name for it (max 120)."
                  },
                  "brief": {
                    "type": "string",
                    "description": "What the email is about: the offer, the deadline, who it's for (4-4000 characters). Not with the content fields."
                  },
                  "pictures": {
                    "type": "boolean",
                    "description": "With brief: AI header pictures (spends credits). Default false."
                  },
                  "destination": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Where the button goes: { kind: smart_page | smart_form, id }, { kind: url, url } or { kind: none }."
                  },
                  "subject": {
                    "type": "string",
                    "description": "Max 150."
                  },
                  "preheader": {
                    "type": "string",
                    "description": "The preview line inboxes show after the subject (max 200)."
                  },
                  "eyebrow": {
                    "type": "string",
                    "description": "A small label above the headline (max 40)."
                  },
                  "headline": {
                    "type": "string",
                    "description": "Max 160."
                  },
                  "greeting": {
                    "type": "string",
                    "description": "e.g. Hi {first_name}, (max 80)."
                  },
                  "body": {
                    "type": "string",
                    "description": "The words in markdown (## headings, - bullets, **bold**, [links](https://...)), max 12000. Replaces every section with one text section."
                  },
                  "blocks": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Instead of body, up to 24 sections as GET shows them: text {md}, offer {value, label, was, note, kicker}, image {url}, split {url, title, md}, features / steps {title, items}, quote {text, name, stars}, gallery {images}, banner {text}, faq {items: [{q, a}]}, cards {items}, button {label, url}, divider, html {html} (email-safe HTML)."
                  },
                  "button_label": {
                    "type": "string",
                    "description": "The main button (max 50)."
                  },
                  "button_url": {
                    "type": "string",
                    "description": "Where it goes: a full http(s) link."
                  },
                  "hero_image_url": {
                    "type": "string",
                    "description": "An https picture at the top, or empty to remove it."
                  },
                  "hero_image_alt": {
                    "type": "string",
                    "description": "Its description."
                  },
                  "signoff": {
                    "type": "string",
                    "description": "e.g. Talk soon,"
                  },
                  "signer_name": {
                    "type": "string",
                    "description": "Who signs it."
                  },
                  "signer_title": {
                    "type": "string",
                    "description": "Their title."
                  },
                  "ps": {
                    "type": "string",
                    "description": "A P.S. under the signature (max 600)."
                  },
                  "design": {
                    "type": "string",
                    "description": "The look.",
                    "enum": [
                      "classic",
                      "bold",
                      "letter",
                      "flyer",
                      "newsletter",
                      "elegant",
                      "minimal",
                      "bright",
                      "fresh",
                      "blocks",
                      "retro",
                      "night",
                      "soft"
                    ]
                  },
                  "audience": {
                    "type": "string",
                    "description": "Who gets it: everyone with an email address, everyone who hasn't booked, leads in some stages, or leads with any of some tags.",
                    "enum": [
                      "everyone",
                      "not_booked",
                      "stages",
                      "tags"
                    ]
                  },
                  "stages": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "audience stages: stage keys (GET /v1/stages)."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "audience tags: tag names or ids (GET /v1/tags)."
                  },
                  "not_booked": {
                    "type": "boolean",
                    "description": "audience tags: only those who haven't booked."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "brief": "$49 furnace tune-up through November 15, for past customers",
                "design": "fresh"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/email-campaigns/{campaign_id}": {
      "get": {
        "operationId": "get_email_campaign",
        "tags": [
          "Email marketing"
        ],
        "summary": "One email campaign",
        "description": "Adds its content (subject, preheader, headline, sections in blocks, the button, signature, design), where the button goes (destination), problems (what still blocks sending; ready_to_send when there are none) and the header pictures' status.\n\nRequired scope: `messaging:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "campaign_id",
            "in": "path",
            "required": true,
            "description": "Campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "update_email_campaign",
        "tags": [
          "Email marketing"
        ],
        "summary": "Change a draft",
        "description": "Send only what changes, as the builder saves it; brief has the AI rewrite the whole draft instead. A campaign that was sent or scheduled answers 409: duplicate it.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "path",
            "required": true,
            "description": "Campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New name."
                  },
                  "brief": {
                    "type": "string",
                    "description": "The AI rewrites the draft from this (not with the content fields)."
                  },
                  "pictures": {
                    "type": "boolean",
                    "description": "With brief: new AI header pictures (spends credits)."
                  },
                  "subject": {
                    "type": "string",
                    "description": "Max 150."
                  },
                  "preheader": {
                    "type": "string",
                    "description": "The preview line inboxes show after the subject (max 200)."
                  },
                  "eyebrow": {
                    "type": "string",
                    "description": "A small label above the headline (max 40)."
                  },
                  "headline": {
                    "type": "string",
                    "description": "Max 160."
                  },
                  "greeting": {
                    "type": "string",
                    "description": "e.g. Hi {first_name}, (max 80)."
                  },
                  "body": {
                    "type": "string",
                    "description": "The words in markdown (## headings, - bullets, **bold**, [links](https://...)), max 12000. Replaces every section with one text section."
                  },
                  "blocks": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Instead of body, up to 24 sections as GET shows them: text {md}, offer {value, label, was, note, kicker}, image {url}, split {url, title, md}, features / steps {title, items}, quote {text, name, stars}, gallery {images}, banner {text}, faq {items: [{q, a}]}, cards {items}, button {label, url}, divider, html {html} (email-safe HTML)."
                  },
                  "button_label": {
                    "type": "string",
                    "description": "The main button (max 50)."
                  },
                  "button_url": {
                    "type": "string",
                    "description": "Where it goes: a full http(s) link."
                  },
                  "hero_image_url": {
                    "type": "string",
                    "description": "An https picture at the top, or empty to remove it."
                  },
                  "hero_image_alt": {
                    "type": "string",
                    "description": "Its description."
                  },
                  "signoff": {
                    "type": "string",
                    "description": "e.g. Talk soon,"
                  },
                  "signer_name": {
                    "type": "string",
                    "description": "Who signs it."
                  },
                  "signer_title": {
                    "type": "string",
                    "description": "Their title."
                  },
                  "ps": {
                    "type": "string",
                    "description": "A P.S. under the signature (max 600)."
                  },
                  "design": {
                    "type": "string",
                    "description": "The look.",
                    "enum": [
                      "classic",
                      "bold",
                      "letter",
                      "flyer",
                      "newsletter",
                      "elegant",
                      "minimal",
                      "bright",
                      "fresh",
                      "blocks",
                      "retro",
                      "night",
                      "soft"
                    ]
                  },
                  "audience": {
                    "type": "string",
                    "description": "Who gets it: everyone with an email address, everyone who hasn't booked, leads in some stages, or leads with any of some tags.",
                    "enum": [
                      "everyone",
                      "not_booked",
                      "stages",
                      "tags"
                    ]
                  },
                  "stages": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "audience stages: stage keys (GET /v1/stages)."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "audience tags: tag names or ids (GET /v1/tags)."
                  },
                  "not_booked": {
                    "type": "boolean",
                    "description": "audience tags: only those who haven't booked."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "subject": "Last call: $49 tune-ups end Friday",
                "audience": "not_booked"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_email_campaign",
        "tags": [
          "Email marketing"
        ],
        "summary": "Delete an email campaign",
        "description": "A scheduled or sending one answers 409: stop it first.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "path",
            "required": true,
            "description": "Campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/email-campaigns/{campaign_id}/send": {
      "post": {
        "operationId": "send_email_campaign",
        "tags": [
          "Email marketing"
        ],
        "summary": "Send or schedule a draft",
        "description": "Emails real people. As the Send button: to the saved audience (or the one you send), now or at schedule_at, all at once or a few at a time (drip). Left out: leads with no email address, who unsubscribed, bounced or marked it spam. Each email costs messaging credits (none through the business's own email server); a send the credits don't cover answers 402 before anything goes out. A draft is sent once: a second call answers 409. recipients is how many it goes to.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "path",
            "required": true,
            "description": "Campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "audience": {
                    "type": "string",
                    "description": "Who gets it: everyone with an email address, everyone who hasn't booked, leads in some stages, or leads with any of some tags.",
                    "enum": [
                      "everyone",
                      "not_booked",
                      "stages",
                      "tags"
                    ]
                  },
                  "stages": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "audience stages: stage keys (GET /v1/stages)."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "audience tags: tag names or ids (GET /v1/tags)."
                  },
                  "not_booked": {
                    "type": "boolean",
                    "description": "audience tags: only those who haven't booked."
                  },
                  "schedule_at": {
                    "type": "string",
                    "description": "ISO 8601 with a time zone, 2 minutes to 90 days ahead. Leave out to send now."
                  },
                  "drip": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "A few at a time: { every: 1-10000 emails, minutes: 5-43200 between batches }. The whole send must fit in 90 days."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "audience": "tags",
                "tags": [
                  "Past customer"
                ],
                "schedule_at": "2026-11-03T09:00:00-05:00",
                "drip": {
                  "every": 100,
                  "minutes": 60
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "campaign": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "name": "Fall tune-up",
                      "status": "scheduled",
                      "subject": "Your $49 tune-up is back",
                      "audience": "tags",
                      "stages": [],
                      "tags": [
                        "uuid"
                      ],
                      "not_booked": true,
                      "drip": {
                        "every": 50,
                        "minutes": 60
                      },
                      "scheduled_at": null,
                      "started_at": "2026-10-20T15:00:00Z",
                      "sent_at": "2026-10-21T02:00:00Z",
                      "next_batch_at": null,
                      "total_recipients": 540,
                      "sent_count": 532,
                      "failed_count": 2,
                      "skipped_count": 6,
                      "credits_charged": 1.6,
                      "error": null,
                      "results": null,
                      "created_at": "2026-10-19T18:00:00Z",
                      "updated_at": "2026-10-21T02:00:00Z"
                    },
                    "scheduled": true,
                    "recipients": 540
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/email-campaigns/{campaign_id}/cancel": {
      "post": {
        "operationId": "cancel_email_campaign",
        "tags": [
          "Email marketing"
        ],
        "summary": "Stop a send",
        "description": "A scheduled campaign goes back to draft; one that is sending stops (the emails not yet sent are skipped).\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "path",
            "required": true,
            "description": "Campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/email-campaigns/{campaign_id}/send-rest": {
      "post": {
        "operationId": "send_email_campaign_rest",
        "tags": [
          "Email marketing"
        ],
        "summary": "Send the rest now",
        "description": "A campaign going out a few at a time: everyone still waiting gets it now. Emails real people.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "path",
            "required": true,
            "description": "Campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/email-campaigns/{campaign_id}/duplicate": {
      "post": {
        "operationId": "duplicate_email_campaign",
        "tags": [
          "Email marketing"
        ],
        "summary": "Duplicate a campaign",
        "description": "A new draft with the same content, audience and pace, to change and send again.\n\nRequired scope: `messaging:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "path",
            "required": true,
            "description": "Campaign id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/contracts": {
      "get": {
        "operationId": "list_contracts",
        "tags": [
          "Contracts"
        ],
        "summary": "Contracts",
        "description": "The sub-account's contracts, newest first: who signs (lead and recipient), status (draft, sent, signed, declined, voided) and stage as the page shows it (draft, sent, viewed, signed, completed when you countersigned, declined, voided, expired), when it was sent, opened, signed or declined (and why), the onboarding call it booked, and sign_url once it is out of draft.\n\nRequired scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "signed",
                "declined",
                "voided"
              ]
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "One lead's contracts.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "next_before from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "contracts": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "lead_id": "uuid",
                        "template_id": "uuid",
                        "recipient_name": "Jane Doe",
                        "recipient_email": "jane@example.com",
                        "recipient_phone": "+14155550123",
                        "subject": "Service agreement",
                        "status": "sent",
                        "stage": "viewed",
                        "sent_at": "2026-10-28T16:00:00Z",
                        "last_sent_at": "2026-10-28T16:00:00Z",
                        "send_count": 1,
                        "viewed_at": "2026-10-28T16:20:00Z",
                        "last_viewed_at": "2026-10-28T16:20:00Z",
                        "view_count": 2,
                        "signed_at": null,
                        "signer_name": null,
                        "agency_signed_at": null,
                        "declined_at": null,
                        "decline_reason": null,
                        "voided_at": null,
                        "expires_at": null,
                        "booking_enabled": false,
                        "booking_calendar_id": null,
                        "booking_id": null,
                        "booked_at": null,
                        "created_by": "uuid",
                        "created_at": "2026-10-28T15:58:00Z",
                        "updated_at": "2026-10-28T16:20:00Z",
                        "sign_url": "https://app.acme.com/sign/abc123"
                      }
                    ],
                    "has_more": false,
                    "next_before": null
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_contract",
        "tags": [
          "Contracts"
        ],
        "summary": "Create a contract (draft)",
        "description": "Saved as a draft, as New contract saves one. From a template (template_id, with values for its blanks; the template's onboarding call setting comes along) or from your own text (body in markdown: # headings, paragraphs, - lists, **bold**, at least 40 characters). The lead's name, email, phone and address, the business's name and today's date fill in by themselves; recipient_* are for a contract without a lead. Nothing is sent: share it with POST /v1/contracts/{contract_id}/link or send it with /send (which needs a lead).\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "lead_id": {
                    "type": "string",
                    "description": "Who signs."
                  },
                  "template_id": {
                    "type": "string",
                    "description": "From this template (GET /v1/contract-templates). Or body."
                  },
                  "values": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "{ blank: value } for the template's {{blanks}} (up to 2000 characters each)."
                  },
                  "body": {
                    "type": "string",
                    "description": "Instead of a template: the contract's text in markdown (max 60000)."
                  },
                  "subject": {
                    "type": "string",
                    "description": "The title (max 200). Default: the template's name, or the text's first line."
                  },
                  "recipient_name": {
                    "type": "string",
                    "description": "Without a lead."
                  },
                  "recipient_email": {
                    "type": "string",
                    "description": "Without a lead."
                  },
                  "recipient_phone": {
                    "type": "string",
                    "description": "Without a lead."
                  },
                  "expires_in_days": {
                    "type": "number",
                    "description": "The signing link stops working after this many days (1-365). Default: never."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "lead_id": "uuid",
                "template_id": "uuid",
                "values": {
                  "start_date": "November 3",
                  "price": "$1,200"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/contracts/{contract_id}": {
      "get": {
        "operationId": "get_contract",
        "tags": [
          "Contracts"
        ],
        "summary": "One contract",
        "description": "Adds its text (body_html), the values filled in, the {{blanks}} left and its history (created, sent, resent with the channels, viewed, signed, declined, voided).\n\nRequired scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "contract_id",
            "in": "path",
            "required": true,
            "description": "Contract id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/contract-templates": {
      "get": {
        "operationId": "list_contract_templates",
        "tags": [
          "Contracts"
        ],
        "summary": "Contract templates",
        "description": "The sub-account's templates with their {{blanks}}. Most fill in by themselves on a new contract (client_name, client_email, client_phone, client_address, business_name, date); send values for the rest.\n\nRequired scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_body",
            "in": "query",
            "required": false,
            "description": "Include each template's text (body_html).",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "templates": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "name": "Service agreement",
                        "description": null,
                        "category": null,
                        "variables": [
                          "client_name",
                          "start_date",
                          "price"
                        ],
                        "blanks": [
                          "client_name",
                          "start_date",
                          "price"
                        ],
                        "booking_enabled": false,
                        "booking_calendar_id": null,
                        "created_at": "2026-10-01T00:00:00Z",
                        "updated_at": "2026-10-20T00:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/contracts/{contract_id}/link": {
      "post": {
        "operationId": "get_contract_link",
        "tags": [
          "Contracts"
        ],
        "summary": "Get the signing link (nothing sent)",
        "description": "As Copy link: the link to share yourself, with nothing emailed or texted. A draft's link doesn't open, so a draft comes out of draft first (status sent). A voided contract answers 409.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contract_id",
            "in": "path",
            "required": true,
            "description": "Contract id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "allow_blanks": {
                    "type": "boolean",
                    "description": "Let it go out with {{blanks}} left for the signer to fill on the signing page. Default false: 409 with the blanks."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "sign_url": "https://app.acme.com/sign/abc123",
                    "was_draft": true,
                    "contract": {
                      "id": "uuid",
                      "status": "sent",
                      "stage": "sent"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/contracts/{contract_id}/send": {
      "post": {
        "operationId": "send_contract",
        "tags": [
          "Contracts"
        ],
        "summary": "Send a contract for signature",
        "description": "Emails and / or texts the contract's lead the signing link (in the business's name, from its own email and number, as the key's person; billed like any text or email; opted-out leads aren't sent to), and the message shows in the lead's conversation. A draft or declined contract becomes sent; sending again re-sends the same link. sent_via lists what went, failures why a channel didn't. Signed or voided contracts answer 409; one with no lead too.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contract_id",
            "in": "path",
            "required": true,
            "description": "Contract id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string",
                    "description": "Default email.",
                    "enum": [
                      "email",
                      "sms",
                      "both"
                    ]
                  },
                  "note": {
                    "type": "string",
                    "description": "A short note with the link (max 1000)."
                  },
                  "allow_blanks": {
                    "type": "boolean",
                    "description": "Let it go out with {{blanks}} left for the signer to fill on the signing page. Default false: 409 with the blanks."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "channel": "both",
                "note": "Here's the agreement we talked about."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "sent_via": [
                      "email",
                      "text"
                    ],
                    "failures": [],
                    "sign_url": "https://app.acme.com/sign/abc123",
                    "contract": {
                      "id": "uuid",
                      "status": "sent",
                      "stage": "sent",
                      "send_count": 1
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/contracts/{contract_id}/void": {
      "post": {
        "operationId": "void_contract",
        "tags": [
          "Contracts"
        ],
        "summary": "Void a contract",
        "description": "As Void on the page: the signing link stops working right away. No undo; signed contracts can't be voided.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contract_id",
            "in": "path",
            "required": true,
            "description": "Contract id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/sales": {
      "get": {
        "operationId": "get_sales",
        "tags": [
          "Dashboard"
        ],
        "summary": "The Sales page: won jobs, parts, tips and profit",
        "description": "Settings > Features > Sales (off until an owner or admin turns it on: 403 feature_off until then). Won jobs in the window by the date they were won, as the Sales page adds them up: what customers paid, parts, tips and profit in total, per technician (deal_sold_by, else whoever the lead is assigned to), per lead source and per tag, plus the jobs themselves. Every group also has plans, monthly_cents (the monthly revenue its new plans add) and expected_cents (what its jobs are expected to bring in, one-time jobs at their amount), and each job its deal_type and plan (Plans & memberships). Record a sale with PATCH /v1/leads/{lead_id}: status won, deal_value_cents, deal_parts_cents, deal_tip_cents, deal_sold_by. Owners and admins only.\n\nRequired scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "1-365, default 30.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO start, instead of days.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO end, default now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/ad-insights": {
      "get": {
        "operationId": "get_ad_insights",
        "tags": [
          "Ads & Google"
        ],
        "summary": "The sub-account's Ad Insights",
        "description": "Exactly what its Ad Insights page shows: spend, leads, cost per lead, impressions and clicks for the Meta ad accounts attached to it (one, or all of them added up with each account's share in accounts, and an error for one that couldn't be read), each read with whichever connected Facebook profile can; per campaign and per ad, only the campaigns and ads the agency picked for this sub-account; ads left out (ads_hidden) when the agency hides them from the client. Amounts are added up in each account's own currency (accounts_meta). 403 ad_insights_off when Ad Insights is off for it.\n\nRequired scope: `meta:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ad_account_id",
            "in": "query",
            "required": false,
            "description": "\"all\" (default) or one attached account, act_123.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_preset",
            "in": "query",
            "required": false,
            "description": "Default last_30d.",
            "schema": {
              "type": "string",
              "enum": [
                "today",
                "yesterday",
                "last_7d",
                "last_14d",
                "last_30d",
                "last_90d",
                "this_month",
                "last_month",
                "maximum"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD, with until (instead of date_preset).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "query",
            "required": false,
            "description": "Only this campaign's ads.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace_id": "uuid",
                    "summary": {
                      "total_spend": 1240.5,
                      "total_leads": 62,
                      "avg_cost_per_lead": 20.01,
                      "total_impressions": 88120,
                      "total_clicks": 1904
                    },
                    "campaigns": [
                      {
                        "id": "120210000000",
                        "name": "Roof leads - Dallas",
                        "effective_status": "ACTIVE",
                        "spend": 820.1,
                        "leads": 44,
                        "impressions": 60110,
                        "clicks": 1302
                      }
                    ],
                    "ads": [],
                    "account_id": "all",
                    "account_ids": [
                      "act_123",
                      "act_456"
                    ],
                    "accounts_meta": {
                      "act_123": {
                        "name": "Acme Roofing",
                        "currency": "USD"
                      }
                    },
                    "accounts": [
                      {
                        "id": "act_123",
                        "total_spend": 820.1,
                        "total_leads": 44,
                        "campaigns": 3,
                        "running": 2,
                        "daily_budget": 40
                      }
                    ],
                    "window": {
                      "date_preset": "last_30d"
                    },
                    "filtered": true,
                    "ads_hidden": false
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-ads": {
      "get": {
        "operationId": "google_ads_status",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Google Ads: the connection and conversions sent",
        "description": "The sub-account's own Google Ads connection (Ad Insights > Google Ads): its ad accounts (customers), the one in use, whether booked / won leads are sent back as conversions and to which conversion actions, and the newest conversions with their status (pending, sent, failed, skipped) and how each lead was matched (gclid, gbraid, wbraid or email / phone). Owners and admins.\n\nRequired scope: `meta:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-ads/report": {
      "get": {
        "operationId": "google_ads_report",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Google Ads reports, live",
        "description": "One of the Google Ads screens, read live from Google for one of the sub-account's ad accounts: overview (campaigns with spend, clicks, conversions, totals and by day), ad_groups, keywords, ads, search_terms, negatives, campaign_settings (campaign_id), conversion_actions, bidding, recommendations. Nothing is changed. Owners and admins.\n\nRequired scope: `meta:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "report",
            "in": "query",
            "required": false,
            "description": "Default overview.",
            "schema": {
              "type": "string",
              "enum": [
                "overview",
                "ad_groups",
                "keywords",
                "ads",
                "search_terms",
                "negatives",
                "campaign_settings",
                "conversion_actions",
                "bidding",
                "recommendations"
              ]
            }
          },
          {
            "name": "range",
            "in": "query",
            "required": false,
            "description": "Default last_30d.",
            "schema": {
              "type": "string",
              "enum": [
                "today",
                "last_7d",
                "last_14d",
                "last_30d",
                "last_90d"
              ]
            }
          },
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "description": "One of its accounts (GET /v1/google-ads). Default: the one picked in the app.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "query",
            "required": false,
            "description": "Narrow to one campaign.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ad_group_id",
            "in": "query",
            "required": false,
            "description": "Narrow to one ad group.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "search_terms: rows, default 500.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/profiles": {
      "get": {
        "operationId": "list_gbp_profiles",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Google Business Profiles",
        "description": "Every Business Profile connected to the sub-account (it can have several): the location, rating, review count, whether Google's API answers for it, and its auto-reply, auto-post and photo settings; plus the Google logins connected (email only). Owners and admins.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/reviews": {
      "get": {
        "operationId": "list_gbp_reviews",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Google reviews and replies",
        "description": "Reviews synced from Google every 15 minutes, newest first, with the reply sent, the draft waiting for approval and its status. Owners and admins.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "profile_id",
            "in": "query",
            "required": false,
            "description": "One profile.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter.",
            "schema": {
              "type": "string",
              "enum": [
                "new",
                "draft",
                "sending",
                "sent",
                "skipped",
                "failed"
              ]
            }
          },
          {
            "name": "unanswered",
            "in": "query",
            "required": false,
            "description": "Only reviews without a reply.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "max_stars",
            "in": "query",
            "required": false,
            "description": "This many stars or fewer.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/posts": {
      "get": {
        "operationId": "list_gbp_posts",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Google Business Profile posts",
        "description": "Drafts, scheduled and published posts (title, text, button, offer, photo, when, the live link). Owners and admins.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "profile_id",
            "in": "query",
            "required": false,
            "description": "One profile.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "scheduled",
                "publishing",
                "published",
                "failed"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_gbp_post",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Create a Google post",
        "description": "A post in your own words, Google's rules checked first. Saved as a draft waiting for an OK, or approved at once with approve: true (it then goes up publicly on the business's Google profile on scheduled_for, on its own when the profile is connected to Google; otherwise the app shows it to copy). Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "profile_id": {
                    "type": "string",
                    "description": "One of its profiles (default its first)."
                  },
                  "kind": {
                    "type": "string",
                    "description": "STANDARD (an update, default), OFFER or EVENT.",
                    "enum": [
                      "STANDARD",
                      "OFFER",
                      "EVENT"
                    ]
                  },
                  "title": {
                    "type": "string",
                    "description": "OFFER and EVENT posts: up to 58 characters."
                  },
                  "body": {
                    "type": "string",
                    "description": "The post, up to 1,500 characters. Google refuses a phone number in the text: use the CALL button."
                  },
                  "cta_type": {
                    "type": "string",
                    "description": "The button (not on OFFER posts). Left out: the profile's own button and link.",
                    "enum": [
                      "NONE",
                      "LEARN_MORE",
                      "BOOK",
                      "CALL",
                      "ORDER",
                      "SHOP",
                      "SIGN_UP"
                    ]
                  },
                  "cta_url": {
                    "type": "string",
                    "description": "Full https:// link for the button (or an offer's link)."
                  },
                  "coupon_code": {
                    "type": "string",
                    "description": "OFFER: the code, up to 58 characters."
                  },
                  "offer_terms": {
                    "type": "string",
                    "description": "OFFER: terms and conditions."
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "EVENT: when it starts, ISO date-time."
                  },
                  "ends_at": {
                    "type": "string",
                    "description": "EVENT: when it ends; OFFER: when it ends (default 30 days). ISO."
                  },
                  "image_url": {
                    "type": "string",
                    "description": "A public link to a JPG or PNG (5 MB, 250 x 250 at least), copied in like an upload."
                  },
                  "scheduled_for": {
                    "type": "string",
                    "description": "When it goes up on Google, ISO date-time (default: the next posting day on the profile's schedule)."
                  },
                  "approve": {
                    "type": "boolean",
                    "description": "true: approved now (scheduled), as the app's Schedule it. Default false (a draft)."
                  }
                },
                "required": [
                  "workspace_id",
                  "body"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "body": "Spring tune-ups are open: book a visit and we'll check your whole system in under an hour.",
                "cta_type": "BOOK",
                "cta_url": "https://acmehvac.com/book"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "post": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "status": "draft",
                      "kind": "STANDARD",
                      "title": null,
                      "body": "Spring tune-ups are open: book a visit and we'll check your whole system in under an hour.",
                      "cta_type": "BOOK",
                      "cta_url": "https://acmehvac.com/book",
                      "coupon_code": null,
                      "offer_terms": null,
                      "starts_at": null,
                      "ends_at": null,
                      "image_url": null,
                      "scheduled_for": "2026-11-03T17:00:00Z",
                      "published_at": null,
                      "google_post_url": null,
                      "source": "user",
                      "error": null,
                      "created_at": "2026-10-29T15:00:00Z",
                      "updated_at": "2026-10-29T15:00:00Z"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/optimizer": {
      "get": {
        "operationId": "list_gbp_optimizer",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Profile Optimizer runs",
        "description": "Each Profile Optimizer check, newest first: the score, what it found, the keywords, its suggestions and the changes made from it (and undone). Owners and admins.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "profile_id",
            "in": "query",
            "required": false,
            "description": "One profile.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-25, default 5.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/map-rankings": {
      "get": {
        "operationId": "list_map_rankings",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Map Rankings: keywords and scans",
        "description": "Where the business ranks on Google Maps across a grid around it: the keywords tracked (grid, schedule, next and last scan) and the scans, newest first, each with its summary (points, found, top 3, average rank). Owners and admins.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "keyword_id",
            "in": "query",
            "required": false,
            "description": "One keyword's scans.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "profile_id",
            "in": "query",
            "required": false,
            "description": "One profile.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/map-rankings/scans/{scan_id}": {
      "get": {
        "operationId": "get_rank_scan",
        "tags": [
          "Ads & Google"
        ],
        "summary": "One Map Rankings scan",
        "description": "Every grid point with its rank (null: not in the top 20), the competitors found, and the next steps to rank #1 once they've been written (they are written when the scan is first opened in the app). Owners and admins.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "scan_id",
            "in": "path",
            "required": true,
            "description": "Scan id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/listings": {
      "get": {
        "operationId": "get_listings",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Business Listings",
        "description": "The directories the business is being listed on: each one's status (queued, scanning, listed, mismatch, not_listed, working, needs_you, submitted, live, owner, skipped, failed) with counts, what differs or what's needed from the owner, the listing link, and the latest activity. Passwords and verification codes are never returned. Owners and admins.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/posts/write": {
      "post": {
        "operationId": "write_gbp_posts",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Have the AI write Google posts",
        "description": "The AI writes 1-8 posts in the business's voice from an idea, or one post about each photo of the work in photo_urls (the post carries that photo), dated on the profile's posting days. They are saved as drafts: nothing goes to Google until one is approved or published. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "profile_id": {
                    "type": "string",
                    "description": "One of its profiles (default its first)."
                  },
                  "idea": {
                    "type": "string",
                    "description": "What to write about, up to 600 characters (needed for OFFER and EVENT)."
                  },
                  "kind": {
                    "type": "string",
                    "description": "STANDARD (an update, default), OFFER or EVENT.",
                    "enum": [
                      "STANDARD",
                      "OFFER",
                      "EVENT"
                    ]
                  },
                  "count": {
                    "type": "number",
                    "description": "1-8, default 4 (with photo_urls: one per photo)."
                  },
                  "photo_urls": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 8 public JPG or PNG links of the business's work."
                  },
                  "cta_type": {
                    "type": "string",
                    "description": "The button (not on OFFER posts). Left out: the profile's own button and link.",
                    "enum": [
                      "NONE",
                      "LEARN_MORE",
                      "BOOK",
                      "CALL",
                      "ORDER",
                      "SHOP",
                      "SIGN_UP"
                    ]
                  },
                  "cta_url": {
                    "type": "string",
                    "description": "Full https:// link for the button (or an offer's link)."
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "EVENT: when it starts, ISO date-time."
                  },
                  "ends_at": {
                    "type": "string",
                    "description": "EVENT: when it ends; OFFER: when it ends (default 30 days). ISO."
                  },
                  "scheduled_for": {
                    "type": "string",
                    "description": "The date of a single post, ISO."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "idea": "Free furnace check with any tune-up this month",
                "count": 2
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "posts": [
                      {
                        "id": "uuid",
                        "workspace_id": "uuid",
                        "profile_id": "uuid",
                        "status": "draft",
                        "kind": "STANDARD",
                        "title": null,
                        "body": "Spring tune-ups are open: book a visit and we'll check your whole system in under an hour.",
                        "cta_type": "BOOK",
                        "cta_url": "https://acmehvac.com/book",
                        "coupon_code": null,
                        "offer_terms": null,
                        "starts_at": null,
                        "ends_at": null,
                        "image_url": null,
                        "scheduled_for": "2026-11-03T17:00:00Z",
                        "published_at": null,
                        "google_post_url": null,
                        "source": "user",
                        "error": null,
                        "created_at": "2026-10-29T15:00:00Z",
                        "updated_at": "2026-10-29T15:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/posts/{post_id}": {
      "patch": {
        "operationId": "update_gbp_post",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Edit a Google post",
        "description": "Change a post that isn't on Google yet (draft, scheduled or failed): send only what changes; image_url null removes the photo. Google's rules are checked on the result. A scheduled post stays approved for its new date. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "post_id",
            "in": "path",
            "required": true,
            "description": "Post id (GET /v1/google-business/posts).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "OFFER and EVENT posts: up to 58 characters."
                  },
                  "body": {
                    "type": "string",
                    "description": "The post, up to 1,500 characters. Google refuses a phone number in the text: use the CALL button."
                  },
                  "cta_type": {
                    "type": "string",
                    "description": "The button (not on OFFER posts). Left out: the profile's own button and link.",
                    "enum": [
                      "NONE",
                      "LEARN_MORE",
                      "BOOK",
                      "CALL",
                      "ORDER",
                      "SHOP",
                      "SIGN_UP"
                    ]
                  },
                  "cta_url": {
                    "type": "string",
                    "description": "Full https:// link for the button (or an offer's link)."
                  },
                  "coupon_code": {
                    "type": "string",
                    "description": "OFFER: the code, up to 58 characters."
                  },
                  "offer_terms": {
                    "type": "string",
                    "description": "OFFER: terms and conditions."
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "EVENT: when it starts, ISO date-time."
                  },
                  "ends_at": {
                    "type": "string",
                    "description": "EVENT: when it ends; OFFER: when it ends (default 30 days). ISO."
                  },
                  "scheduled_for": {
                    "type": "string",
                    "description": "When it goes up on Google, ISO date-time (default: the next posting day on the profile's schedule)."
                  },
                  "image_url": {
                    "type": "string",
                    "description": "A public JPG or PNG link, or null to remove the photo."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "body": "Spring tune-ups are open this week only.",
                "scheduled_for": "2026-11-04T17:00:00Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "post": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "status": "draft",
                      "kind": "STANDARD",
                      "title": null,
                      "body": "Spring tune-ups are open: book a visit and we'll check your whole system in under an hour.",
                      "cta_type": "BOOK",
                      "cta_url": "https://acmehvac.com/book",
                      "coupon_code": null,
                      "offer_terms": null,
                      "starts_at": null,
                      "ends_at": null,
                      "image_url": null,
                      "scheduled_for": "2026-11-03T17:00:00Z",
                      "published_at": null,
                      "google_post_url": null,
                      "source": "user",
                      "error": null,
                      "created_at": "2026-10-29T15:00:00Z",
                      "updated_at": "2026-10-29T15:00:00Z"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_gbp_post",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Delete a Google post",
        "description": "Removes the post from the app. A post already published stays on Google (still_on_google: true); remove it there. Not while it is going up. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "post_id",
            "in": "path",
            "required": true,
            "description": "Post id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "deleted": true,
                    "id": "uuid",
                    "still_on_google": false
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/posts/{post_id}/approve": {
      "post": {
        "operationId": "approve_gbp_post",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Approve a Google post",
        "description": "A draft becomes scheduled: it goes up publicly on the business's Google profile on its date (on its own when the profile is connected to Google). Google's rules are checked first and the date must not have passed (send scheduled_for to move it). Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "post_id",
            "in": "path",
            "required": true,
            "description": "Post id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scheduled_for": {
                    "type": "string",
                    "description": "A new date, ISO (default the post's own)."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "post": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "status": "scheduled",
                      "kind": "STANDARD",
                      "title": null,
                      "body": "Spring tune-ups are open: book a visit and we'll check your whole system in under an hour.",
                      "cta_type": "BOOK",
                      "cta_url": "https://acmehvac.com/book",
                      "coupon_code": null,
                      "offer_terms": null,
                      "starts_at": null,
                      "ends_at": null,
                      "image_url": null,
                      "scheduled_for": "2026-11-03T17:00:00Z",
                      "published_at": null,
                      "google_post_url": null,
                      "source": "user",
                      "error": null,
                      "created_at": "2026-10-29T15:00:00Z",
                      "updated_at": "2026-10-29T15:00:00Z"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/posts/{post_id}/unschedule": {
      "post": {
        "operationId": "unschedule_gbp_post",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Unschedule a Google post",
        "description": "A scheduled post goes back to the drafts, so it doesn't go up on its date. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "post_id",
            "in": "path",
            "required": true,
            "description": "Post id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "post": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "status": "draft",
                      "kind": "STANDARD",
                      "title": null,
                      "body": "Spring tune-ups are open: book a visit and we'll check your whole system in under an hour.",
                      "cta_type": "BOOK",
                      "cta_url": "https://acmehvac.com/book",
                      "coupon_code": null,
                      "offer_terms": null,
                      "starts_at": null,
                      "ends_at": null,
                      "image_url": null,
                      "scheduled_for": "2026-11-03T17:00:00Z",
                      "published_at": null,
                      "google_post_url": null,
                      "source": "user",
                      "error": null,
                      "created_at": "2026-10-29T15:00:00Z",
                      "updated_at": "2026-10-29T15:00:00Z"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/posts/{post_id}/publish": {
      "post": {
        "operationId": "publish_gbp_post",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Post on Google now",
        "description": "Publishes the post publicly on the business's Google profile right away, as the app's Post now (a draft, scheduled or failed post). It needs the profile connected to Google; Google's rules are checked first. When Google doesn't take it the answer is 409 with details.code: awaiting_google (Google hasn't opened its API to the account yet) or not_posted (the reason is in error and on the post). Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "post_id",
            "in": "path",
            "required": true,
            "description": "Post id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "post": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "status": "published",
                      "kind": "STANDARD",
                      "title": null,
                      "body": "Spring tune-ups are open: book a visit and we'll check your whole system in under an hour.",
                      "cta_type": "BOOK",
                      "cta_url": "https://acmehvac.com/book",
                      "coupon_code": null,
                      "offer_terms": null,
                      "starts_at": null,
                      "ends_at": null,
                      "image_url": null,
                      "scheduled_for": "2026-11-03T17:00:00Z",
                      "published_at": "2026-10-29T15:02:11Z",
                      "google_post_url": "https://local.google.com/place?id=123&use=posts&lpsid=456",
                      "source": "user",
                      "error": null,
                      "created_at": "2026-10-29T15:00:00Z",
                      "updated_at": "2026-10-29T15:00:00Z"
                    },
                    "url": "https://local.google.com/place?id=123&use=posts&lpsid=456"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/reviews/{review_id}/draft": {
      "post": {
        "operationId": "draft_gbp_reply",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Have the AI draft a review reply",
        "description": "The AI writes the reply in the business's voice and tone (the tool's reply settings), or rewrites it following instruction, and saves it as the review's draft. Nothing is posted. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "review_id",
            "in": "path",
            "required": true,
            "description": "Review id (GET /v1/google-business/reviews).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "instruction": {
                    "type": "string",
                    "description": "How to write or change it, up to 600 characters (e.g. mention the warranty)."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "review": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "source": "api",
                      "reviewer_name": "Dana R.",
                      "star_rating": 5,
                      "comment": "Fast and friendly.",
                      "reply_text": null,
                      "draft_reply": "Thank you, Dana! It was a pleasure.",
                      "status": "draft",
                      "sent_by": null,
                      "error": null
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/reviews/{review_id}": {
      "patch": {
        "operationId": "update_gbp_review",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Edit a reply draft, or skip a review",
        "description": "draft_reply replaces the saved draft (nothing is posted); skip: true takes the review off the list to answer, false brings it back. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "review_id",
            "in": "path",
            "required": true,
            "description": "Review id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "draft_reply": {
                    "type": "string",
                    "description": "The reply draft, up to 4,000 characters."
                  },
                  "skip": {
                    "type": "boolean",
                    "description": "true skips the review; false brings it back."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "draft_reply": "Thank you, Dana! We're glad the new furnace is keeping you warm."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "review": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "source": "api",
                      "reviewer_name": "Dana R.",
                      "star_rating": 5,
                      "comment": "Fast and friendly.",
                      "reply_text": null,
                      "draft_reply": "Thank you, Dana! It was a pleasure.",
                      "status": "draft",
                      "sent_by": null,
                      "error": null
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/reviews/{review_id}/reply": {
      "post": {
        "operationId": "reply_gbp_review",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Reply to a Google review",
        "description": "Posts text (or the saved draft) as the business's public reply under the review on Google, as the app's Post reply; on a review already answered it replaces the reply. Only reviews that came from Google with the profile connected: one read from Google Maps or pasted in answers 409 copy_by_hand (paste the reply on Google yourself); awaiting_google and not_posted as for posts. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "review_id",
            "in": "path",
            "required": true,
            "description": "Review id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "description": "The reply, up to 4,000 characters (default: the saved draft)."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "text": "Thank you, Dana! We're glad the new furnace is keeping you warm."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "review": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "source": "api",
                      "reviewer_name": "Dana R.",
                      "star_rating": 5,
                      "comment": "Fast and friendly.",
                      "reply_text": "Thank you, Dana! We're glad the new furnace is keeping you warm.",
                      "draft_reply": "Thank you, Dana! It was a pleasure.",
                      "status": "sent",
                      "sent_by": "user",
                      "error": null
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/optimizer/apply": {
      "post": {
        "operationId": "apply_gbp_optimizer",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Apply Profile Optimizer suggestions",
        "description": "Makes the picked suggestions of a Profile Optimizer check (the newest, or run_id) on the business's live Google profile, which everyone sees: categories added, a new description, services, attributes turned on, the Book button linked to one of its calendars, holiday hours. Ids come from the run's suggestions (GET /v1/google-business/optimizer). Each change is recorded and can be undone; dry_run: true asks Google to check them without changing anything. results says what worked. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "profile_id": {
                    "type": "string",
                    "description": "One of its profiles (default its first)."
                  },
                  "run_id": {
                    "type": "string",
                    "description": "The check (default the newest)."
                  },
                  "categories": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Category ids to add (suggestions.categories[].id)."
                  },
                  "description": {
                    "type": "string",
                    "description": "The description to use, 50-750 characters (the suggested one, edited or not)."
                  },
                  "services": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Service ids (suggestions.services[].id)."
                  },
                  "attributes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Attribute ids to turn on (suggestions.attributes[].id)."
                  },
                  "booking_calendar_id": {
                    "type": "string",
                    "description": "The calendar the Book button opens (suggestions.booking.calendars[].id)."
                  },
                  "holidays": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "[{ key, closed }] from suggestions.holidays (closed defaults to true)."
                  },
                  "dry_run": {
                    "type": "boolean",
                    "description": "true: Google validates, nothing changes."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "categories": [
                  "categories/gcid:hvac_contractor"
                ],
                "attributes": [
                  "attributes/has_onsite_services"
                ],
                "dry_run": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "dry_run": true,
                    "results": [
                      {
                        "key": "categories",
                        "ok": true,
                        "validated": true,
                        "message": null
                      },
                      {
                        "key": "attributes",
                        "ok": true,
                        "validated": true,
                        "message": "Not sent in a test run."
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/google-business/optimizer/changes/{change_id}/undo": {
      "post": {
        "operationId": "undo_gbp_optimizer_change",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Undo a Profile Optimizer change",
        "description": "Puts what was on the Google profile before that change back (the change ids are in GET /v1/google-business/optimizer, runs[].changes). Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "change_id",
            "in": "path",
            "required": true,
            "description": "Change id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "change": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "run_id": "uuid",
                      "profile_id": "uuid",
                      "kind": "categories",
                      "summary": "Added HVAC contractor",
                      "applied_at": "2026-10-28T16:00:00Z",
                      "undone_at": "2026-10-29T15:00:00Z"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/map-rankings/keywords": {
      "post": {
        "operationId": "create_rank_keyword",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Track a Map Rankings keyword",
        "description": "Tracks where the business ranks on Google Maps for a search, across a grid of points centred on the business (or center_lat / center_lng). On a schedule (weekly by default) its first scan runs within minutes and then on the schedule; each scan costs 0.25 (5x5), 0.5 (7x7), 1 (9x9) or 2 (13x13) credits from the account, the first 3 scans of a business are free. Up to 10 keywords per business. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "profile_id": {
                    "type": "string",
                    "description": "One of its profiles (default its first)."
                  },
                  "keyword": {
                    "type": "string",
                    "description": "What people search, e.g. \"furnace repair\" (up to 80 characters)."
                  },
                  "grid_size": {
                    "type": "number",
                    "description": "Points per side: 5, 7 (default), 9 or 13."
                  },
                  "spacing_m": {
                    "type": "number",
                    "description": "Meters between points: 402, 805, 1609 (default), 2414, 3219, 4828 or 8047."
                  },
                  "center_lat": {
                    "type": "number",
                    "description": "With center_lng: the grid's centre (default the business)."
                  },
                  "center_lng": {
                    "type": "number",
                    "description": "With center_lat."
                  },
                  "schedule": {
                    "type": "string",
                    "description": "Default weekly.",
                    "enum": [
                      "off",
                      "weekly",
                      "monthly"
                    ]
                  }
                },
                "required": [
                  "workspace_id",
                  "keyword"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "keyword": "furnace repair",
                "grid_size": 7,
                "schedule": "weekly"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "keyword": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "keyword": "furnace repair",
                      "grid_size": 7,
                      "spacing_m": 1609,
                      "center_lat": 30.33,
                      "center_lng": -81.66,
                      "schedule": "weekly",
                      "next_run_at": "2026-10-29T15:00:00Z",
                      "last_scan_at": null,
                      "paused_reason": null,
                      "created_at": "2026-10-29T15:00:00Z"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/map-rankings/keywords/{keyword_id}": {
      "delete": {
        "operationId": "delete_rank_keyword",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Stop tracking a keyword",
        "description": "No more scans for it; its past scans stay readable. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "keyword_id",
            "in": "path",
            "required": true,
            "description": "Keyword id (GET /v1/map-rankings).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "deleted": true,
                    "id": "uuid"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/map-rankings/keywords/{keyword_id}/scan": {
      "post": {
        "operationId": "start_rank_scan",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Scan a keyword now",
        "description": "Starts a scan of the keyword's grid (202): 0.25 to 2 credits by grid size, from the account (free for the first 3 scans of a business); points Google never answers are refunded. One scan of a keyword at a time: while one runs, that one is returned. It takes a few minutes; read it with GET /v1/map-rankings/scans/{scan_id}. 402 when there aren't enough credits. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "keyword_id",
            "in": "path",
            "required": true,
            "description": "Keyword id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "scan": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "keyword_id": "uuid",
                      "keyword": "furnace repair",
                      "grid_size": 7,
                      "spacing_m": 1609,
                      "center_lat": 30.33,
                      "center_lng": -81.66,
                      "status": "running",
                      "trigger": "manual",
                      "credits": 0.5,
                      "summary": null,
                      "error": null,
                      "created_at": "2026-10-29T15:00:00Z",
                      "finished_at": null
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/listings/scan": {
      "post": {
        "operationId": "scan_listings",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Check the business on the directories",
        "description": "The free check of every directory (202): starts Business Listings for the business, or looks again for the current run (at most every 6 hours). business sets the details every directory gets (default: from its Google profile); they can change until something has been sent. Read the results with GET /v1/listings. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "profile_id": {
                    "type": "string",
                    "description": "One of its profiles (default its first)."
                  },
                  "business": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "name, street, unit, city, state, zip, country, phone, website, email, category, categories, description, tagline, hours, services, first_name, last_name, year_founded, logo_url, service_area_only."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "campaign": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "status": "scanning",
                      "business": {
                        "name": "Acme HVAC"
                      },
                      "inbox_email": null,
                      "live": null,
                      "scanned_at": null,
                      "started_at": null,
                      "finished_at": null,
                      "recheck_at": null,
                      "created_at": "2026-10-29T15:00:00Z",
                      "updated_at": "2026-10-29T15:00:00Z"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/listings/start": {
      "post": {
        "operationId": "start_listings",
        "tags": [
          "Ads & Google"
        ],
        "summary": "Start listing the business",
        "description": "Signs the business up, with its details, on every directory the check didn't find it on or found with wrong details, as the app's Start: those listings are public. 8 credits a directory from the account, refunded for each that ends without a listing; credits_charged says how many. Some directories then need the owner (a code, a robot check): GET /v1/listings shows them. Owners and admins.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.\n\nSpends credits.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "campaign_id": {
                    "type": "string",
                    "description": "Default the current run (GET /v1/listings)."
                  }
                },
                "required": [
                  "workspace_id"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "started": true,
                    "credits_charged": 96,
                    "campaign": {
                      "id": "uuid",
                      "workspace_id": "uuid",
                      "profile_id": "uuid",
                      "status": "running"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/ConfirmationRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/support/tickets": {
      "get": {
        "operationId": "list_support_tickets",
        "tags": [
          "Support"
        ],
        "summary": "Support tickets",
        "description": "The sub-account's Support tickets, newest activity first: subject, category, status (open, answered, resolved), who is answering (the assistant or the team), unread state and rating. The agency team sees every ticket; a client admin every ticket of the sub-account; anyone else their own. 403 feature_off while Support is off for the sub-account.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter.",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "answered",
                "resolved"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "create_support_ticket",
        "tags": [
          "Support"
        ],
        "summary": "Open a support ticket",
        "description": "What the sub-account's Support page does: the ticket goes to the account's support team, their AI assistant answers first (usually within a minute; read the ticket for the answer) and hands it to a person when it can't solve it. Replies also arrive by email. Any member of the sub-account; at most 5 new tickets an hour per person. Files are added in the app. 403 feature_off while Support is off for the sub-account.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_id": {
                    "type": "string",
                    "description": "Sub-account id (omit on keys restricted to one sub-account)."
                  },
                  "message": {
                    "type": "string",
                    "description": "What is going on (2-20,000 characters)."
                  },
                  "subject": {
                    "type": "string",
                    "description": "Up to 160 characters. Default: the start of the message."
                  },
                  "topic": {
                    "type": "string",
                    "description": "Default question.",
                    "enum": [
                      "question",
                      "problem",
                      "billing",
                      "request",
                      "ads",
                      "other"
                    ]
                  }
                },
                "required": [
                  "workspace_id",
                  "message"
                ],
                "additionalProperties": true
              },
              "example": {
                "workspace_id": "uuid",
                "subject": "Texts not sending",
                "message": "Since this morning our texts to new leads fail. We changed nothing.",
                "topic": "problem"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "ticket": {
                      "id": "uuid",
                      "number": 1031,
                      "workspace_id": "uuid",
                      "subject": "Texts not sending",
                      "category": "problem",
                      "status": "open",
                      "handler": "assistant",
                      "created_at": "2026-10-29T15:02:11Z"
                    },
                    "assistant_answering": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/support/tickets/{ticket_id}": {
      "get": {
        "operationId": "get_support_ticket",
        "tags": [
          "Support"
        ],
        "summary": "One support ticket and its messages",
        "description": "Every message in order (the customer, the assistant, the team), with the files' names. Internal notes only for the agency team.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "ticket_id",
            "in": "path",
            "required": true,
            "description": "Ticket id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "patch": {
        "operationId": "update_support_ticket",
        "tags": [
          "Support"
        ],
        "summary": "Status, priority, assignee, who answers (support team)",
        "description": "The Support desk's changes, applied in this order: assigned_to (a team member's user id who can open the sub-account, or null to unassign; the new assignee is emailed and pushed the ticket), priority, handler (staff: the team answers from now on; assistant: hand it back, and the assistant answers at once if the client is waiting), status (resolved, or open to reopen). If one fails, the ones before it stay. Returns the ticket. The agency's owners and team only.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ticket_id",
            "in": "path",
            "required": true,
            "description": "Ticket id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "resolved closes it; open reopens it.",
                    "enum": [
                      "open",
                      "resolved"
                    ]
                  },
                  "priority": {
                    "type": "string",
                    "description": "Set by hand, it stays even when the assistant suggests another.",
                    "enum": [
                      "urgent",
                      "high",
                      "normal",
                      "low"
                    ]
                  },
                  "assigned_to": {
                    "type": "string",
                    "description": "A team member's user id, or null to unassign."
                  },
                  "handler": {
                    "type": "string",
                    "description": "Who answers the client.",
                    "enum": [
                      "staff",
                      "assistant"
                    ]
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "assigned_to": "uuid",
                "priority": "high",
                "handler": "staff"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "ticket": {
                      "id": "uuid",
                      "number": 1031,
                      "status": "open",
                      "handler": "staff",
                      "assigned_to": "uuid",
                      "priority": "high"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/support/tickets/{ticket_id}/messages": {
      "post": {
        "operationId": "add_support_message",
        "tags": [
          "Support"
        ],
        "summary": "Add to a ticket (as the client)",
        "description": "A new message on a ticket the key's person opened (a client admin: any of the sub-account's tickets), as on the Support page. While the assistant handles the ticket it answers again; otherwise the team is told. answered_by says which. The support team answers with POST /v1/support/tickets/{ticket_id}/reply instead (403 here).\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ticket_id",
            "in": "path",
            "required": true,
            "description": "Ticket id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "Up to 20,000 characters."
                  }
                },
                "required": [
                  "message"
                ],
                "additionalProperties": true
              },
              "example": {
                "message": "Still failing after the restart."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "ticket_id": "uuid",
                    "added": true,
                    "answered_by": "assistant"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/support/tickets/{ticket_id}/reply": {
      "post": {
        "operationId": "reply_support_ticket",
        "tags": [
          "Support"
        ],
        "summary": "Answer the client (support team)",
        "description": "The support team's reply, as from the Support desk: it shows on the client's Support page, is emailed to them (emailed says whether it went) and pushed to their phone. A reply still holding [check: ...] parts (from the assistant's suggested reply) is refused until they are filled in. The agency's owners and team only; others get 403.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ticket_id",
            "in": "path",
            "required": true,
            "description": "Ticket id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "The reply, up to 20,000 characters."
                  }
                },
                "required": [
                  "body"
                ],
                "additionalProperties": true
              },
              "example": {
                "body": "Thanks Pat, your texting number was paused by the carrier. We've fixed it; texts are going out again."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "ticket_id": "uuid",
                    "message_id": "uuid",
                    "emailed": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/support/tickets/{ticket_id}/notes": {
      "post": {
        "operationId": "add_support_note",
        "tags": [
          "Support"
        ],
        "summary": "Add an internal note (support team)",
        "description": "A note on the ticket only the support team sees; the client is not told. The agency's owners and team only.\n\nRequired scope: `workspaces:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ticket_id",
            "in": "path",
            "required": true,
            "description": "Ticket id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "The note, up to 20,000 characters."
                  }
                },
                "required": [
                  "body"
                ],
                "additionalProperties": true
              },
              "example": {
                "body": "Carrier flagged the number on 10-28. Asked them to lift it."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "ticket_id": "uuid",
                    "message_id": "uuid",
                    "internal": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/activity": {
      "get": {
        "operationId": "list_activity",
        "tags": [
          "Dashboard"
        ],
        "summary": "The Activity log",
        "description": "Everything that happened in the sub-account, newest first, as Settings > Activity log shows it: every change (the fields that changed, before and after, secrets hidden) with who made it (a person, an API key, the AI or an automation), their IP and browser, who opened what (contacts, conversations, recordings, exports), and messages, calls, bookings, automation runs, notifications and errors. Keyset paged by next_cursor. summary=true adds the page's counts by type and by day and who did the most. Owners and admins only.\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "1-365, default 30.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO start, instead of days.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO end, default now.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Comma-separated: change, access, lead, inbound, outbound, ai, call, booking, appointment, automation, notification, webhook, api, error, system.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "actor",
            "in": "query",
            "required": false,
            "description": "A user id, or user, api, ai, automation, lead or system.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "Only what happened to this lead.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Words in the title or description, or a contact's name, phone or email.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "summary",
            "in": "query",
            "required": false,
            "description": "Also return counts by type and day and the most active people.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "description": "IANA time zone for summary days, default UTC.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-200, default 50.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "workspace_id": "uuid",
                    "window": {
                      "from": "2026-09-24T00:00:00Z",
                      "to": "2026-10-24T00:00:00Z",
                      "days": 30
                    },
                    "activity": [
                      {
                        "id": "al:uuid",
                        "occurred_at": "2026-10-24T15:02:11Z",
                        "source": "al",
                        "category": "change",
                        "title": null,
                        "description": "Changed the AI texting settings",
                        "status": "ok",
                        "lead_id": null,
                        "lead_name": null,
                        "lead_phone": null,
                        "actor_id": "uuid",
                        "actor_kind": "api",
                        "actor_label": "API key · Zapier",
                        "actor_name": "Pat Owner",
                        "actor_email": "pat@example.com",
                        "metadata": null,
                        "audit": {
                          "entity_table": "workspace_outreach_settings",
                          "action": "update",
                          "changed_fields": [
                            "ai_reply_enabled"
                          ],
                          "old_values": {
                            "ai_reply_enabled": false
                          },
                          "new_values": {
                            "ai_reply_enabled": true
                          },
                          "ip_address": "203.0.113.4",
                          "user_agent": "Zapier",
                          "source": "api"
                        }
                      }
                    ],
                    "has_more": true,
                    "next_cursor": "MjAyNi0xMC0yNFQxNTowMjoxMVp8YWw6dXVpZA"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/review-funnels": {
      "get": {
        "operationId": "list_review_funnels",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "Review pages",
        "description": "Tools → Review funnel: each review page, which one is the default, its rating threshold, where happy customers are sent, and its public_path (/r/<code> on the sub-account's domain).\n\nRequired scope: `workspaces:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/review-requests": {
      "get": {
        "operationId": "list_review_requests",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "Who was asked for a review, and what they did",
        "description": "Sent, opened, rated (1-5), clicked through to a review site, and any private feedback, most recently active first.\n\nRequired scope: `leads:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "description": "Sub-account id (omit on keys restricted to one sub-account).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "funnel_id",
            "in": "query",
            "required": false,
            "description": "One review page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "description": "One lead.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "rated",
            "in": "query",
            "required": false,
            "description": "Only rated (true) or not yet rated (false).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO time, changed at or after.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-100, default 25.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}/review-link": {
      "post": {
        "operationId": "create_review_link",
        "tags": [
          "Invoices & reviews"
        ],
        "summary": "A lead's personal review link",
        "description": "Makes (or returns) the lead's link on a review page, like Copy review link on the lead. Sends nothing; share it however you like. Owners and admins only.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "funnel_id": {
                    "type": "string",
                    "description": "Which review page. Default: the default page."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lead_id": "uuid",
                    "code": "k3x9p2",
                    "url": "https://app.example-agency.com/r/k3x9p2"
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/notes/{note_id}": {
      "patch": {
        "operationId": "update_note",
        "tags": [
          "CRM leads"
        ],
        "summary": "Edit a note you wrote",
        "description": "Note ids are the note events in GET /v1/leads/{lead_id}/timeline. People can edit only their own notes, as in the app.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "note_id",
            "in": "path",
            "required": true,
            "description": "Note id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "The note, max 4000."
                  }
                },
                "required": [
                  "body"
                ],
                "additionalProperties": true
              },
              "example": {
                "body": "Prefers a call after 5pm."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "delete_note",
        "tags": [
          "CRM leads"
        ],
        "summary": "Delete a note",
        "description": "Owners and admins.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "note_id",
            "in": "path",
            "required": true,
            "description": "Note id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/leads/{lead_id}/do-not-contact": {
      "post": {
        "operationId": "set_do_not_contact",
        "tags": [
          "CRM leads"
        ],
        "summary": "Mark a lead do-not-contact",
        "description": "Like Do not contact on the Leads page: no more emails, and texts to the lead's number are blocked. The app has no undo, so it takes an owner or admin.\n\nRequired scope: `leads:write`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "path",
            "required": true,
            "description": "Lead id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "lead_id": "uuid",
                    "do_not_contact": true,
                    "emails_blocked": true,
                    "texts_blocked": true
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/billing/summary": {
      "get": {
        "operationId": "billing_summary",
        "tags": [
          "Account"
        ],
        "summary": "Credit balance and plan summary",
        "description": "The credit pool this key spends from, computed like the app's credit meter. Team members share their account owner's pool (shared_team_pool: true) and see that pool. credits.total_available = monthly_remaining + purchased + rollover (rollover only until rollover_expires_at); owed is credit debt taken off the next top-up or renewal. spent_this_cycle is net of refunds since cycle_started_at. cancels_at: a cancelled plan keeps working until then (Plan & billing: \"Your plan ends on ...\"); null when it renews.\n\nRequired scope: `billing:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "plan": "premium",
                    "shared_team_pool": false,
                    "cycle_started_at": "2026-09-12T00:00:00Z",
                    "next_reset_at": "2026-10-12T00:00:00Z",
                    "paused_until": null,
                    "cancels_at": null,
                    "pending_downgrade": null,
                    "credits": {
                      "monthly_remaining": 212,
                      "monthly_limit": 300,
                      "purchased": 150,
                      "rollover": 40,
                      "rollover_expires_at": "2026-10-12T00:00:00.000Z",
                      "owed": 0,
                      "total_available": 402
                    },
                    "spent_this_cycle": 88,
                    "auto_topup": {
                      "enabled": true,
                      "threshold_credits": 50,
                      "topup_credits": 500
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/team/credits": {
      "get": {
        "operationId": "team_credits",
        "tags": [
          "Account"
        ],
        "summary": "Credits used by each team member",
        "description": "For each team member (Settings → Team): credits_used_this_cycle (what their own runs took from the shared pool since cycle_started_at, net of refunds), monthly_limit (null = no limit), remaining (null when there is no limit), limit_reached, and last_job (their newest ad-tool job: id, tool_type, status, created_at, and the sub-account it ran for). Pending invites are listed with status pending. The team owner sees every member plus themself as `owner`; a team member's key sees only their own row (owner is null). pool is the shared balance (total_available as in GET /v1/billing/summary) and what the whole account spent this cycle; members_used_this_cycle adds up the members' rows. A member's count starts again at 0 on next_reset_at.\n\nRequired scope: `billing:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "team_id": "uuid",
                    "viewer": "owner",
                    "cycle_started_at": "2026-10-08T13:51:54.000Z",
                    "next_reset_at": "2026-11-08T13:51:54+00:00",
                    "pool": {
                      "total_available": 1840,
                      "spent_this_cycle": 1712.5
                    },
                    "members_used_this_cycle": 1067.5,
                    "owner": {
                      "user_id": "uuid",
                      "email": "owner@agency.com",
                      "name": "Pat Owner",
                      "last_job": {
                        "id": "uuid",
                        "tool_type": "copy_ad",
                        "status": "completed",
                        "created_at": "2026-10-22T09:12:00Z",
                        "workspace_id": null
                      }
                    },
                    "members": [
                      {
                        "member_id": "uuid",
                        "user_id": "uuid",
                        "email": "sam@agency.com",
                        "name": "Sam Lee",
                        "status": "active",
                        "joined_at": "2026-08-02T10:00:00Z",
                        "credits_used_this_cycle": 241.5,
                        "monthly_limit": 500,
                        "remaining": 258.5,
                        "limit_reached": false,
                        "last_job": {
                          "id": "uuid",
                          "tool_type": "generate_facebook_ads",
                          "status": "completed",
                          "created_at": "2026-10-22T08:40:00Z",
                          "workspace_id": "uuid"
                        }
                      },
                      {
                        "member_id": "uuid",
                        "user_id": null,
                        "email": "new@agency.com",
                        "name": null,
                        "status": "pending",
                        "joined_at": null,
                        "credits_used_this_cycle": 0,
                        "monthly_limit": null,
                        "remaining": null,
                        "limit_reached": false,
                        "last_job": null
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/team/members/{member_id}": {
      "patch": {
        "operationId": "update_team_member",
        "tags": [
          "Account"
        ],
        "summary": "Set a team member's monthly credit limit",
        "description": "Like Settings → Team → a member's access: monthly_credit_limit is a whole number of credits per cycle, or null for no limit. Once a member reaches it, their runs are refused until the next reset or a higher limit. Only the team owner can do this. member_id is the member_id from GET /v1/team/credits (the member's user_id works too). Returns the member as GET /v1/team/credits shows them.\n\nRequired scope: `billing:admin`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "member_id",
            "in": "path",
            "required": true,
            "description": "member_id (or user_id) of the team member.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "monthly_credit_limit": {
                    "type": "number",
                    "description": "Credits per cycle, 0-1000000, or null for no limit."
                  }
                },
                "required": [
                  "monthly_credit_limit"
                ],
                "additionalProperties": true
              },
              "example": {
                "monthly_credit_limit": 500
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "member": {
                      "member_id": "uuid",
                      "user_id": "uuid",
                      "email": "sam@agency.com",
                      "name": "Sam Lee",
                      "status": "active",
                      "joined_at": "2026-08-02T10:00:00Z",
                      "credits_used_this_cycle": 241.5,
                      "monthly_limit": 500,
                      "remaining": 258.5,
                      "limit_reached": false,
                      "last_job": null
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/billing/transactions": {
      "get": {
        "operationId": "credit_transactions",
        "tags": [
          "Account"
        ],
        "summary": "Credit history",
        "description": "Every credit movement on the account, newest first, as the Credit history page lists it. amount is signed: charges are negative, refunds, purchases and gifts positive. tool is the tool that charged; workspace_id the sub-account it was for, job_id / lead_id what it was for, member_user_id the team member who ran it (when known); credit_source is where it came from (monthly, purchased, rollover, fractional). Team members' spending is in the owner's history: a member's key sees only rows on their own account.\n\nRequired scope: `billing:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO time, at or after.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "ISO time, before.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "One type.",
            "schema": {
              "type": "string",
              "enum": [
                "used",
                "reserved",
                "refund",
                "purchased",
                "admin_gift",
                "bonus",
                "reset",
                "referral"
              ]
            }
          },
          {
            "name": "tool",
            "in": "query",
            "required": false,
            "description": "One tool, as named in `tool` (e.g. Ad Copier, AI Text Replies).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "One sub-account.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1-200, default 50.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "transactions": [
                      {
                        "id": "uuid",
                        "created_at": "2026-10-22T08:40:12Z",
                        "type": "used",
                        "tool": "Ad Generator",
                        "amount": -4,
                        "credit_source": "monthly",
                        "workspace_id": "uuid",
                        "job_id": "uuid",
                        "lead_id": null,
                        "member_user_id": "uuid",
                        "balance_after": {
                          "monthly": 212,
                          "purchased": 150
                        }
                      },
                      {
                        "id": "uuid",
                        "created_at": "2026-10-22T08:31:02Z",
                        "type": "refund",
                        "tool": "Video Ad Copier",
                        "amount": 12,
                        "credit_source": "refund",
                        "workspace_id": null,
                        "job_id": "uuid",
                        "lead_id": null,
                        "member_user_id": null,
                        "balance_after": {
                          "monthly": 216,
                          "purchased": 150
                        }
                      }
                    ],
                    "has_more": true,
                    "next_cursor": "MjAyNi0xMC0yMlQwODozMTowMlp8dXVpZA=="
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/billing/workspaces": {
      "get": {
        "operationId": "workspace_credit_usage",
        "tags": [
          "Account"
        ],
        "summary": "Credits used per sub-account",
        "description": "Agency → Usage: every sub-account you can see (not deleted or archived) with the credits it used in the window, most first, the number of events, unpaid_events (ran while the account was out of credits) and the split by tool (texts, lead alerts, email, AI text replies, chat widget, AI agents, phone calls, AI receptionist, AI calling, phone numbers, ad tools). by_tool at the top adds up every sub-account. Useful for billing clients for what they used. For one sub-account by day, use GET /v1/usage.\n\nRequired scope: `billing:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "1-365, default 30.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO start, instead of days (window at most 366 days).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO end, default now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "window": {
                      "from": "2026-09-22T00:00:00Z",
                      "to": "2026-10-22T00:00:00Z",
                      "days": 30
                    },
                    "total_credits": 1204.75,
                    "by_tool": {
                      "messaging": {
                        "events": 9120,
                        "credits": 684
                      },
                      "ai_text": {
                        "events": 1610,
                        "credits": 402.5
                      },
                      "ad_tools": {
                        "events": 31,
                        "credits": 118.25
                      }
                    },
                    "workspaces": [
                      {
                        "workspace_id": "uuid",
                        "name": "Acme Roofing",
                        "industry": "roofing",
                        "credits": 512.5,
                        "events": 4410,
                        "unpaid_events": 0,
                        "by_tool": {
                          "messaging": {
                            "events": 3900,
                            "credits": 292.5
                          },
                          "ai_text": {
                            "events": 480,
                            "credits": 120
                          },
                          "ad_tools": {
                            "events": 30,
                            "credits": 100
                          }
                        }
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/keys/self": {
      "get": {
        "operationId": "key_self",
        "tags": [
          "Account"
        ],
        "summary": "Inspect the calling API key",
        "description": "What this key may do and how much of it is used: `access_level` (read / write / admin / custom) and `scopes`, the `workspace` it is locked to (if any), `rate_limit_per_min`, `credit_budget` (limit, used, remaining and reset date; null when the key has no budget) and `usage_30d` (requests, errors, rate_limited). `type` is `account` (made in Settings → API) or `sub_account` (made in a sub-account's own Settings → API in the client CRM), and `endpoints` gives the API and MCP addresses for the host you called. A sub-account key can call this.\n\nRequired scope: `keys:read`.\n\nCallable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "key": {
                      "id": "uuid",
                      "key_prefix": "zu-ab12",
                      "name": "Zapier",
                      "is_active": true,
                      "access_level": "write",
                      "scopes": [
                        "write"
                      ],
                      "workspace": {
                        "id": "uuid",
                        "name": "Acme Roofing"
                      },
                      "rate_limit_per_min": 120,
                      "credit_budget": {
                        "limit_per_month": 500,
                        "used_this_month": 88,
                        "remaining": 412,
                        "resets_at": "2026-10-01T00:00:00Z"
                      },
                      "usage_30d": {
                        "requests": 1830,
                        "errors": 12,
                        "rate_limited": 0
                      },
                      "last_used_at": "2026-09-30T15:00:00Z",
                      "created_at": "2026-09-01T00:00:00Z",
                      "type": "account"
                    },
                    "endpoints": {
                      "api": "https://api.zuops.com/functions/v1/api-gateway/v1",
                      "mcp": "https://api.zuops.com/functions/v1/mcp-server"
                    }
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/audit": {
      "get": {
        "operationId": "audit_log",
        "tags": [
          "Account"
        ],
        "summary": "API request log",
        "description": "Every request made with your keys: action, method, path (no query string, never a body), status, credits used and time. Kept for 90 days.\n\nRequired scope: `audit:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 50, max 200.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "entries": [
                      {
                        "id": "uuid",
                        "action": "list-conversations",
                        "method": "GET",
                        "path": "/v1/conversations",
                        "cost_cents": 0,
                        "credits_used": 0,
                        "response_status": 200,
                        "workspace_id": "uuid",
                        "created_at": "2026-09-30T15:00:00Z"
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/phone-numbers/search": {
      "post": {
        "operationId": "phone_search",
        "tags": [
          "Account"
        ],
        "summary": "Search available phone numbers (does not purchase)",
        "description": "Local numbers that can text. Buying one is done in the app (Settings → Phone).\n\nRequired scope: `phone_numbers:read`.\n\nNot callable by a sub-account-pinned API key.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique string per logical operation. Replays within 24h return the original response instead of creating a duplicate or double-charging.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "area_code": {
                    "type": "string",
                    "description": "e.g. 415."
                  },
                  "country": {
                    "type": "string",
                    "description": "ISO country code. Default US."
                  },
                  "contains": {
                    "type": "string",
                    "description": "Digits the number must contain."
                  },
                  "limit": {
                    "type": "number",
                    "description": "Default 10, max 30."
                  }
                },
                "additionalProperties": true
              },
              "example": {
                "area_code": "415",
                "country": "US",
                "limit": 10
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window for this key."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds when the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "country": "US",
                    "area_code": "415",
                    "numbers": [
                      {
                        "phone_number": "+14155550199",
                        "friendly_name": "(415) 555-0199",
                        "locality": "San Francisco",
                        "region": "CA",
                        "iso_country": "US",
                        "capabilities": {
                          "voice": true,
                          "SMS": true,
                          "MMS": true
                        }
                      }
                    ]
                  },
                  "error": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Zuops API key, e.g. `zu-live-...`. Create one in Settings → API."
      }
    },
    "schemas": {
      "SuccessEnvelope": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "success"
        ]
      },
      "ConfirmationRequired": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          },
          "confirmation_required": {
            "type": "boolean",
            "const": true
          },
          "confirmation_token": {
            "type": "string",
            "description": "Single-use, expires after 10 minutes."
          },
          "estimated_credits": {
            "type": "number"
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "data": {
            "type": "null"
          },
          "error": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "success",
          "error"
        ]
      }
    },
    "responses": {
      "RateLimited": {
        "description": "Rate limit exceeded",
        "headers": {
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests allowed per window for this key."
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests left in the current window."
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "integer"
            },
            "description": "Unix seconds when the window resets."
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait before retrying."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "ConfirmationRequired": {
        "description": "Confirmation required, or not enough credits. Re-send the identical request with the returned confirmation_token to proceed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ConfirmationRequired"
            }
          }
        }
      },
      "Error": {
        "description": "Error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    }
  }
}