{
  "openapi": "3.1.0",
  "info": {
    "title": "Run a Call External API",
    "version": "1.0.0",
    "description": "> A bearer-authenticated REST API for pushing data into and pulling data out of **[Run a Call](https://runacall.com)** — the field service platform built for small-to-mid HVAC companies. Migrate from your old CRM, sync with accounting tools, fire Zapier / Make / n8n flows on real-time events, or wire your own scripts. Same surface internally — no second-class endpoints.\n\n## ⚡ Quick start\n\nThree lines to your first request. Replace the token with one from **[Settings → Integrations → API](https://app.runacall.com/settings/integrations/api)** in your dashboard.\n\n```bash\ncurl https://api.runacall.com/v1/customers \\\n  -H \"Authorization: Bearer rkc_live_...\"\n```\n\nThat's it. Bearer header, JSON in, JSON out.\n\n---\n\n## 🔑 Authentication\n\nEvery request needs an `Authorization: Bearer rkc_live_...` header.\n\n| Scope | Grants |\n|---|---|\n| `read` | All `GET` endpoints |\n| `write` | `POST` and `PATCH` on customers / properties / equipment / jobs / memberships |\n\nTokens are **SHA-256 hashed at rest**. The raw value is shown to the org owner exactly once at creation — we cannot retrieve it later. Rotate at any time from the same settings page; the old token works for a 24-hour grace window so deploys don't break.\n\n---\n\n## 🛡️ Tenant isolation\n\nEvery request is scoped to the organization that owns the bearer token.\n\n- Cross-org reads return **404 (not 403)** to mask existence\n- No `admin` scope, no DELETE endpoints, no destructive verbs\n- Soft-delete (`is_deleted = true`) is a UI-only operation\n- RLS + service-role org-filter run independently — defense in depth\n\n---\n\n## 🚦 Rate limits\n\n| Layer | Limit | Window |\n|---|---|---|\n| Per key — sustained | 60 req | 1 min rolling |\n| Per key — burst | 120 req | 10 s rolling |\n| Per org — all keys combined | 300 req | 1 min rolling |\n| Concurrent in-flight per key | 5 | — |\n| Per-IP — unauthed paths | 10 req | 1 min rolling |\n\nExceeding any layer returns **`429 Too Many Requests`** with a `Retry-After` header. Founders can raise a known integrator's limit via per-key override — email **[admin@runacall.com](mailto:admin@runacall.com)**.\n\n---\n\n## 🔁 Idempotency — upsert by `external_id`\n\nEvery writable resource (customers, properties, equipment, jobs, memberships) accepts `external_id` + `external_source`. POSTing the same pair twice **upserts the existing row** — returns 200 with the same UUID instead of 201 with a duplicate.\n\n```bash\n# First call — creates\ncurl -X POST https://api.runacall.com/v1/customers \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"external_id\":\"crm-acme-001\",\"external_source\":\"servicetitan\",\"first_name\":\"Jenny\",\"last_name\":\"Rosen\"}'\n# → 201 Created, id: 497f6eca-6276-4993-bfeb-53cbbbba6f08\n\n# Same call again — upserts, same id\ncurl -X POST https://api.runacall.com/v1/customers \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"external_id\":\"crm-acme-001\",\"external_source\":\"servicetitan\",\"first_name\":\"Jenny\",\"last_name\":\"Rosen\"}'\n# → 200 OK, id: 497f6eca-6276-4993-bfeb-53cbbbba6f08\n```\n\nSafe to retry. Safe to script bulk imports against. Network blip? Re-fire — you'll never end up with duplicates.\n\n---\n\n## 📄 Pagination\n\nCursor-based, never offset. Pass back the `next_cursor` from a response as `?starting_after=<cursor>` to fetch the next page.\n\n```json\n{\n  \"data\": [ /* up to 100 items */ ],\n  \"has_more\": true,\n  \"next_cursor\": \"MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ\"\n}\n```\n\nDefault page size 50, hard cap 100. There's no way to ask for \"all rows\" in a single call — keep your loop honest.\n\n---\n\n## ❌ Errors\n\nEvery error response is the same envelope:\n\n```json\n{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"external_id is required when upserting\",\n    \"doc_url\": \"https://docs.runacall.com/#errors-invalid_request\",\n    \"request_id\": \"req_a93a7817f5a8485d80204becea\"\n  }\n}\n```\n\nQuote **`request_id`** when reaching out for support — it correlates to our audit log.\n\n<a id=\"errors-invalid_request\"></a>\n### `invalid_request` — 400 / 422\n\nBody or query parameter validation failed. The `message` field names the specific issue. Common causes: missing required field, unknown field (`.strict()` rejects extras), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.\n\n<a id=\"errors-unauthorized\"></a>\n### `unauthorized` — 401\n\nMissing or invalid bearer token. Check the `Authorization` header format: `Bearer rkc_live_...`. If you rotated the token recently, the old value works for 24 hours — verify you're using the right one.\n\n<a id=\"errors-forbidden\"></a>\n### `forbidden` — 403\n\nYour token lacks the required scope. `POST` and `PATCH` endpoints require the `write` scope; everything else needs `read`. Issue a new key with the right scopes from the Settings page.\n\n<a id=\"errors-not_found\"></a>\n### `not_found` — 404\n\nThe resource doesn't exist, OR it belongs to another organization. We mask cross-org existence to prevent enumeration attacks — there's no way to tell from outside which case it is.\n\n<a id=\"errors-conflict\"></a>\n### `conflict` — 409\n\nConcurrent upsert race lost. Two POSTs hit the same `(external_id, external_source)` at once; only one won the unique-index race. **Retry the same payload** — your second attempt will hit the existing row via the upsert path and return 200.\n\n<a id=\"errors-rate_limited\"></a>\n### `rate_limited` — 429\n\nOne of the rate-limit layers above kicked in. The response includes a `Retry-After` header (seconds). Back off and retry.\n\n<a id=\"errors-internal_error\"></a>\n### `internal_error` — 500\n\nA bug on our end. Quote the `request_id` when emailing **admin@runacall.com** and we'll investigate. We log every 5xx response with full context.\n\n---\n\n## 🪝 Webhooks\n\nPush real-time events to your own endpoints instead of polling. Subscribe a URL via **[Settings → Integrations → Webhooks](https://app.runacall.com/settings/integrations/webhooks)**.\n\n**15 canonical events** across customers, jobs, estimates, invoices, payments, voice calls, and memberships. Every delivery is **HMAC-SHA256 signed** over `\\${ts}.\\${raw_body}` — header: `Run-A-Call-Signature: t=<unix>,v1=<hex>`. Verify the timestamp is within 5 minutes (replay protection), then verify the signature.\n\nFailed deliveries retry 3x with exponential backoff. After **10 consecutive failures** the subscription auto-pauses and we email the org owner — resume with one click.\n\nSee the **Webhooks** section in the sidebar for the full event catalog + payload shapes.\n\n---\n\n## 📦 What you can build\n\nA few patterns the API was designed for:\n\n- **One-shot migration from your old CRM** — script a pull from ServiceTitan / Housecall Pro / a spreadsheet → POST against `/customers` + `/properties` + `/equipment` with your own `external_id`. Re-run safely; idempotent.\n- **Accounting sync** — subscribe to `invoice.paid` + `payment.received` webhooks → push into QuickBooks / Xero / your ERP.\n- **Zapier / Make / n8n automation** — webhook triggers fan out to Slack, Notion, Google Sheets, anywhere.\n- **Custom analytics** — `GET /voice-calls/{id}` returns transcripts. No other HVAC platform exposes this.\n\nNeed an integration we haven't documented yet? Email **[admin@runacall.com](mailto:admin@runacall.com)** with what you're trying to do and we'll wire it.",
    "contact": {
      "name": "Run a Call Support",
      "email": "admin@runacall.com"
    }
  },
  "servers": [
    {
      "url": "https://api.runacall.com/v1",
      "description": "Production (bearer-token surface)"
    }
  ],
  "tags": [
    {
      "name": "Customers",
      "description": "Customer records — full CRUD in Phase 2."
    },
    {
      "name": "Properties",
      "description": "Service locations belonging to a customer."
    },
    {
      "name": "Equipment",
      "description": "HVAC equipment installed at a property."
    },
    {
      "name": "Jobs",
      "description": "Service / install / maintenance / estimate jobs."
    },
    {
      "name": "Memberships",
      "description": "Customer service-agreement subscriptions."
    },
    {
      "name": "Estimates",
      "description": "Good/Better/Best estimates (read-only — push via Jobs)."
    },
    {
      "name": "Invoices",
      "description": "Derived from completed jobs (read-only)."
    },
    {
      "name": "Payments",
      "description": "Recorded against invoices (read-only)."
    },
    {
      "name": "Voice Calls",
      "description": "AI receptionist calls — unique-to-us asset with transcripts."
    },
    {
      "name": "Pricebook",
      "description": "Services, parts, labor, and bundles."
    },
    {
      "name": "Communications",
      "description": "Outbound + inbound SMS/email log per customer."
    },
    {
      "name": "Webhooks",
      "description": "Outbound HTTP webhook deliveries from Run a Call to subscriber URLs.\n\n**Authentication.** Every POST carries `Run-A-Call-Signature: t=<unix>,v1=<hex>`.\nThe signed string is `${ts}.${raw_body}`. Verify with HMAC-SHA256 against the\nsubscription secret. Reject if `Math.abs(now - t) > 5 minutes` to defeat\nreplay attacks.\n\n**Deduplication.** Each delivery's `event.id` (header\n`Run-A-Call-Event-Id`, also in the JSON body) is stable across retries\nof the same logical event. Store the last N event ids you've processed\nand dedupe before applying business logic.\n\n**Retry policy.** Subscriber returns 2xx within 10 seconds → success.\nAnything else (non-2xx, timeout, network error) is retried up to 3 total\nattempts with default Inngest backoff. After 10 consecutive terminal\nfailures, the subscription is auto-paused and the org owner is emailed.\nResume from /settings/integrations/webhooks/{id}.\n\n**Concurrency.** Run a Call caps fan-out at 100 matching subscriptions\nper single internal event and concurrent deliveries at 50 in-flight per\nfunction — beyond that, the system bails rather than fan out unbounded."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "rkc_live_*",
        "description": "Bearer token issued from /settings/integrations/api. Format: `rkc_live_<32 base62 chars>`. Hashed at rest; the raw token is shown to the org owner exactly once at creation."
      }
    },
    "schemas": {
      "CustomerCreatedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "customer.created"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Customer"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "customer"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "Customer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "crm-acme-001"
            ]
          },
          "external_source": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "servicetitan"
            ]
          },
          "first_name": {
            "type": "string",
            "examples": [
              "Jenny"
            ]
          },
          "last_name": {
            "type": "string",
            "examples": [
              "Rosen"
            ]
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Acme HVAC"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "jenny.rosen@example.com"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "+15555550100"
            ]
          },
          "phone_alt": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "+15555550101"
            ]
          },
          "billing_address_line_1": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "510 Townsend St"
            ]
          },
          "billing_address_line_2": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Suite 200"
            ]
          },
          "billing_city": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "San Francisco"
            ]
          },
          "billing_state": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "CA"
            ]
          },
          "billing_zip": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "94103"
            ]
          },
          "source": {
            "type": "string",
            "examples": [
              "google"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "vip",
                "commercial"
              ]
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "external_id",
          "external_source",
          "first_name",
          "last_name",
          "company_name",
          "email",
          "phone",
          "phone_alt",
          "billing_address_line_1",
          "billing_address_line_2",
          "billing_city",
          "billing_state",
          "billing_zip",
          "source",
          "tags",
          "created_at",
          "updated_at"
        ]
      },
      "CustomerUpdatedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "customer.updated"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Customer"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "customer"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "JobScheduledEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "job.scheduled"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Job"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "job"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "crm-acme-job-2026-1042"
            ]
          },
          "external_source": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "servicetitan"
            ]
          },
          "customer_id": {
            "type": "string",
            "format": "uuid"
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "equipment_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "assigned_tech_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "examples": [
              "scheduled"
            ]
          },
          "priority": {
            "type": "string",
            "examples": [
              "scheduled"
            ]
          },
          "type": {
            "type": "string",
            "examples": [
              "service"
            ]
          },
          "source": {
            "type": "string",
            "examples": [
              "call"
            ]
          },
          "scheduled_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-05-20"
            ]
          },
          "scheduled_start": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "scheduled_end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "actual_arrival": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "actual_start": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "actual_end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "problem_description": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "AC not cooling — possibly low refrigerant. Customer says it ran fine yesterday."
            ]
          },
          "diagnosis": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "R-410A leak at evaporator coil. ~0.5 lb low."
            ]
          },
          "work_performed": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Replaced evaporator coil. Recharged refrigerant to spec. Pressure-tested."
            ]
          },
          "parent_job_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "estimate_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "external_id",
          "external_source",
          "customer_id",
          "property_id",
          "equipment_id",
          "assigned_tech_id",
          "status",
          "priority",
          "type",
          "source",
          "scheduled_date",
          "scheduled_start",
          "scheduled_end",
          "actual_arrival",
          "actual_start",
          "actual_end",
          "problem_description",
          "diagnosis",
          "work_performed",
          "parent_job_id",
          "estimate_id",
          "created_at",
          "updated_at"
        ]
      },
      "JobStatus_changedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "job.status_changed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Job"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "job"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "JobCompletedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "job.completed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Job"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "job"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "EstimateSentEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "estimate.sent"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Estimate"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "estimate"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "EstimateOption": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tier": {
            "type": "string",
            "examples": [
              "better"
            ]
          },
          "title": {
            "type": "string",
            "examples": [
              "Better — 16 SEER AC + smart thermostat"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Carrier Performance series, smart thermostat install, 10-year parts warranty."
            ]
          },
          "total_price": {
            "type": "number",
            "examples": [
              7850
            ]
          },
          "is_selected": {
            "type": "boolean"
          },
          "sort_order": {
            "type": "number"
          }
        },
        "required": [
          "id",
          "tier",
          "title",
          "description",
          "total_price",
          "is_selected",
          "sort_order"
        ]
      },
      "Estimate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "crm-acme-est-001"
            ]
          },
          "external_source": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "servicetitan"
            ]
          },
          "customer_id": {
            "type": "string",
            "format": "uuid"
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "equipment_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "examples": [
              "sent"
            ]
          },
          "valid_until": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-06-13"
            ]
          },
          "customer_approved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "AC replacement + new smart thermostat install. Includes 10-year parts warranty."
            ]
          },
          "converted_job_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency code. Hardcoded `USD` in V1.",
            "examples": [
              "USD"
            ]
          },
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Total of the selected option (`is_selected = true`). `null` when no option is selected yet.",
            "examples": [
              7850
            ]
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EstimateOption"
            },
            "description": "Good / Better / Best tiers sorted by `sort_order`. Empty array when the estimate has no options yet."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "external_id",
          "external_source",
          "customer_id",
          "property_id",
          "equipment_id",
          "status",
          "valid_until",
          "customer_approved_at",
          "notes",
          "converted_job_id",
          "currency",
          "amount",
          "options",
          "created_at",
          "updated_at"
        ]
      },
      "EstimateApprovedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "estimate.approved"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Estimate"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "estimate"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "InvoiceIssuedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "invoice.issued"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Invoice"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "invoice"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "crm-acme-inv-001"
            ]
          },
          "external_source": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "servicetitan"
            ]
          },
          "customer_id": {
            "type": "string",
            "format": "uuid"
          },
          "job_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "invoice_number": {
            "type": "string",
            "examples": [
              "INV-2026-1042"
            ]
          },
          "status": {
            "type": "string",
            "examples": [
              "sent"
            ]
          },
          "subtotal": {
            "type": "number",
            "examples": [
              450
            ]
          },
          "tax_amount": {
            "type": "number",
            "examples": [
              36
            ]
          },
          "tax_rate": {
            "type": "number",
            "examples": [
              0.08
            ]
          },
          "total": {
            "type": "number",
            "examples": [
              486
            ]
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency code. Hardcoded `USD` in V1.",
            "examples": [
              "USD"
            ]
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-06-12"
            ]
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Service call + evaporator coil replacement. Net 30."
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "external_id",
          "external_source",
          "customer_id",
          "job_id",
          "invoice_number",
          "status",
          "subtotal",
          "tax_amount",
          "tax_rate",
          "total",
          "currency",
          "due_date",
          "sent_at",
          "paid_at",
          "notes",
          "created_at",
          "updated_at"
        ]
      },
      "InvoicePaidEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "invoice.paid"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Invoice"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "invoice"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "PaymentReceivedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "payment.received"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Payment"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "payment"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "Payment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoice_id": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "type": "number",
            "examples": [
              486
            ]
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency code. Hardcoded `USD` in V1.",
            "examples": [
              "USD"
            ]
          },
          "method": {
            "type": "string",
            "examples": [
              "credit_card"
            ]
          },
          "status": {
            "type": "string",
            "examples": [
              "succeeded"
            ]
          },
          "stripe_payment_id": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "pi_3OqK4nJ8ZcU2bC0a1Y2eX3f"
            ]
          },
          "collected_at": {
            "type": "string",
            "format": "date-time"
          },
          "collected_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "invoice_id",
          "amount",
          "currency",
          "method",
          "status",
          "stripe_payment_id",
          "collected_at",
          "collected_by",
          "created_at",
          "updated_at"
        ]
      },
      "VoiceCallCompletedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "voice.call.completed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/VoiceCallDetail"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "voice_call"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "VoiceCallDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/VoiceCallListRow"
          },
          {
            "type": "object",
            "properties": {
              "transcript": {
                "type": [
                  "string",
                  "null"
                ],
                "examples": [
                  "Agent: Hi, thanks for calling Acme HVAC. How can I help today?\nCaller: My AC isn't cooling. Can someone come out?\nAgent: I can schedule a tech for tomorrow morning. Does 9am work?\nCaller: Yes, that's perfect.\nAgent: Booked. You'll get a confirmation text shortly."
                ]
              },
              "recording_url": {
                "type": [
                  "string",
                  "null"
                ],
                "examples": [
                  "https://storage.runacall.com/voice-recordings/{org}/{call_id}.mp3"
                ]
              }
            },
            "required": [
              "transcript",
              "recording_url"
            ]
          }
        ]
      },
      "VoiceCallListRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "call_direction": {
            "type": "string",
            "examples": [
              "inbound"
            ]
          },
          "call_status": {
            "type": "string",
            "examples": [
              "completed"
            ]
          },
          "from_number": {
            "type": "string",
            "examples": [
              "+15555550100"
            ]
          },
          "to_number": {
            "type": "string",
            "examples": [
              "+15555550199"
            ]
          },
          "duration_seconds": {
            "type": [
              "number",
              "null"
            ],
            "examples": [
              247
            ]
          },
          "is_spam": {
            "type": "boolean"
          },
          "is_billable": {
            "type": "boolean"
          },
          "summary": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Customer reporting AC not cooling. Requested same-day service. Confirmed appointment for tomorrow 9am."
            ]
          },
          "user_sentiment": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "positive"
            ]
          },
          "call_intent": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "service_request"
            ]
          },
          "urgency_level": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "routine"
            ]
          },
          "triage_status": {
            "type": "string",
            "examples": [
              "new"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "customer_id",
          "call_direction",
          "call_status",
          "from_number",
          "to_number",
          "duration_seconds",
          "is_spam",
          "is_billable",
          "summary",
          "user_sentiment",
          "call_intent",
          "urgency_level",
          "triage_status",
          "created_at",
          "updated_at"
        ]
      },
      "VoiceFollowupCreatedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "voice.followup.created"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/VoiceFollowup"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "voice_followup"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "VoiceFollowup": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "voice_call_id": {
            "type": "string",
            "format": "uuid"
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "category": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "urgency": {
            "type": "string"
          },
          "callback_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "related_estimate_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "related_invoice_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "related_job_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "voice_call_id",
          "customer_id",
          "category",
          "reason",
          "urgency",
          "callback_number",
          "related_estimate_id",
          "related_invoice_id",
          "related_job_id",
          "status",
          "created_at",
          "updated_at"
        ]
      },
      "MembershipCreatedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "membership.created"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Membership"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "membership"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "Membership": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "crm-acme-mship-001"
            ]
          },
          "external_source": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "servicetitan"
            ]
          },
          "customer_id": {
            "type": "string",
            "format": "uuid"
          },
          "service_agreement_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "examples": [
              "active"
            ]
          },
          "billing_cycle": {
            "type": "string",
            "examples": [
              "monthly"
            ]
          },
          "start_date": {
            "type": "string",
            "examples": [
              "2026-01-15"
            ]
          },
          "end_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2027-01-15"
            ]
          },
          "next_billing_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-06-15"
            ]
          },
          "next_maintenance_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-09-15"
            ]
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancellation_reason": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Customer moved out of service area."
            ]
          },
          "suspended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "external_id",
          "external_source",
          "customer_id",
          "service_agreement_id",
          "status",
          "billing_cycle",
          "start_date",
          "end_date",
          "next_billing_date",
          "next_maintenance_date",
          "cancelled_at",
          "cancellation_reason",
          "suspended_at",
          "created_at",
          "updated_at"
        ]
      },
      "MembershipRenewedEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "membership.renewed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Membership"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "membership"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "MembershipCancelledEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable event id (e.g. evt_a93a7817f5a8485d80204becea). Dedupe across retries by this value.",
            "examples": [
              "evt_a93a7817f5a8485d80204becea"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "membership.cancelled"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Membership"
              },
              {
                "type": "object",
                "properties": {
                  "object": {
                    "type": "string",
                    "enum": [
                      "membership"
                    ]
                  }
                },
                "required": [
                  "object"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "organization_id",
          "data"
        ]
      },
      "CustomerList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Customer"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "unauthorized",
                  "forbidden",
                  "not_found",
                  "method_not_allowed",
                  "conflict",
                  "rate_limited",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string",
                "examples": [
                  "Missing or malformed Authorization header. Expected: Authorization: Bearer rkc_live_..."
                ]
              },
              "doc_url": {
                "type": "string",
                "format": "uri",
                "examples": [
                  "https://docs.runacall.com/api#errors-unauthorized"
                ]
              },
              "request_id": {
                "type": "string",
                "examples": [
                  "req_a93a7817f5a8485d80204becea"
                ]
              }
            },
            "required": [
              "code",
              "message",
              "doc_url",
              "request_id"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "CustomerDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Customer"
          }
        },
        "required": [
          "data"
        ]
      },
      "CustomerLookup": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Customer"
          },
          "has_duplicates": {
            "type": "boolean"
          }
        },
        "required": [
          "data",
          "has_duplicates"
        ]
      },
      "CustomerWriteRequest": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string",
            "description": "Integrator's own identifier. When paired with `external_source`, POSTing the same value twice upserts the existing row (200) instead of creating a duplicate (201).",
            "examples": [
              "crm-acme-001"
            ]
          },
          "external_source": {
            "type": "string",
            "description": "Namespace for `external_id` (e.g. 'servicetitan'). Required when `external_id` is provided.",
            "examples": [
              "servicetitan"
            ]
          },
          "first_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "examples": [
              "Jenny"
            ]
          },
          "last_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "examples": [
              "Rosen"
            ]
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "examples": [
              "Acme HVAC"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 254,
            "format": "email",
            "examples": [
              "jenny.rosen@example.com"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 30,
            "examples": [
              "+15555550100"
            ]
          },
          "phone_alt": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 30,
            "examples": [
              "+15555550101"
            ]
          },
          "billing_address_line_1": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "examples": [
              "510 Townsend St"
            ]
          },
          "billing_address_line_2": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "examples": [
              "Suite 200"
            ]
          },
          "billing_city": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "San Francisco"
            ]
          },
          "billing_state": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50,
            "examples": [
              "CA"
            ]
          },
          "billing_zip": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "examples": [
              "94103"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "google",
              "referral",
              "angi",
              "website",
              "other"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 50
            },
            "maxItems": 20,
            "examples": [
              [
                "vip",
                "commercial"
              ]
            ]
          }
        },
        "required": [
          "first_name",
          "last_name"
        ]
      },
      "CustomerPatchRequest": {
        "type": "object",
        "properties": {
          "first_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "examples": [
              "Jenny"
            ]
          },
          "last_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "examples": [
              "Rosen"
            ]
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "examples": [
              "Acme HVAC"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 254,
            "format": "email",
            "examples": [
              "jenny.rosen@example.com"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 30,
            "examples": [
              "+15555550100"
            ]
          },
          "phone_alt": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 30,
            "examples": [
              "+15555550101"
            ]
          },
          "billing_address_line_1": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "examples": [
              "510 Townsend St"
            ]
          },
          "billing_address_line_2": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "examples": [
              "Suite 200"
            ]
          },
          "billing_city": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "San Francisco"
            ]
          },
          "billing_state": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50,
            "examples": [
              "CA"
            ]
          },
          "billing_zip": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "examples": [
              "94103"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "google",
              "referral",
              "angi",
              "website",
              "other"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 50
            },
            "maxItems": 20,
            "examples": [
              [
                "vip",
                "commercial"
              ]
            ]
          }
        },
        "description": "Partial update. `external_id` / `external_source` cannot be changed via PATCH (re-POST with a new pair instead). Unknown keys return 422."
      },
      "PropertyList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Property"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "Property": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "crm-acme-001-prop1"
            ]
          },
          "external_source": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "servicetitan"
            ]
          },
          "customer_id": {
            "type": "string",
            "format": "uuid"
          },
          "address_line_1": {
            "type": "string",
            "examples": [
              "510 Townsend St"
            ]
          },
          "address_line_2": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Suite 200"
            ]
          },
          "city": {
            "type": "string",
            "examples": [
              "San Francisco"
            ]
          },
          "state": {
            "type": "string",
            "examples": [
              "CA"
            ]
          },
          "zip": {
            "type": "string",
            "examples": [
              "94103"
            ]
          },
          "type": {
            "type": "string",
            "examples": [
              "residential"
            ]
          },
          "access_notes": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Gate code: 4321. Dog in backyard."
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ],
            "examples": [
              37.7793
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ],
            "examples": [
              -122.4192
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "external_id",
          "external_source",
          "customer_id",
          "address_line_1",
          "address_line_2",
          "city",
          "state",
          "zip",
          "type",
          "access_notes",
          "lat",
          "lng",
          "created_at",
          "updated_at"
        ]
      },
      "PropertyDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Property"
          }
        },
        "required": [
          "data"
        ]
      },
      "PropertyWriteRequest": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string",
            "examples": [
              "crm-acme-001-prop1"
            ]
          },
          "external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "customer_id": {
            "type": "string",
            "format": "uuid",
            "description": "Customer UUID. Provide this OR `customer_external_id` + `customer_external_source`."
          },
          "customer_external_id": {
            "type": "string",
            "examples": [
              "crm-acme-001"
            ]
          },
          "customer_external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "address_line_1": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "examples": [
              "510 Townsend St"
            ]
          },
          "address_line_2": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "examples": [
              "Suite 200"
            ]
          },
          "city": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "examples": [
              "San Francisco"
            ]
          },
          "state": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "examples": [
              "CA"
            ]
          },
          "zip": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20,
            "examples": [
              "94103"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "residential",
              "commercial"
            ]
          },
          "access_notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "examples": [
              "Gate code: 4321. Dog in backyard."
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -90,
            "maximum": 90,
            "examples": [
              37.7793
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -180,
            "maximum": 180,
            "examples": [
              -122.4192
            ]
          }
        },
        "required": [
          "address_line_1",
          "city",
          "state",
          "zip"
        ]
      },
      "PropertyPatchRequest": {
        "type": "object",
        "properties": {
          "address_line_1": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "examples": [
              "510 Townsend St"
            ]
          },
          "address_line_2": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "examples": [
              "Suite 200"
            ]
          },
          "city": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "examples": [
              "San Francisco"
            ]
          },
          "state": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "examples": [
              "CA"
            ]
          },
          "zip": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20,
            "examples": [
              "94103"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "residential",
              "commercial"
            ]
          },
          "access_notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "examples": [
              "Gate code: 4321. Dog in backyard."
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -90,
            "maximum": 90,
            "examples": [
              37.7793
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -180,
            "maximum": 180,
            "examples": [
              -122.4192
            ]
          }
        }
      },
      "EquipmentList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Equipment"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "Equipment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "crm-acme-001-eq1"
            ]
          },
          "external_source": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "servicetitan"
            ]
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "examples": [
              "ac"
            ]
          },
          "manufacturer": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Carrier"
            ]
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "24ABC636A003"
            ]
          },
          "serial_number": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "3618E12345"
            ]
          },
          "install_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2018-06-15"
            ]
          },
          "warranty_expiry": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2028-06-15"
            ]
          },
          "condition": {
            "type": "string",
            "examples": [
              "good"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "3-ton split system, R-410A refrigerant."
            ]
          },
          "data_plate_photo_url": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "https://storage.runacall.com/eq-photos/{org}/3618E12345.jpg"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "external_id",
          "external_source",
          "property_id",
          "type",
          "manufacturer",
          "model",
          "serial_number",
          "install_date",
          "warranty_expiry",
          "condition",
          "notes",
          "data_plate_photo_url",
          "created_at",
          "updated_at"
        ]
      },
      "EquipmentDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Equipment"
          }
        },
        "required": [
          "data"
        ]
      },
      "EquipmentLookup": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Equipment"
          },
          "has_duplicates": {
            "type": "boolean"
          }
        },
        "required": [
          "data",
          "has_duplicates"
        ]
      },
      "EquipmentWriteRequest": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string",
            "examples": [
              "crm-acme-001-eq1"
            ]
          },
          "external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "property_id": {
            "type": "string",
            "format": "uuid",
            "description": "Property UUID. Provide this OR `property_external_id` + `property_external_source`."
          },
          "property_external_id": {
            "type": "string",
            "examples": [
              "crm-acme-001-prop1"
            ]
          },
          "property_external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "furnace",
              "ac",
              "heat_pump",
              "boiler",
              "mini_split",
              "water_heater",
              "thermostat",
              "iaq",
              "other"
            ]
          },
          "manufacturer": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "Carrier"
            ]
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "24ABC636A003"
            ]
          },
          "serial_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "3618E12345"
            ]
          },
          "install_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2018-06-15"
            ]
          },
          "warranty_expiry": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2028-06-15"
            ]
          },
          "condition": {
            "type": "string",
            "enum": [
              "good",
              "fair",
              "poor",
              "critical"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 5000,
            "examples": [
              "3-ton split system, R-410A refrigerant."
            ]
          },
          "data_plate_photo_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2048,
            "examples": [
              "https://storage.runacall.com/eq-photos/{org}/3618E12345.jpg"
            ]
          }
        },
        "required": [
          "type"
        ]
      },
      "EquipmentPatchRequest": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "furnace",
              "ac",
              "heat_pump",
              "boiler",
              "mini_split",
              "water_heater",
              "thermostat",
              "iaq",
              "other"
            ]
          },
          "manufacturer": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "Carrier"
            ]
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "24ABC636A003"
            ]
          },
          "serial_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "3618E12345"
            ]
          },
          "install_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2018-06-15"
            ]
          },
          "warranty_expiry": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2028-06-15"
            ]
          },
          "condition": {
            "type": "string",
            "enum": [
              "good",
              "fair",
              "poor",
              "critical"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 5000,
            "examples": [
              "3-ton split system, R-410A refrigerant."
            ]
          },
          "data_plate_photo_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2048,
            "examples": [
              "https://storage.runacall.com/eq-photos/{org}/3618E12345.jpg"
            ]
          }
        }
      },
      "JobList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Job"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "JobDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Job"
          }
        },
        "required": [
          "data"
        ]
      },
      "JobWriteRequest": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string",
            "examples": [
              "crm-acme-job-2026-1042"
            ]
          },
          "external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "customer_id": {
            "type": "string",
            "format": "uuid"
          },
          "customer_external_id": {
            "type": "string",
            "examples": [
              "crm-acme-001"
            ]
          },
          "customer_external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "property_external_id": {
            "type": "string",
            "examples": [
              "crm-acme-001-prop1"
            ]
          },
          "property_external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "equipment_id": {
            "type": "string",
            "format": "uuid"
          },
          "equipment_external_id": {
            "type": "string",
            "examples": [
              "crm-acme-001-eq1"
            ]
          },
          "equipment_external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "scheduled",
              "en_route",
              "in_progress",
              "completed",
              "invoiced",
              "closed",
              "follow_up",
              "cancelled"
            ]
          },
          "priority": {
            "type": "string",
            "enum": [
              "emergency",
              "urgent",
              "scheduled"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "service",
              "maintenance",
              "install",
              "estimate",
              "callback"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "call",
              "website",
              "membership_auto",
              "follow_up",
              "estimate_conversion",
              "recurring"
            ]
          },
          "scheduled_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-05-20"
            ]
          },
          "scheduled_start": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-05-20T16:00:00Z"
            ]
          },
          "scheduled_end": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-05-20T18:00:00Z"
            ]
          },
          "problem_description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 5000,
            "examples": [
              "AC not cooling — possibly low refrigerant."
            ]
          }
        },
        "description": "Required FKs (customer, property) can be supplied as UUID OR as `{name}_external_id` + `{name}_external_source`. Equipment is optional, same dual form. PATCH on jobs is status-only."
      },
      "JobPatchRequest": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "created",
              "scheduled",
              "en_route",
              "in_progress",
              "completed",
              "invoiced",
              "closed",
              "follow_up",
              "cancelled"
            ]
          }
        },
        "required": [
          "status"
        ],
        "description": "Status-only per spec §4. Invalid transitions (per VALID_STATUS_TRANSITIONS — same rules the UI enforces) return 422. No-op (same status) returns 200 with the unchanged row."
      },
      "MembershipList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Membership"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "MembershipDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Membership"
          }
        },
        "required": [
          "data"
        ]
      },
      "MembershipWriteRequest": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string",
            "examples": [
              "crm-acme-mship-001"
            ]
          },
          "external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "customer_id": {
            "type": "string",
            "format": "uuid"
          },
          "customer_external_id": {
            "type": "string",
            "examples": [
              "crm-acme-001"
            ]
          },
          "customer_external_source": {
            "type": "string",
            "examples": [
              "servicetitan"
            ]
          },
          "service_agreement_id": {
            "type": "string",
            "format": "uuid",
            "description": "Service agreement UUID. The service_agreements table does NOT carry external_id columns in V1 — UUID is the only supported reference."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "cancelled",
              "expired"
            ]
          },
          "billing_cycle": {
            "type": "string",
            "enum": [
              "monthly",
              "annual"
            ]
          },
          "start_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-01-15"
            ]
          },
          "end_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2027-01-15"
            ]
          },
          "next_billing_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-06-15"
            ]
          },
          "next_maintenance_date": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2026-09-15"
            ]
          }
        },
        "required": [
          "service_agreement_id"
        ]
      },
      "MembershipPatchRequest": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "cancelled",
              "expired"
            ]
          }
        },
        "required": [
          "status"
        ],
        "description": "Status-only per spec §4."
      },
      "EstimateList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Estimate"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "EstimateDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Estimate"
          }
        },
        "required": [
          "data"
        ]
      },
      "InvoiceList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Invoice"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "InvoiceDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Invoice"
          }
        },
        "required": [
          "data"
        ]
      },
      "PaymentList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Payment"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "PaymentDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Payment"
          }
        },
        "required": [
          "data"
        ]
      },
      "VoiceCallList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VoiceCallListRow"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "VoiceCallDetailEnvelope": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/VoiceCallDetail"
          }
        },
        "required": [
          "data"
        ]
      },
      "PricebookList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PricebookItem"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "PricebookItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "category_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "examples": [
              "Diagnostic Service Call"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "1-hour HVAC system diagnostic. Includes written estimate."
            ]
          },
          "type": {
            "type": "string",
            "examples": [
              "service"
            ]
          },
          "unit_price": {
            "type": "number",
            "examples": [
              89
            ]
          },
          "unit_cost": {
            "type": "number",
            "examples": [
              25
            ]
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency code. Hardcoded `USD` in V1.",
            "examples": [
              "USD"
            ]
          },
          "is_taxable": {
            "type": "boolean"
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "category_id",
          "name",
          "description",
          "type",
          "unit_price",
          "unit_cost",
          "currency",
          "is_taxable",
          "is_active",
          "created_at",
          "updated_at"
        ]
      },
      "PricebookDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PricebookItem"
          }
        },
        "required": [
          "data"
        ]
      },
      "CommunicationList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Communication"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MjAyNi0wNS0wN1QwODo1OTowMi4xMjIzNDQrMDA6MDB8MzZhNTlmMDQ"
            ],
            "description": "Opaque cursor. Pass back as `?starting_after=<cursor>` to fetch the next page."
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ]
      },
      "Communication": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "customer_id": {
            "type": "string",
            "format": "uuid"
          },
          "job_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "channel": {
            "type": "string",
            "examples": [
              "sms"
            ]
          },
          "direction": {
            "type": "string",
            "examples": [
              "outbound"
            ]
          },
          "type": {
            "type": "string",
            "examples": [
              "on_my_way"
            ]
          },
          "recipient": {
            "type": "string",
            "examples": [
              "+15555550100"
            ]
          },
          "message_body": {
            "type": "string",
            "examples": [
              "Hi Jenny, this is Acme HVAC. Your tech is on the way — ETA 9:15am."
            ]
          },
          "status": {
            "type": "string",
            "examples": [
              "delivered"
            ]
          },
          "twilio_message_sid": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "SM3a8f7e6d5c4b3a2918e7f6d5c4b3a291"
            ]
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "customer_id",
          "job_id",
          "channel",
          "direction",
          "type",
          "recipient",
          "message_body",
          "status",
          "twilio_message_sid",
          "sent_at",
          "created_at",
          "updated_at"
        ]
      },
      "CommunicationDetail": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Communication"
          }
        },
        "required": [
          "data"
        ]
      }
    },
    "parameters": {}
  },
  "paths": {
    "/customers": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/customers \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/customers\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time",
              "description": "Return customers created strictly after this ISO-8601 timestamp."
            },
            "required": false,
            "description": "Return customers created strictly after this ISO-8601 timestamp.",
            "name": "created_after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Create or upsert a customer",
        "description": "POSTing the same `external_id` + `external_source` twice upserts the existing row instead of creating a duplicate.",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST https://api.runacall.com/v1/customers \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/customers\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerWriteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — an existing row was updated. Returned when the supplied `external_id` + `external_source` matched an existing row (idempotent retry).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "201": {
            "description": "Created — a new row was inserted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/customers/{id}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Get a customer",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/customers/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/customers/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Customers"
        ],
        "summary": "Update a customer",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X PATCH https://api.runacall.com/v1/customers/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/customers/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerPatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — row updated. Body returns the full updated resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/customers/lookup": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Look up a customer by phone, email, or external_id",
        "description": "Returns the most-recent match plus a `has_duplicates` hint when more than one customer shares the lookup key.",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/customers/lookup \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/customers/lookup\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "phone",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "email"
            },
            "required": false,
            "name": "email",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "external_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "external_source",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Matched resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerLookup"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/properties": {
      "get": {
        "tags": [
          "Properties"
        ],
        "summary": "List properties",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/properties \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/properties\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "customer_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "created_after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Properties"
        ],
        "summary": "Create or upsert a property",
        "description": "Provide `customer_id` (UUID) OR `customer_external_id` + `customer_external_source` to identify the owning customer.",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST https://api.runacall.com/v1/properties \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/properties\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PropertyWriteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — an existing row was updated. Returned when the supplied `external_id` + `external_source` matched an existing row (idempotent retry).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyDetail"
                }
              }
            }
          },
          "201": {
            "description": "Created — a new row was inserted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/properties/{id}": {
      "get": {
        "tags": [
          "Properties"
        ],
        "summary": "Get a propertie",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/properties/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/properties/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Properties"
        ],
        "summary": "Update a property",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X PATCH https://api.runacall.com/v1/properties/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/properties/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PropertyPatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — row updated. Body returns the full updated resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/properties/lookup": {
      "get": {
        "tags": [
          "Properties"
        ],
        "summary": "Look up a property by external_id",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/properties/lookup \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/properties/lookup\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "external_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "external_source",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Matched resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/equipment": {
      "get": {
        "tags": [
          "Equipment"
        ],
        "summary": "List equipment",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/equipment \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/equipment\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "property_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "created_after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EquipmentList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Equipment"
        ],
        "summary": "Create or upsert equipment",
        "description": "Provide `property_id` (UUID) OR `property_external_id` + `property_external_source` to identify the property the unit is installed at.",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST https://api.runacall.com/v1/equipment \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/equipment\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EquipmentWriteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — an existing row was updated. Returned when the supplied `external_id` + `external_source` matched an existing row (idempotent retry).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EquipmentDetail"
                }
              }
            }
          },
          "201": {
            "description": "Created — a new row was inserted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EquipmentDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/equipment/{id}": {
      "get": {
        "tags": [
          "Equipment"
        ],
        "summary": "Get a equipment",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/equipment/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/equipment/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EquipmentDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Equipment"
        ],
        "summary": "Update equipment",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X PATCH https://api.runacall.com/v1/equipment/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/equipment/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EquipmentPatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — row updated. Body returns the full updated resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EquipmentDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/equipment/lookup": {
      "get": {
        "tags": [
          "Equipment"
        ],
        "summary": "Look up equipment by serial number or external_id",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/equipment/lookup \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/equipment/lookup\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "serial_number",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "external_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "external_source",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Matched resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EquipmentLookup"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/jobs": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "List jobs",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/jobs \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/jobs\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "customer_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "created",
                "scheduled",
                "en_route",
                "in_progress",
                "completed",
                "invoiced",
                "closed",
                "follow_up",
                "cancelled"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "scheduled_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "created_after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Jobs"
        ],
        "summary": "Create or upsert a job",
        "description": "Required FKs (customer, property) can be supplied as UUID OR external_id pair. equipment is optional, same dual form. PATCH on this resource is status-only.",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST https://api.runacall.com/v1/jobs \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/jobs\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobWriteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — an existing row was updated. Returned when the supplied `external_id` + `external_source` matched an existing row (idempotent retry).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobDetail"
                }
              }
            }
          },
          "201": {
            "description": "Created — a new row was inserted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get a job",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/jobs/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/jobs/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Jobs"
        ],
        "summary": "Update a job's status",
        "description": "Status-only. Invalid transitions per VALID_STATUS_TRANSITIONS return 422 (e.g. completed → scheduled). No-op (same status) returns 200 with the unchanged row.",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X PATCH https://api.runacall.com/v1/jobs/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/jobs/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobPatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — row updated. Body returns the full updated resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/memberships": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "List memberships",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/memberships \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/memberships\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "customer_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "suspended",
                "cancelled",
                "expired"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Memberships"
        ],
        "summary": "Create or upsert a membership",
        "description": "Provide `customer_id` (UUID) OR `customer_external_id` + `customer_external_source` for the owning customer. `service_agreement_id` is UUID-only — that table has no external_id columns in V1. PATCH on this resource is status-only.",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST https://api.runacall.com/v1/memberships \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/memberships\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MembershipWriteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — an existing row was updated. Returned when the supplied `external_id` + `external_source` matched an existing row (idempotent retry).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipDetail"
                }
              }
            }
          },
          "201": {
            "description": "Created — a new row was inserted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/memberships/{id}": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "Get a membership",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/memberships/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/memberships/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Memberships"
        ],
        "summary": "Update a membership's status",
        "security": [
          {
            "bearerAuth": [
              "write"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X PATCH https://api.runacall.com/v1/memberships/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '<see request body schema>'"
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/memberships/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({ /* see request body schema */ }),\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MembershipPatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — row updated. Body returns the full updated resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent INSERT race lost — another request with the same `external_id` + `external_source` won. Retry the same payload; the upsert will hit the existing row and return 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Request body validation failed (Zod). Common causes: missing required field, unknown field (.strict()), `external_id` without `external_source`, FK reference not found in this organization, invalid job status transition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/memberships/lookup": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "Look up a membership by external_id",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/memberships/lookup \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/memberships/lookup\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "external_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "external_source",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Matched resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/estimates": {
      "get": {
        "tags": [
          "Estimates"
        ],
        "summary": "List estimates",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/estimates \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/estimates\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "customer_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "viewed",
                "approved",
                "declined",
                "expired"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EstimateList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/estimates/{id}": {
      "get": {
        "tags": [
          "Estimates"
        ],
        "summary": "Get a estimate",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/estimates/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/estimates/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EstimateDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/estimates/lookup": {
      "get": {
        "tags": [
          "Estimates"
        ],
        "summary": "Look up an estimate by external_id",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/estimates/lookup \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/estimates/lookup\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "external_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "external_source",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Matched resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EstimateDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/invoices": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "List invoices",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/invoices \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/invoices\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "customer_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "viewed",
                "paid",
                "partial",
                "overdue",
                "void"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Convenience shortcut: `paid=true` → status='paid'; `paid=false` → status≠'paid'."
            },
            "required": false,
            "description": "Convenience shortcut: `paid=true` → status='paid'; `paid=false` → status≠'paid'.",
            "name": "paid",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time",
              "description": "Filter by `sent_at > issued_after` (not created_at — drafts predate issuance)."
            },
            "required": false,
            "description": "Filter by `sent_at > issued_after` (not created_at — drafts predate issuance).",
            "name": "issued_after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{id}": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Get a invoice",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/invoices/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/invoices/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/invoices/lookup": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Look up an invoice by external_id",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/invoices/lookup \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/invoices/lookup\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "external_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "external_source",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Matched resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/payments": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "List payments",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/payments \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/payments\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "invoice_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "succeeded",
                "pending",
                "failed",
                "refunded"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "credit_card",
                "ach",
                "check",
                "cash"
              ]
            },
            "required": false,
            "name": "method",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "collected_after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/payments/{id}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Get a payment",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/payments/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/payments/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/voice-calls": {
      "get": {
        "tags": [
          "Voice Calls"
        ],
        "summary": "List voice calls",
        "description": "List endpoint excludes `transcript` and `recording_url` to keep payloads small. Detail includes both.",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/voice-calls \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/voice-calls\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "customer_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "new",
                "reviewed",
                "converted",
                "dismissed"
              ]
            },
            "required": false,
            "name": "triage_status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "created_after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceCallList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/voice-calls/{id}": {
      "get": {
        "tags": [
          "Voice Calls"
        ],
        "summary": "Get a voice call",
        "description": "Returns full call detail including transcript text and recording_url. Recording URLs are signed when fetched from Supabase Storage; they expire after 15 minutes.",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/voice-calls/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/voice-calls/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceCallDetailEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/pricebook": {
      "get": {
        "tags": [
          "Pricebook"
        ],
        "summary": "List pricebook",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/pricebook \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/pricebook\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "category_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "service",
                "part",
                "labor",
                "bundle"
              ]
            },
            "required": false,
            "name": "type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PricebookList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/pricebook/{id}": {
      "get": {
        "tags": [
          "Pricebook"
        ],
        "summary": "Get a pricebook",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/pricebook/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/pricebook/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PricebookDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/communications": {
      "get": {
        "tags": [
          "Communications"
        ],
        "summary": "List communications",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/communications \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/communications\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Page size. Default 50, hard-capped at 100.",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "description": "Page size. Default 50, hard-capped at 100.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque cursor from a previous response's `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "name": "starting_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "customer_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "job_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "sms",
                "email"
              ]
            },
            "required": false,
            "name": "channel",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "outbound",
                "inbound"
              ]
            },
            "required": false,
            "name": "direction",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "sent",
                "delivered",
                "failed",
                "read"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "sent_after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommunicationList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/communications/{id}": {
      "get": {
        "tags": [
          "Communications"
        ],
        "summary": "Get a communication",
        "security": [
          {
            "bearerAuth": [
              "read"
            ]
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.runacall.com/v1/communications/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a \\\n  -H \"Authorization: Bearer rkc_live_...\""
          },
          {
            "lang": "javascript",
            "label": "Node.js",
            "source": "const res = await fetch(\"https://api.runacall.com/v1/communications/49f1efa6-9a3c-4287-8d12-2e8c79b3f50a\", {\n  headers: {\n    Authorization: `Bearer ${process.env.RUNACALL_API_KEY}`,\n  },\n});\nconst data = await res.json();"
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Resource UUID."
            },
            "required": true,
            "description": "Resource UUID.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Single resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommunicationDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — query parameter validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (or exists in another org — existence is masked).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Retry after the seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "customer.created": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "customer.created",
        "description": "A new customer was created (UI, REST API, or imported). Fires once per customer row.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerCreatedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "customer.updated": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "customer.updated",
        "description": "A user-initiated change to a customer's contact info, billing address, or tags. Background sync writes (QBO sync, voice-driven last-contact) do not fire this event.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerUpdatedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "job.scheduled": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "job.scheduled",
        "description": "A job's status transitioned to `scheduled`. Always fires alongside `job.status_changed`.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobScheduledEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "job.status_changed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "job.status_changed",
        "description": "A job's status changed. Fires on every real transition. Lifecycle-specific events (`job.scheduled`, `job.completed`) fire alongside this one when the new status matches.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobStatus_changedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "job.completed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "job.completed",
        "description": "A job's status transitioned to `completed`. Always fires alongside `job.status_changed`.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobCompletedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "estimate.sent": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "estimate.sent",
        "description": "An estimate was sent to the customer. Fires on every send including re-sends (correcting a typo, follow-up nudge). Each send is a customer-touching action.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EstimateSentEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "estimate.approved": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "estimate.approved",
        "description": "A customer approved an estimate, either via the public approval token or in-person on a tech's device. The emitted row's `converted_job_id` is populated when the auto-conversion path runs.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EstimateApprovedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "invoice.issued": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "invoice.issued",
        "description": "An invoice transitioned from `draft` to `sent` (first issuance). Re-sends of an already-sent invoice do NOT fire this event — it's a state-transition signal, not a touchpoint signal.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceIssuedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "invoice.paid": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "invoice.paid",
        "description": "A payment closed the invoice — sum of recorded payments ≥ invoice total. Fires immediately after `payment.received` for the closing payment.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoicePaidEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "payment.received": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "payment.received",
        "description": "Any payment recorded against an invoice (cash, check, ACH, credit card, Stripe Connect). Fires before `invoice.paid` when the same payment closes the invoice.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentReceivedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "voice.call.completed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "voice.call.completed",
        "description": "An AI receptionist call finished post-call analysis. Fires for spam-classified calls too (subscribers may want them for fraud / observability).",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VoiceCallCompletedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "voice.followup.created": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "voice.followup.created",
        "description": "The AI receptionist or a Retell mid-call tool created a follow-up task. Single canonical INSERT seam — auto-resolver only updates status, never re-fires this event.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VoiceFollowupCreatedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "membership.created": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "membership.created",
        "description": "A new customer membership was enrolled (UI or REST).",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MembershipCreatedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "membership.renewed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "membership.renewed",
        "description": "Reserved. Not currently emitted — the Stripe Connect `invoice.paid` → `customer_memberships` correlation requires a Connect-routed handler that ships in a future phase. Subscribers should poll `GET /api/v1/memberships?status=active` until then.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MembershipRenewedEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    },
    "membership.cancelled": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "membership.cancelled",
        "description": "A customer membership was cancelled (UI action, REST PATCH to `status='cancelled'`, or Stripe Connect subscription deletion). All three paths emit.",
        "requestBody": {
          "required": true,
          "description": "Webhook event envelope POST'd to the subscriber URL.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MembershipCancelledEnvelope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber acknowledged delivery."
          },
          "202": {
            "description": "Subscriber accepted delivery for async processing."
          },
          "204": {
            "description": "Subscriber acknowledged delivery (no body)."
          },
          "default": {
            "description": "Anything other than 2xx triggers a retry (up to 3 attempts). After 10 consecutive terminal failures the subscription auto-pauses."
          }
        }
      }
    }
  },
  "x-tagGroups": [
    {
      "name": "API resources",
      "tags": [
        "Customers",
        "Properties",
        "Equipment",
        "Jobs",
        "Memberships",
        "Estimates",
        "Invoices",
        "Payments",
        "Voice Calls",
        "Pricebook",
        "Communications"
      ]
    },
    {
      "name": "Real-time events",
      "tags": [
        "Webhooks"
      ]
    }
  ]
}
